From 75cadb007953446850591a275c590bf486c05fd1 Mon Sep 17 00:00:00 2001 From: stele Date: Sat, 24 Oct 2020 19:14:45 +0200 Subject: [PATCH 01/63] Zamiana metalic na floata z wazonym efektem. --- shaders/light_common.glsl | 17 ++++++----------- shaders/mat_default_specgloss.frag | 2 +- shaders/mat_normalmap_specgloss.frag | 2 +- shaders/mat_parallax_specgloss.frag | 6 +++--- shaders/mat_reflmap_specgloss.frag | 2 +- shaders/mat_shadowlessnormalmap_specgloss.frag | 2 +- shaders/mat_sunlessnormalmap_specgloss.frag | 2 +- shaders/mat_water_specgloss.frag | 2 +- 8 files changed, 15 insertions(+), 20 deletions(-) diff --git a/shaders/light_common.glsl b/shaders/light_common.glsl index 79b8ce1e..f22394e6 100644 --- a/shaders/light_common.glsl +++ b/shaders/light_common.glsl @@ -8,7 +8,7 @@ uniform sampler2D headlightmap; #include float glossiness = 1.0; -bool metalic = false; +float metalic = 0.0; float length2(vec3 v) { @@ -167,16 +167,11 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl fragcolor += emissioncolor; vec3 specularcolor = specularamount * lights[0].color; - if ((param[1].w < 0.0) || (metalic == true)) - { - fragcolor += specularcolor; - fragcolor *= texturecolor; - } - else - { - fragcolor *= texturecolor; - fragcolor += specularcolor; - } + if (param[1].w < 0.0) + { + float metalic = 1.0; + } + fragcolor = mix(((fragcolor + specularcolor) * texturecolor),(fragcolor * texturecolor + specularcolor),metalic) ; return fragcolor; } diff --git a/shaders/mat_default_specgloss.frag b/shaders/mat_default_specgloss.frag index a72f04d5..cb7a2e5a 100644 --- a/shaders/mat_default_specgloss.frag +++ b/shaders/mat_default_specgloss.frag @@ -43,7 +43,7 @@ void main() float reflectivity = param[1].z; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - metalic = (texture(specgloss, f_coord).b > 0.5) ? true : false; + float metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); diff --git a/shaders/mat_normalmap_specgloss.frag b/shaders/mat_normalmap_specgloss.frag index 08059c28..86085df2 100644 --- a/shaders/mat_normalmap_specgloss.frag +++ b/shaders/mat_normalmap_specgloss.frag @@ -53,7 +53,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - metalic = (texture(specgloss, f_coord).b > 0.5) ? true : false; + float metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_parallax_specgloss.frag b/shaders/mat_parallax_specgloss.frag index 74e9e78c..4ce17da7 100644 --- a/shaders/mat_parallax_specgloss.frag +++ b/shaders/mat_parallax_specgloss.frag @@ -59,9 +59,9 @@ void main() normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0)); vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz)); float reflectivity = param[1].z * texture(normalmap, f_coord_p).a; - float specularity = texture(specgloss, f_coord).r; - glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - metalic = (texture(specgloss, f_coord).b > 0.5) ? true : false; + float specularity = texture(specgloss, f_coord_p).r; + glossiness = texture(specgloss, f_coord_p).g * abs(param[1].w); + float metalic = texture(specgloss, f_coord_p).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_reflmap_specgloss.frag b/shaders/mat_reflmap_specgloss.frag index 60f99add..b5f9bc38 100644 --- a/shaders/mat_reflmap_specgloss.frag +++ b/shaders/mat_reflmap_specgloss.frag @@ -47,7 +47,7 @@ void main() float reflectivity = param[1].z * texture(reflmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - metalic = (texture(specgloss, f_coord).b > 0.5) ? true : false; + float metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_shadowlessnormalmap_specgloss.frag b/shaders/mat_shadowlessnormalmap_specgloss.frag index ac0775b4..58175c7c 100644 --- a/shaders/mat_shadowlessnormalmap_specgloss.frag +++ b/shaders/mat_shadowlessnormalmap_specgloss.frag @@ -53,7 +53,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - metalic = (texture(specgloss, f_coord).b > 0.5) ? true : false; + float metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, 1.0); vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); diff --git a/shaders/mat_sunlessnormalmap_specgloss.frag b/shaders/mat_sunlessnormalmap_specgloss.frag index ca84c346..cf5a7433 100644 --- a/shaders/mat_sunlessnormalmap_specgloss.frag +++ b/shaders/mat_sunlessnormalmap_specgloss.frag @@ -110,7 +110,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - metalic = (texture(specgloss, f_coord).b > 0.5) ? true : false; + float metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights_sunless(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); diff --git a/shaders/mat_water_specgloss.frag b/shaders/mat_water_specgloss.frag index 3dc3adcd..8d263a83 100644 --- a/shaders/mat_water_specgloss.frag +++ b/shaders/mat_water_specgloss.frag @@ -64,7 +64,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, texture_coords ).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - metalic = (texture(specgloss, f_coord).b > 0.5) ? true : false; + float metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); From d3ba7c10fc52b6d1f6c04b88cf8333b87462d786 Mon Sep 17 00:00:00 2001 From: stele Date: Sun, 13 Dec 2020 21:24:36 +0100 Subject: [PATCH 02/63] Detail normalmap shaders @Jan21 --- shaders/mat_detail_normalmap.frag | 81 +++++++++++ shaders/mat_detail_normalmap_specgloss.frag | 89 ++++++++++++ shaders/mat_detail_parallax.frag | 134 +++++++++++++++++++ shaders/mat_detail_parallax_specgloss.frag | 141 ++++++++++++++++++++ 4 files changed, 445 insertions(+) create mode 100644 shaders/mat_detail_normalmap.frag create mode 100644 shaders/mat_detail_normalmap_specgloss.frag create mode 100644 shaders/mat_detail_parallax.frag create mode 100644 shaders/mat_detail_parallax_specgloss.frag diff --git a/shaders/mat_detail_normalmap.frag b/shaders/mat_detail_normalmap.frag new file mode 100644 index 00000000..85f55289 --- /dev/null +++ b/shaders/mat_detail_normalmap.frag @@ -0,0 +1,81 @@ +in vec3 f_normal; +in vec2 f_coord; +in vec4 f_pos; +in mat3 f_tbn; + +in vec4 f_clip_pos; +in vec4 f_clip_future_pos; + +#include + +layout(location = 0) out vec4 out_color; +#if MOTIONBLUR_ENABLED +layout(location = 1) out vec4 out_motion; +#endif + +#param (color, 0, 0, 4, diffuse) +#param (diffuse, 1, 0, 1, diffuse) +#param (specular, 1, 1, 1, specular) +#param (reflection, 1, 2, 1, one) +#param (glossiness, 1, 3, 1, glossiness) +#param (detail_scale, 2, 0, 1, one) +#param (detail_height_scale, 2, 1, 1, one) + +#texture (diffuse, 0, sRGB_A) +uniform sampler2D diffuse; + +#texture (normalmap, 1, RGBA) +uniform sampler2D normalmap; + +#texture (detailnormalmap, 2, RGBA) +uniform sampler2D detailnormalmap; + +#define NORMALMAP +#include +#include +#include + +void main() +{ + vec4 tex_color = texture(diffuse, f_coord); + + bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) ); + if(alphatestfail) + discard; + + vec3 fragcolor = ambient; + + vec4 normal_map = texture(normalmap, f_coord); + vec4 detailnormal_map = texture(detailnormalmap, f_coord * param[2].x); + vec3 normal; + vec3 normaldetail; + + normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0; + normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0)); + normaldetail.xyz = normaldetail.xyz * param[2].y; + normal.xy = normal_map.rg* 2.0 - 1.0; + normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0)); + + vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z))); + + + float reflblend = (normal_map.a + detailnormal_map.a); + float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend; + float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5; + + fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); + vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); +#if POSTFX_ENABLED + out_color = color; +#else + out_color = tonemap(color); +#endif +#if MOTIONBLUR_ENABLED + { + vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;; + vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;; + + out_motion = vec4(a - b, 0.0f, 0.0f); + } +#endif +} diff --git a/shaders/mat_detail_normalmap_specgloss.frag b/shaders/mat_detail_normalmap_specgloss.frag new file mode 100644 index 00000000..f2c30763 --- /dev/null +++ b/shaders/mat_detail_normalmap_specgloss.frag @@ -0,0 +1,89 @@ +in vec3 f_normal; +in vec2 f_coord; +in vec4 f_pos; +in mat3 f_tbn; + +in vec4 f_clip_pos; +in vec4 f_clip_future_pos; + +#include + +layout(location = 0) out vec4 out_color; +#if MOTIONBLUR_ENABLED +layout(location = 1) out vec4 out_motion; +#endif + +#param (color, 0, 0, 4, diffuse) +#param (diffuse, 1, 0, 1, diffuse) +#param (specular, 1, 1, 1, specular) +#param (reflection, 1, 2, 1, one) +#param (glossiness, 1, 3, 1, glossiness) +#param (detail_scale, 2, 0, 1, one) +#param (detail_height_scale, 2, 1, 1, one) + +#texture (diffuse, 0, sRGB_A) +uniform sampler2D diffuse; + +#texture (normalmap, 1, RGBA) +uniform sampler2D normalmap; + +#texture (detailnormalmap, 2, RGBA) +uniform sampler2D detailnormalmap; + +#texture (specgloss, 3, RGBA) +uniform sampler2D specgloss; + +#define NORMALMAP +#include +#include +#include + +void main() +{ + vec4 tex_color = texture(diffuse, f_coord); + + bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) ); + if(alphatestfail) + discard; +// if (tex_color.a < opacity) +// discard; + + vec3 fragcolor = ambient; + + vec4 normal_map = texture(normalmap, f_coord); + vec4 detailnormal_map = texture(detailnormalmap, f_coord * param[2].x); + vec4 specgloss_map = texture(specgloss, f_coord); + vec3 normal; + vec3 normaldetail; + + normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0; + normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0)); + normaldetail.xyz = normaldetail.xyz * param[2].y; + normal.xy = normal_map.rg* 2.0 - 1.0; + normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0)); + + vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z))); + + float reflblend = (normal_map.a + detailnormal_map.a); + float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend; + float specularity = specgloss_map.r; + glossiness = specgloss_map.g * abs(param[1].w); + metalic = (specgloss_map.b > 0.5) ? true : false; + + + fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); + vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); +#if POSTFX_ENABLED + out_color = color; +#else + out_color = tonemap(color); +#endif +#if MOTIONBLUR_ENABLED + { + vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;; + vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;; + + out_motion = vec4(a - b, 0.0f, 0.0f); + } +#endif +} diff --git a/shaders/mat_detail_parallax.frag b/shaders/mat_detail_parallax.frag new file mode 100644 index 00000000..75919b13 --- /dev/null +++ b/shaders/mat_detail_parallax.frag @@ -0,0 +1,134 @@ +in vec3 f_normal; +in vec2 f_coord; +in vec4 f_pos; +in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos; +in vec4 f_clip_pos; +in vec4 f_clip_future_pos; +in vec3 TangentFragPos; + +#include + +layout(location = 0) out vec4 out_color; +#if MOTIONBLUR_ENABLED +layout(location = 1) out vec4 out_motion; +#endif + + +#param (color, 0, 0, 4, diffuse) +#param (diffuse, 1, 0, 1, diffuse) +#param (specular, 1, 1, 1, specular) +#param (reflection, 1, 2, 1, one) +#param (glossiness, 1, 3, 1, glossiness) +#param (detail_scale, 2, 0, 1, one) +#param (detail_height_scale, 2, 1, 1, one) +#param (height_scale, 2, 2, 1, zero) +#param (height_offset, 2, 3, 1, zero) + +#texture (diffuse, 0, sRGB_A) +uniform sampler2D diffuse; + +#texture (normalmap, 1, RGBA) +uniform sampler2D normalmap; + +#texture (detailnormalmap, 2, RGBA) +uniform sampler2D detailnormalmap; + +#define PARALLAX +#include +#include +#include + +vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir); + +void main() +{ + //parallax mapping + vec3 viewDir = normalize(-TangentFragPos); //tangent view pos - tangent frag pos + vec2 f_coord_p = ParallaxMapping(f_coord, viewDir); + vec4 normal_map = texture(normalmap, f_coord_p); + vec4 detailnormal_map = texture(detailnormalmap, f_coord_p * param[2].x); + vec4 tex_color = texture(diffuse, f_coord_p); + + bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) ); + if(alphatestfail) + discard; +// if (tex_color.a < opacity) +// discard; + + vec3 fragcolor = ambient; + + vec3 normal; + vec3 normaldetail; + + normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0; + normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0)); + normaldetail.xyz = normaldetail.xyz * param[2].y; + normal.xy = normal_map.rg* 2.0 - 1.0; + normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0)); + + vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z))); + float reflectivity = param[1].z * normal_map.a; + float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5; + + fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); + + vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); +#if POSTFX_ENABLED + out_color = color; +#else + out_color = tonemap(color); +#endif +#if MOTIONBLUR_ENABLED + { + vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;; + vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;; + + out_motion = vec4(a - b, 0.0f, 0.0f); + } +#endif +} + +vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir) +{ + float pos_len = length(f_pos.xyz); + + if (pos_len > 100.0) { + return f_coord; + } + +#if EXTRAEFFECTS_ENABLED + const float minLayers = 8.0; + const float maxLayers = 32.0; + float LayersWeight = pos_len / 20.0; + vec2 currentTexCoords = f_coord; + float currentDepthMapValue = texture(normalmap, currentTexCoords).b; + LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight); + float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers + float layerDepth = 1.0 / numLayers; // calculate the size of each layer + float currentLayerDepth = 0.0; // depth of current layer + vec2 P = viewDir.xy * param[2].z; // the amount to shift the texture coordinates per layer (from vector P) + vec2 deltaTexCoords = P / numLayers; + + + while(currentLayerDepth < currentDepthMapValue) + { + currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P + currentDepthMapValue = texture(normalmap, currentTexCoords).b; // get depthmap value at current texture coordinates + currentLayerDepth += layerDepth; // get depth of next layer + } + + vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations) + + float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation + float beforeDepth = texture(normalmap, prevTexCoords).b - currentLayerDepth + layerDepth; + + float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates + vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight); + + return finalTexCoords; +#else + float height = texture(normalmap, f_coord).b; + vec2 p = viewDir.xy / viewDir.z * (height * (param[2].z - param[2].w) * 0.2); + return f_coord - p; +#endif +} diff --git a/shaders/mat_detail_parallax_specgloss.frag b/shaders/mat_detail_parallax_specgloss.frag new file mode 100644 index 00000000..40d914f6 --- /dev/null +++ b/shaders/mat_detail_parallax_specgloss.frag @@ -0,0 +1,141 @@ +in vec3 f_normal; +in vec2 f_coord; +in vec4 f_pos; +in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos; +in vec4 f_clip_pos; +in vec4 f_clip_future_pos; +in vec3 TangentFragPos; + +#include + +layout(location = 0) out vec4 out_color; +#if MOTIONBLUR_ENABLED +layout(location = 1) out vec4 out_motion; +#endif + + +#param (color, 0, 0, 4, diffuse) +#param (diffuse, 1, 0, 1, diffuse) +#param (specular, 1, 1, 1, specular) +#param (reflection, 1, 2, 1, one) +#param (glossiness, 1, 3, 1, glossiness) +#param (detail_scale, 2, 0, 1, one) +#param (detail_height_scale, 2, 1, 1, one) +#param (height_scale, 2, 2, 1, zero) +#param (height_offset, 2, 3, 1, zero) + +#texture (diffuse, 0, sRGB_A) +uniform sampler2D diffuse; + +#texture (normalmap, 1, RGBA) +uniform sampler2D normalmap; + +#texture (specgloss, 2, RGBA) +uniform sampler2D specgloss; + +#texture (detailnormalmap, 3, RGBA) +uniform sampler2D detailnormalmap; + + +#define PARALLAX +#include +#include +#include + +vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir); + +void main() +{ + //parallax mapping + vec3 viewDir = normalize(vec3(0.0f, 0.0f, 0.0f) - TangentFragPos); //tangent view pos - tangent frag pos + vec2 f_coord_p = ParallaxMapping(f_coord, viewDir); + + vec4 normal_map = texture(normalmap, f_coord_p); + vec4 detailnormal_map = texture(detailnormalmap, f_coord_p * param[2].x); + vec4 tex_color = texture(diffuse, f_coord_p); + vec4 specgloss_map = texture(specgloss, f_coord_p); + + bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) ); + if(alphatestfail) + discard; + + vec3 fragcolor = ambient; + + vec3 normal; + vec3 normaldetail; + + normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0; + normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0)); + normaldetail.xyz = normaldetail.xyz * param[2].y; + normal.xy = normal_map.rg* 2.0 - 1.0; + normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0)); + + vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z))); + float reflectivity = param[1].z * normal_map.a; + float specularity = specgloss_map.r; + glossiness = specgloss_map.g * abs(param[1].w); + metalic = (specgloss_map.b > 0.5) ? true : false; + + fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); + + vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); +#if POSTFX_ENABLED + out_color = color; +#else + out_color = tonemap(color); +#endif +#if MOTIONBLUR_ENABLED + { + vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;; + vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;; + + out_motion = vec4(a - b, 0.0f, 0.0f); + } +#endif +} + +vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir) +{ + + float pos_len = length(f_pos.xyz); + + if (pos_len > 100.0) { + return f_coord; + } + +#if EXTRAEFFECTS_ENABLED + const float minLayers = 8.0; + const float maxLayers = 32.0; + float LayersWeight = pos_len / 20.0; + vec2 currentTexCoords = f_coord; + float currentDepthMapValue = texture(normalmap, currentTexCoords).b; + LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight); + float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers + float layerDepth = 1.0 / numLayers; // calculate the size of each layer + float currentLayerDepth = 0.0; // depth of current layer + vec2 P = viewDir.xy * param[2].z; // the amount to shift the texture coordinates per layer (from vector P) + vec2 deltaTexCoords = P / numLayers; + + + while(currentLayerDepth < currentDepthMapValue) + { + currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P + currentDepthMapValue = texture(normalmap, currentTexCoords).b; // get depthmap value at current texture coordinates + currentLayerDepth += layerDepth; // get depth of next layer + } + + vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations) + + float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation + float beforeDepth = texture(normalmap, currentTexCoords).b; - currentLayerDepth + layerDepth; + + float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates + vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight); + + return finalTexCoords; +#else + float height = normal_map.b; + vec2 p = viewDir.xy / viewDir.z * (height * (param[2].z - param[2].w) * 0.2); + return f_coord - p; +#endif +} From 3ac9b0381ffe0e73449a21da789affde75c43261 Mon Sep 17 00:00:00 2001 From: stele Date: Sat, 23 Jan 2021 18:14:30 +0100 Subject: [PATCH 03/63] Wheels animation distance changed to 200*D*LODbias --- DynObj.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/DynObj.cpp b/DynObj.cpp index 99d07a17..394ec012 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -4937,9 +4937,8 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co { //++iAnimatedAxles; pAnimations[i].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu pAnimations[i].yUpdate = std::bind( &TDynamicObject::UpdateAxle, this, std::placeholders::_1 ); - pAnimations[i].fMaxDist = 50 * MoverParameters->WheelDiameter; // nie kręcić w większej odległości - pAnimations[i].fMaxDist *= pAnimations[i].fMaxDist * MoverParameters->WheelDiameter; // 50m do kwadratu, a średnica do trzeciej - pAnimations[i].fMaxDist *= Global.fDistanceFactor; // współczynnik przeliczeniowy jakości ekranu + pAnimations[i].fMaxDist = Global.fDistanceFactor * MoverParameters->WheelDiameter * 200; + pAnimations[i].fMaxDist *= pAnimations[i].fMaxDist; } } // Ra: ustawianie indeksów osi From 6b214b473f0c6a2835e5a94ed171a1e08e5e4e28 Mon Sep 17 00:00:00 2001 From: stele Date: Sat, 23 Jan 2021 18:42:25 +0100 Subject: [PATCH 04/63] Fixed spelling error in event syntax; old one remains for compatibility. --- Event.cpp | 2 +- ref/asio | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 ref/asio diff --git a/Event.cpp b/Event.cpp index b59c9fa3..dc822f18 100644 --- a/Event.cpp +++ b/Event.cpp @@ -191,7 +191,7 @@ basic_event::event_conditions::deserialize( cParser &Input ) { else if( token == "trackfree" ) { flags |= flags::track_free; } - else if( token == "propability" ) { + else if( token == "propability" || "probability") { //remove propability in few years after changing old scenery scripts 01.2021 flags |= flags::probability; Input.getTokens(); Input >> probability; diff --git a/ref/asio b/ref/asio new file mode 160000 index 00000000..22afb860 --- /dev/null +++ b/ref/asio @@ -0,0 +1 @@ +Subproject commit 22afb86087a77037cd296d27134756c9b0d2cb75 From 49f01b16902005aaa56f4a48b70c98081cd56087 Mon Sep 17 00:00:00 2001 From: stele Date: Sat, 23 Jan 2021 19:32:25 +0100 Subject: [PATCH 05/63] Spelling error left in log text --- Event.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Event.cpp b/Event.cpp index dc822f18..1a3e88af 100644 --- a/Event.cpp +++ b/Event.cpp @@ -266,7 +266,7 @@ basic_event::event_conditions::export_as_text( std::ostream &Output ) const { } if( ( flags & flags::probability ) != 0 ) { Output - << "propability " + << "probability " << probability << ' '; } if( ( flags & ( flags::text | flags::value1 | flags::value2 ) ) != 0 ) { From 134e459dbd9cd226350dedfd13133c95191c6e2c Mon Sep 17 00:00:00 2001 From: stele Date: Sat, 23 Jan 2021 20:11:05 +0100 Subject: [PATCH 06/63] Explicit type conversion --- shaders/light_common.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shaders/light_common.glsl b/shaders/light_common.glsl index f22394e6..74338c94 100644 --- a/shaders/light_common.glsl +++ b/shaders/light_common.glsl @@ -32,7 +32,7 @@ float calc_shadow() //basic // shadow = texture(shadowmap, coords.xyz + vec3(0.0, 0.0, bias)); //PCF - float bias = 0.00005f * (cascade + 1U); + float bias = 0.00005f * float(cascade + 1U); vec2 texel = vec2(1.0) / vec2(textureSize(shadowmap, 0)); float radius = 1.0; for (float y = -1.5; y <= 1.5; y += 1.0) From 4f11ead6401f30599f0201c0b1de17411e6f2634 Mon Sep 17 00:00:00 2001 From: stele Date: Sat, 23 Jan 2021 20:20:07 +0100 Subject: [PATCH 07/63] Leftover metalic as bool --- shaders/mat_detail_normalmap_specgloss.frag | 2 +- shaders/mat_detail_parallax_specgloss.frag | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shaders/mat_detail_normalmap_specgloss.frag b/shaders/mat_detail_normalmap_specgloss.frag index f2c30763..fbd90257 100644 --- a/shaders/mat_detail_normalmap_specgloss.frag +++ b/shaders/mat_detail_normalmap_specgloss.frag @@ -68,7 +68,7 @@ void main() float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend; float specularity = specgloss_map.r; glossiness = specgloss_map.g * abs(param[1].w); - metalic = (specgloss_map.b > 0.5) ? true : false; + float metalic = specgloss_map.b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_detail_parallax_specgloss.frag b/shaders/mat_detail_parallax_specgloss.frag index 40d914f6..dc621dc1 100644 --- a/shaders/mat_detail_parallax_specgloss.frag +++ b/shaders/mat_detail_parallax_specgloss.frag @@ -74,7 +74,7 @@ void main() float reflectivity = param[1].z * normal_map.a; float specularity = specgloss_map.r; glossiness = specgloss_map.g * abs(param[1].w); - metalic = (specgloss_map.b > 0.5) ? true : false; + float metalic = specgloss_map.b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); From 0ff083282dbe15b0641576064534323495c3e919 Mon Sep 17 00:00:00 2001 From: stele Date: Sat, 23 Jan 2021 21:08:20 +0100 Subject: [PATCH 08/63] Syntax error --- shaders/mat_detail_parallax_specgloss.frag | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shaders/mat_detail_parallax_specgloss.frag b/shaders/mat_detail_parallax_specgloss.frag index dc621dc1..e5309317 100644 --- a/shaders/mat_detail_parallax_specgloss.frag +++ b/shaders/mat_detail_parallax_specgloss.frag @@ -67,13 +67,13 @@ void main() normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0; normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0)); normaldetail.xyz = normaldetail.xyz * param[2].y; - normal.xy = normal_map.rg* 2.0 - 1.0; + normal.xy = normal_map.rg* 2.0 - 1.0; normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0)); vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z))); float reflectivity = param[1].z * normal_map.a; float specularity = specgloss_map.r; - glossiness = specgloss_map.g * abs(param[1].w); + float glossiness = specgloss_map.g * abs(param[1].w); float metalic = specgloss_map.b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); @@ -127,7 +127,7 @@ vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir) vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations) float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation - float beforeDepth = texture(normalmap, currentTexCoords).b; - currentLayerDepth + layerDepth; + float beforeDepth = texture(normalmap, prevTexCoords).b - currentLayerDepth + layerDepth; float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight); From c98ff244f9104b818b2a7e3d8601af732f504794 Mon Sep 17 00:00:00 2001 From: stele Date: Sat, 23 Jan 2021 21:22:17 +0100 Subject: [PATCH 09/63] Normalmap sampling inside ParallaxMapping function --- shaders/mat_detail_parallax_specgloss.frag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shaders/mat_detail_parallax_specgloss.frag b/shaders/mat_detail_parallax_specgloss.frag index e5309317..416a6854 100644 --- a/shaders/mat_detail_parallax_specgloss.frag +++ b/shaders/mat_detail_parallax_specgloss.frag @@ -134,7 +134,7 @@ vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir) return finalTexCoords; #else - float height = normal_map.b; + float height = texture(normalmap, f_coord).b; vec2 p = viewDir.xy / viewDir.z * (height * (param[2].z - param[2].w) * 0.2); return f_coord - p; #endif From f92a355e89655608641f4fe905952bf1eee0184f Mon Sep 17 00:00:00 2001 From: stele Date: Sun, 24 Jan 2021 18:27:28 +0100 Subject: [PATCH 10/63] Shader with only detail normalmap --- shaders/mat_default_detail.frag | 70 +++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 shaders/mat_default_detail.frag diff --git a/shaders/mat_default_detail.frag b/shaders/mat_default_detail.frag new file mode 100644 index 00000000..2761aff5 --- /dev/null +++ b/shaders/mat_default_detail.frag @@ -0,0 +1,70 @@ +in vec3 f_normal; +in vec2 f_coord; +in vec4 f_pos; +in mat3 f_tbn; + +in vec4 f_clip_pos; +in vec4 f_clip_future_pos; + +#include + +#param (color, 0, 0, 4, diffuse) +#param (diffuse, 1, 0, 1, diffuse) +#param (specular, 1, 1, 1, specular) +#param (reflection, 1, 2, 1, zero) +#param (glossiness, 1, 3, 1, glossiness) +#param (detail_scale, 2, 0, 1, one) + +#texture (diffuse, 0, sRGB_A) +uniform sampler2D diffuse; + +layout(location = 0) out vec4 out_color; +#if MOTIONBLUR_ENABLED +layout(location = 1) out vec4 out_motion; +#endif + +#texture (detailnormalmap, 1, RGBA) +uniform sampler2D detailnormalmap; + +#define NORMALMAP +#include +#include +#include + +void main() +{ + vec4 tex_color = texture(diffuse, f_coord); + + bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) ); + if(alphatestfail) + discard; +// if (tex_color.a < opacity) +// discard; + + vec3 fragcolor = ambient; + + vec3 normal; + normal.xy = (texture(detailnormalmap, f_coord * param[2].x).rg * 2.0 - 1.0); + normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0)); + vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz)); + float reflectivity = param[1].z; + float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5; + glossiness = abs(param[1].w); + + fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); + vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); +#if POSTFX_ENABLED + out_color = color; +#else + out_color = tonemap(color); +#endif + +#if MOTIONBLUR_ENABLED + { + vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;; + vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;; + + out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult); + } +#endif +} From ea6e75741679b37361b07fe5e81a3e87fcb5d9de Mon Sep 17 00:00:00 2001 From: stele Date: Mon, 25 Jan 2021 00:30:22 +0100 Subject: [PATCH 11/63] Shouldn't take any keyword as random condition anymore. --- Event.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Event.cpp b/Event.cpp index 1a3e88af..1627df8c 100644 --- a/Event.cpp +++ b/Event.cpp @@ -191,7 +191,7 @@ basic_event::event_conditions::deserialize( cParser &Input ) { else if( token == "trackfree" ) { flags |= flags::track_free; } - else if( token == "propability" || "probability") { //remove propability in few years after changing old scenery scripts 01.2021 + 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; From 517c9c89f8256aa30d8e714710c6ddb97a598250 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Thu, 4 Mar 2021 03:41:53 +0100 Subject: [PATCH 12/63] build 210303. vehicle control hint system, virtual trainman toggle, configurable diesel engine rpm deceleration rate, vehicle repair enhancement, debug panel enhancements, customizable vehicle displays refresh rate, idling compressor sound, traction ac motor sound, braking sound enhancement, sky state enhancement, minor bug fixes --- Driver.cpp | 6976 +++++++++++++++++++------------------ Driver.h | 132 +- DynObj.cpp | 473 +-- DynObj.h | 25 +- Event.cpp | 2 +- Globals.cpp | 7 + Globals.h | 3 +- Logs.h | 1 + McZapkie/MOVER.h | 12 +- McZapkie/Mover.cpp | 34 +- MdlMngr.cpp | 2 +- Model3d.cpp | 3 +- Texture.cpp | 2 +- Train.cpp | 100 +- Train.h | 4 +- application.cpp | 6 +- color.h | 4 + driverhints.cpp | 1310 +++++++ drivermode.cpp | 3 +- driveruipanels.cpp | 130 +- maszyna.vcxproj | 1 + maszyna.vcxproj.filters | 3 + opengl33renderer.cpp | 2 +- particles.cpp | 2 +- scene.cpp | 1 + simulationenvironment.cpp | 10 +- simulationenvironment.h | 2 +- translation.cpp | 220 +- translation.h | 104 + uilayer.cpp | 11 + utilities.cpp | 10 +- utilities.h | 20 +- version.h | 4 +- 33 files changed, 5836 insertions(+), 3783 deletions(-) create mode 100644 driverhints.cpp diff --git a/Driver.cpp b/Driver.cpp index 55ae0230..f75b71d6 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -37,6 +37,8 @@ http://mozilla.org/MPL/2.0/. #define LOGBACKSCAN 0 #define LOGPRESS 0 +auto const EU07_AI_NOACCELERATION = -0.05; + // finds point of specified track nearest to specified event. returns: distance to that point from the specified end of the track // TODO: move this to file with all generic routines, too easy to forget it's here and it may come useful double @@ -274,12 +276,17 @@ void TSpeedPos::CommandCheck() default: // inna komenda w evencie skanowanym powoduje zatrzymanie i wysłanie tej komendy // nie manewrowa, nie przystanek, nie zatrzymać na SBL - iFlags &= ~(spShuntSemaphor | spPassengerStopPoint | spStopOnSBL); // jak nieznana komenda w komórce sygnałowej, to zatrzymujemy + fVelNext = 0.0; +/* fVelNext = ( - evEvent->is_command() ? - 0.0 : // ask for a stop if we have a command for the vehicle - -1.0 ); // if we don't or it was already passed then don't be a bother + evEvent->is_command() ? 0.0 : // ask for a stop if we have a command for the vehicle + ( iFlags & ( spSemaphor | spShuntSemaphor )) != 0 ? fVelNext : // don't change semafor velocity + -1.0 ); // otherwise don't be a bother +*/ + // TODO: check whether clearing spShuntSemaphor flag doesn't cause problems + // potentially it can invalidate shunt semaphor used to transmit timetable or similar command + iFlags &= ~(spShuntSemaphor | spPassengerStopPoint | spStopOnSBL); } }; @@ -866,746 +873,173 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN // fDist - dystans w jakim należy rozważyć ruch // fNext - prędkość na końcu tego dystansu // fAcc - zalecane przyspieszenie w chwili obecnej - kryterium wyboru dystansu - double a; // przyspieszenie - double v; // prędkość - double d; // droga - double d_to_next_sem = 10000.0; //ustaiwamy na pewno dalej niż widzi AI + auto a { 0.0 }; // przyspieszenie + auto v { 0.0 }; // prędkość + auto d { 0.0 }; // droga + auto d_to_next_sem { 10000.0 }; //ustaiwamy na pewno dalej niż widzi AI + auto go { TCommandType::cm_Unknown }; + eSignNext = nullptr; IsAtPassengerStop = false; - auto IsScheduledPassengerStopVisible { false }; - TCommandType go = TCommandType::cm_Unknown; - eSignNext = NULL; + IsScheduledPassengerStopVisible = false; // te flagi są ustawiane tutaj, w razie potrzeby iDrivigFlags &= ~(moveTrackEnd | moveSwitchFound | moveSemaphorFound | /*moveSpeedLimitFound*/ moveStopPointFound ); - for( std::size_t i = 0; i < sSpeedTable.size(); ++i ) - { // sprawdzenie rekordów od (iFirst) do (iLast), o ile są istotne - if (sSpeedTable[i].iFlags & spEnabled) // badanie istotności - { // o ile dana pozycja tabelki jest istotna - if (sSpeedTable[i].iFlags & spPassengerStopPoint) { - // jeśli przystanek, trzeba obsłużyć wg rozkładu - iDrivigFlags |= moveStopPointFound; - // stop points are irrelevant when not in one of the basic modes - if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) { continue; } - // first 19 chars of the command is expected to be "PassengerStopPoint:" so we skip them - if( ToLower( sSpeedTable[ i ].evEvent->input_text() ).compare( 19, sizeof( asNextStop ), ToLower( asNextStop ) ) != 0 ) - { // jeśli nazwa nie jest zgodna - if( ( false == IsScheduledPassengerStopVisible ) // check if our next scheduled stop didn't show up earlier in the scan - && ( sSpeedTable[i].fDist < ( 1.15 * fBrakeDist + 300 ) ) - && ( sSpeedTable[i].fDist > 0 ) ) // tylko jeśli W4 jest blisko, przy dwóch może zaczać szaleć - { - // porównuje do następnej stacji, więc trzeba przewinąć do poprzedniej - // nastepnie ustawić następną na aktualną tak żeby prawidłowo ją obsłużył w następnym kroku - if( true == TrainParams.RewindTimeTable( sSpeedTable[ i ].evEvent->input_text() ) ) { - asNextStop = TrainParams.NextStop(); - iStationStart = TrainParams.StationIndex; - } - } - else if( sSpeedTable[ i ].fDist < -fLength ) { - // jeśli został przejechany - sSpeedTable[ i ].iFlags = 0; // to można usunąć (nie mogą być usuwane w skanowaniu) - } - continue; // ignorowanie jakby nie było tej pozycji - } - else if (iDrivigFlags & moveStopPoint) // jeśli pomijanie W4, to nie sprawdza czasu odjazdu - { // tylko gdy nazwa zatrzymania się zgadza - if( ( OrderCurrentGet() & ( Obey_train | Bank ) ) != 0 ) { - // check whether the station specifies radio channel change - // NOTE: we don't do it in shunt mode, as shunting operations tend to use dedicated radio channel - // NOTE: there's a risk radio channel change was specified by a station which we skipped during timetable rewind - // we ignore this for the time being as it's not a high priority error - auto const radiochannel { TrainParams.radio_channel() }; - if( radiochannel > 0 ) { - if( iGuardRadio != 0 ) { - iGuardRadio = radiochannel; - } - if( AIControllFlag ) { - iRadioChannel = radiochannel; - } - } - } - IsScheduledPassengerStopVisible = true; // block potential timetable rewind if the next stop shows up later in the scan - if (false == TrainParams.IsStop()) - { // jeśli nie ma tu postoju - sSpeedTable[i].fVelNext = -1; // maksymalna prędkość w tym miejscu - // przy 160km/h jedzie 44m/s, to da dokładność rzędu 5 sekund - if (sSpeedTable[i].fDist < passengerstopmaxdistance * 0.5 ) { - // zaliczamy posterunek w pewnej odległości przed (choć W4 nie zasłania już semafora) -#if LOGSTOPS - WriteLog( - pVehicle->asName + " as " + TrainParams.TrainName - + ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute) - + " passed " + asNextStop); // informacja -#endif - // przy jakim dystansie (stanie licznika) ma przesunąć na następny postój - fLastStopExpDist = mvOccupied->DistCounter + 0.250 + 0.001 * fLength; - TrainParams.UpdateMTable( simulation::Time, asNextStop ); - UpdateDelayFlag(); - TrainParams.StationIndexInc(); // przejście do następnej - asNextStop = TrainParams.NextStop(); // pobranie kolejnego miejsca zatrzymania - sSpeedTable[i].iFlags = 0; // nie liczy się już - continue; // nie analizować prędkości - } - } // koniec obsługi przelotu na W4 - else { - // zatrzymanie na W4 - if ( false == sSpeedTable[i].bMoved ) { - // potentially shift the stop point in accordance with its defined parameters - /* - // https://rainsted.com/pl/Wersja/18.2.133#Okr.C4.99gi_dla_W4_i_W32 - Pierwszy parametr ujemny - preferowane zatrzymanie czoła składu (np. przed przejściem). - Pierwszy parametr dodatni - preferowane zatrzymanie środka składu (np. przy wiacie, przejściu podziemnym). - Drugi parametr ujemny - wskazanie zatrzymania dla krótszych składów (W32). - Drugi paramer dodatni - długość peronu (W4). - */ - auto L = 0.0; - auto Par1 = sSpeedTable[i].evEvent->input_value(1); - auto Par2 = sSpeedTable[i].evEvent->input_value(2); - if ((Par2 >= 0) || (fLength < -Par2)) { //użyj tego W4 - if (Par1 < 0) { - L = -Par1; - } - else { - //środek - L = Par1 - fMinProximityDist - fLength * 0.5; - } - L = std::max(0.0, std::min(L, std::abs(Par2) - fMinProximityDist - fLength)); - sSpeedTable[i].UpdateDistance(L); - sSpeedTable[i].bMoved = true; - } - else { - sSpeedTable[i].iFlags = 0; - } - } - // for human-driven vehicles discard the stop point if they leave it far enough behind - if( ( false == AIControllFlag ) - && ( sSpeedTable[ i ].fDist < -1 * std::max( fLength + 100, 250.0 ) ) ) { - sSpeedTable[ i ].iFlags = 0; // nie liczy się już zupełnie (nie wyśle SetVelocity) - sSpeedTable[ i ].fVelNext = -1; // można jechać za W4 - if( ( sSpeedTable[ i ].fDist <= 0.0 ) && ( eSignNext == sSpeedTable[ i ].evEvent ) ) { - // sanity check, if we're held by this stop point, let us go - VelSignalLast = -1; - } - continue; - } + for( std::size_t idx = 0; idx < sSpeedTable.size(); ++idx ) { + // o ile dana pozycja tabelki jest istotna + if( ( sSpeedTable[ idx ].iFlags & spEnabled ) == 0 ) { continue; } - IsAtPassengerStop = ( - ( sSpeedTable[ i ].fDist <= passengerstopmaxdistance ) - // Ra 2F1I: odległość plus długość pociągu musi być mniejsza od długości - // peronu, chyba że pociąg jest dłuższy, to wtedy minimalna. - // jeśli długość peronu ((sSpeedTable[i].evEvent->ValueGet(2)) nie podana, - // przyjąć odległość fMinProximityDist - && ( ( iDrivigFlags & moveStopCloser ) != 0 ? - sSpeedTable[ i ].fDist + fLength <= - std::max( - std::abs( sSpeedTable[ i ].evEvent->input_value( 2 ) ), - 2.0 * fMaxProximityDist + fLength ) : // fmaxproximitydist typically equals ~50 m - sSpeedTable[ i ].fDist < d_to_next_sem ) ); + auto &point { sSpeedTable[ idx ] }; - if( !eSignNext ) { - //jeśli nie widzi następnego sygnału ustawia dotychczasową - eSignNext = sSpeedTable[ i ].evEvent; - } - if( mvOccupied->Vel > 0.3 ) { - // jeśli jedzie (nie trzeba czekać, aż się drgania wytłumią - drzwi zamykane od 1.0) to będzie zatrzymanie - sSpeedTable[ i ].fVelNext = 0; - // potentially announce pending stop - if( ( m_lastannouncement != announcement_t::approaching ) - && ( sSpeedTable[ i ].fDist < 750 ) - && ( sSpeedTable[ i ].fDist > 250 ) ) { - announce( announcement_t::approaching ); - } - } else if( true == IsAtPassengerStop ) { - // jeśli się zatrzymał przy W4, albo stał w momencie zobaczenia W4 - if( !AIControllFlag ) { - // w razie przełączenia na AI ma nie podciągać do W4, gdy użytkownik zatrzymał za daleko - iDrivigFlags &= ~moveStopCloser; - } - if (TrainParams.UpdateMTable( simulation::Time, asNextStop) ) { - // to się wykona tylko raz po zatrzymaniu na W4 - if( TrainParams.StationIndex < TrainParams.StationCount ) { - // jeśli są dalsze stacje, bez trąbienia przed odjazdem - // also ignore any horn cue that may be potentially set below 1 km/h and before the actual full stop - iDrivigFlags &= ~( moveStartHorn | moveStartHornNow ); - } - UpdateDelayFlag(); + if( TestFlag( point.iFlags, spPassengerStopPoint ) ) { + if( TableUpdateStopPoint( go, point, d_to_next_sem ) ) { continue; }; + } + v = point.fVelNext; // odczyt prędkości do zmiennej pomocniczej + if( TestFlag( point.iFlags, spSwitch ) ) { + // zwrotnice są usuwane z tabelki dopiero po zjechaniu z nich + iDrivigFlags |= moveSwitchFound; // rozjazd z przodu/pod ogranicza np. sens skanowania wstecz + SwitchClearDist = point.fDist + point.trTrack->Length() + fLength; + } + else if ( TestFlag( point.iFlags, spEvent ) ) // W4 może się deaktywować + { // jeżeli event, może być potrzeba wysłania komendy, aby ruszył + if( TableUpdateEvent( v, go, point, d_to_next_sem, idx ) ) { continue; } + } - // perform loading/unloading - // HACK: manual check if we didn't already do load exchange at this stop - // TODO: remove the check once the station system is in place - if( m_lastexchangestop != asNextStop ) { - m_lastexchangestop = asNextStop; - m_lastexchangedirection = pVehicle->DirectionGet(); - m_lastexchangeplatforms = static_cast( std::floor( std::abs( sSpeedTable[ i ].evEvent->input_value( 2 ) ) ) ) % 10; - auto const exchangetime { simulation::Station.update_load( pVehicles[ end::front ], TrainParams, m_lastexchangeplatforms ) }; - WaitingSet( exchangetime ); - // announce the stop name while at it - announce( announcement_t::current ); - m_makenextstopannouncement = true; - } - - if (TrainParams.DirectionChange()) { - // jeśli "@" w rozkładzie, to wykonanie dalszych komend - // wykonanie kolejnej komendy, nie dotyczy ostatniej stacji - if (iDrivigFlags & movePushPull) { - // SN61 ma się też nie ruszać, chyba że ma wagony - iDrivigFlags |= moveStopHere; // EZT ma stać przy peronie - if (OrderNextGet() != Change_direction) { - OrderPush(Change_direction); // zmiana kierunku - OrderPush( - TrainParams.StationIndex < TrainParams.StationCount ? - Obey_train : - Shunt); // to dalej wg rozkładu - } - } - else { - // a dla lokomotyw... - // pozwolenie na przejechanie za W4 przed czasem i nie ma stać - iDrivigFlags &= ~( moveStopPoint | moveStopHere ); - } - // przejście do kolejnego rozkazu (zmiana kierunku, odczepianie) - JumpToNextOrder(); - // ma nie podjeżdżać pod W4 po przeciwnej stronie - iDrivigFlags &= ~moveStopCloser; - // ten W4 nie liczy się już zupełnie (nie wyśle SetVelocity) - sSpeedTable[i].iFlags = 0; - // jechać - sSpeedTable[i].fVelNext = -1; - // nie analizować prędkości - continue; - } - } - - if( pVehicle->DirectionGet() != m_lastexchangedirection ) { - // generally means the ai driver moved to the opposite end of the consist - // TODO: investigate whether user playing with the reverser can mess this up - auto const left { ( m_lastexchangedirection > 0 ) ? 1 : 2 }; - auto const right { 3 - left }; - m_lastexchangeplatforms = - ( ( m_lastexchangeplatforms & left ) != 0 ? right : 0 ) - + ( ( m_lastexchangeplatforms & right ) != 0 ? left : 0 ); - m_lastexchangedirection = pVehicle->DirectionGet(); - } - if( ( false == TrainParams.IsMaintenance() ) - && ( ( false == TestFlag( iDrivigFlags, moveDoorOpened ) ) - || ( true == DoesAnyDoorNeedOpening ) ) ) { - iDrivigFlags |= moveDoorOpened; // nie wykonywać drugi raz - Doors( true, m_lastexchangeplatforms ); - } - - if (OrderCurrentGet() & ( Shunt | Loose_shunt )) { - OrderNext(Obey_train); // uruchomić jazdę pociągową - CheckVehicles(); // zmienić światła - } - - if (TrainParams.StationIndex < TrainParams.StationCount) { - // jeśli są dalsze stacje, czekamy do godziny odjazdu - if ( ( true == IsCargoTrain ) - || ( true == TrainParams.IsMaintenance() ) - || ( TrainParams.IsTimeToGo( simulation::Time.data().wHour, simulation::Time.data().wMinute + simulation::Time.data().wSecond*0.0167 ) ) ) { - // z dalszą akcją czekamy do godziny odjazdu - // cargo trains and passenger trains at maintenance stop don't need to wait - IsAtPassengerStop = false; - // przy jakim dystansie (stanie licznika) ma przesunąć na następny postój - fLastStopExpDist = mvOccupied->DistCounter + 0.050 + 0.001 * fLength; - TrainParams.StationIndexInc(); // przejście do następnej - asNextStop = TrainParams.NextStop(); // pobranie kolejnego miejsca zatrzymania -#if LOGSTOPS - WriteLog( - pVehicle->asName + " as " + TrainParams.TrainName - + ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute) - + " next " + asNextStop); // informacja -#endif - // update consist weight, brake settings and ai braking tables - // NOTE: this calculation is expected to run after completing loading/unloading - CheckVehicles(); // nastawianie hamulca do jazdy pociągowej - - if( static_cast( std::floor( std::abs( sSpeedTable[ i ].evEvent->input_value( 1 ) ) ) ) % 2 ) { - // nie podjeżdżać do semafora, jeśli droga nie jest wolna - iDrivigFlags |= moveStopHere; - } - else { - //po czasie jedź dalej - iDrivigFlags &= ~moveStopHere; - } - iDrivigFlags |= moveStopCloser; // do następnego W4 podjechać blisko (z dociąganiem) - sSpeedTable[i].iFlags = 0; // nie liczy się już zupełnie (nie wyśle SetVelocity) - sSpeedTable[i].fVelNext = -1; // można jechać za W4 - if( ( sSpeedTable[ i ].fDist <= 0.0 ) && ( eSignNext == sSpeedTable[ i ].evEvent ) ) { - // sanity check, if we're held by this stop point, let us go - VelSignalLast = -1; - } - if (go == TCommandType::cm_Unknown) // jeśli nie było komendy wcześniej - go = TCommandType::cm_Ready; // gotów do odjazdu z W4 (semafor może zatrzymać) - if( false == tsGuardSignal.empty() ) { - // jeśli mamy głos kierownika, to odegrać - iDrivigFlags |= moveGuardSignal; - } - continue; // nie analizować prędkości - } // koniec startu z zatrzymania - } // koniec obsługi początkowych stacji - else { - // jeśli dojechaliśmy do końca rozkładu -#if LOGSTOPS - WriteLog( - pVehicle->asName + " as " + TrainParams.TrainName - + ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute) - + " end of route."); // informacja -#endif - asNextStop = TrainParams.NextStop(); // informacja o końcu trasy - TrainParams.NewName("none"); // czyszczenie nieaktualnego rozkładu - // ma nie podjeżdżać pod W4 i ma je pomijać - iDrivigFlags &= ~( moveStopCloser ); - if( false == TestFlag( iDrivigFlags, movePushPull ) ) { - // if the consist can change direction through a simple cab change it doesn't need fiddling with recognition of passenger stops - iDrivigFlags &= ~( moveStopPoint ); - } - fLastStopExpDist = -1.0f; // nie ma rozkładu, nie ma usuwania stacji - sSpeedTable[i].iFlags = 0; // W4 nie liczy się już (nie wyśle SetVelocity) - sSpeedTable[i].fVelNext = -1; // można jechać za W4 - if( ( sSpeedTable[ i ].fDist <= 0.0 ) && ( eSignNext == sSpeedTable[ i ].evEvent ) ) { - // sanity check, if we're held by this stop point, let us go - VelSignalLast = -1; - } - // wykonanie kolejnego rozkazu (Change_direction albo Shunt) - JumpToNextOrder(); - // ma się nie ruszać aż do momentu podania sygnału - iDrivigFlags |= moveStopHere | moveStartHorn; - continue; // nie analizować prędkości - } // koniec obsługi ostatniej stacji - } // vel 0, at passenger stop - else { - // HACK: momentarily deactivate W4 to trick the controller into moving closer - sSpeedTable[ i ].fVelNext = -1; - } // vel 0, outside of passenger stop - } // koniec obsługi zatrzymania na W4 - } // koniec warunku pomijania W4 podczas zmiany czoła - else - { // skoro pomijanie, to jechać i ignorować W4 - sSpeedTable[i].iFlags = 0; // W4 nie liczy się już (nie zatrzymuje jazdy) - sSpeedTable[i].fVelNext = -1; - continue; // nie analizować prędkości - } - } // koniec obsługi W4 - v = sSpeedTable[ i ].fVelNext; // odczyt prędkości do zmiennej pomocniczej - if( sSpeedTable[ i ].iFlags & spSwitch ) { - // zwrotnice są usuwane z tabelki dopiero po zjechaniu z nich - iDrivigFlags |= moveSwitchFound; // rozjazd z przodu/pod ogranicza np. sens skanowania wstecz - SwitchClearDist = sSpeedTable[ i ].fDist + sSpeedTable[ i ].trTrack->Length() + fLength; - } - else if (sSpeedTable[i].iFlags & spEvent) // W4 może się deaktywować - { // jeżeli event, może być potrzeba wysłania komendy, aby ruszył - if( sSpeedTable[ i ].fDist < 0.0 ) { - // sprawdzanie eventów pasywnych miniętych -/* - if( ( eSignNext != nullptr ) && ( sSpeedTable[ i ].evEvent == eSignNext ) ) { - VelSignalLast = sSpeedTable[ i ].fVelNext; - } -*/ - if( SemNextIndex == i ) { - if( Global.iWriteLogEnabled & 8 ) { - WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextIndex ].GetName() ); - } - SemNextIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód - } - if( SemNextStopIndex == i ) { - if( Global.iWriteLogEnabled & 8 ) { - WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextStopIndex ].GetName() ); - } - SemNextStopIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód - } - switch( sSpeedTable[ i ].evEvent->input_command() ) { - case TCommandType::cm_EmergencyBrake: { - pVehicle->RadioStop(); - sSpeedTable[ i ].iFlags = 0; // signal reveived, deactivate - } - default: { - break; - } - } - } - if( sSpeedTable[ i ].fDist > 0.0 ) { - // check signals ahead - if( sSpeedTable[ i ].IsProperSemaphor( OrderCurrentGet() ) ) { - - if( ( mvOccupied->CategoryFlag & 2 ) - && ( sSpeedTable[ i ].fVelNext != -1.0 ) - && ( sSpeedTable[ i ].fVelNext < 1.0 ) - && ( sSpeedTable[ i ].fDist < -0.5 + std::min( fBrakeDist * 0.2, mvOccupied->Vel * 0.2 ) ) ) { - // special rule for cars: ignore stop signals at distance too short to come to a stop - // as trying to stop in such situation is likely to place the car on train tracks - sSpeedTable[ i ].iFlags &= ~spEnabled; - continue; - } - - if( SemNextIndex == -1 ) { - // jeśli jest mienięty poprzedni semafor a wcześniej - // byl nowy to go dorzucamy do zmiennej, żeby cały czas widział najbliższy - SemNextIndex = i; - if( Global.iWriteLogEnabled & 8 ) { - WriteLog( "Speed table update for " + OwnerName() + ", next semaphor is " + sSpeedTable[ SemNextIndex ].GetName() ); - } - } - if( ( SemNextStopIndex == -1 ) - || ( ( sSpeedTable[ SemNextStopIndex ].fVelNext != 0 ) - && ( sSpeedTable[ i ].fVelNext == 0 ) ) ) { - SemNextStopIndex = i; - } - } - } - if (sSpeedTable[i].iFlags & spOutsideStation) - { // jeśli W5, to reakcja zależna od trybu jazdy - if (OrderCurrentGet() & Obey_train) - { // w trybie pociągowym: można przyspieszyć do wskazanej prędkości (po zjechaniu z rozjazdów) - v = -1.0; // ignorować? - if (sSpeedTable[i].fDist < 0.0) // jeśli wskaźnik został minięty - { - VelSignalLast = v; //ustawienie prędkości na -1 - } - else if (!(iDrivigFlags & moveSwitchFound)) // jeśli rozjazdy już minięte - VelSignalLast = v; //!!! to też koniec ograniczenia - } - else - { // w trybie manewrowym: skanować od niego wstecz, stanąć po wyjechaniu za sygnalizator i zmienić kierunek - v = 0.0; // zmiana kierunku może być podanym sygnałem, ale wypadało by zmienić światło wcześniej - if( !( iDrivigFlags & moveSwitchFound ) ) { // jeśli nie ma rozjazdu - // check for presence of a signal facing the opposite direction - // if there's one, we'll want to pass it before changing direction - basic_event *foundevent = nullptr; - if( sSpeedTable[ i ].fDist - fMaxProximityDist > 0 ) { - auto scandistance{ sSpeedTable[ i ].fDist + fLength - fMaxProximityDist }; - auto *scanvehicle{ pVehicles[ end::rear ] }; - auto scandirection{ scanvehicle->DirectionGet() * scanvehicle->RaDirectionGet() }; - auto *foundtrack = BackwardTraceRoute( scandistance, scandirection, scanvehicle, foundevent, -1, end::front, false ); - } - if( foundevent == nullptr ) { - iDrivigFlags |= moveTrackEnd; // to dalsza jazda trwale ograniczona (W5, koniec toru) - } - } - } - } - else if (sSpeedTable[i].iFlags & spStopOnSBL) { - // jeśli S1 na SBL - if( mvOccupied->Vel < 2.0 ) { - // stanąć nie musi, ale zwolnić przynajmniej - if( ( sSpeedTable[ i ].fDist < fMaxProximityDist ) - && ( Obstacle.distance > 1000 ) ) { - // jest w maksymalnym zasięgu to można go pominąć (wziąć drugą prędkosć) - // as long as there isn't any obstacle in arbitrary view range - eSignSkip = sSpeedTable[ i ].evEvent; - // jazda na widoczność - skanować możliwość kolizji i nie podjeżdżać zbyt blisko - // usunąć flagę po podjechaniu blisko semafora zezwalającego na jazdę - // ostrożnie interpretować sygnały - semafor może zezwalać na jazdę pociągu z przodu! - iDrivigFlags |= moveVisibility; - // store the ordered restricted speed and don't exceed it until the flag is cleared - VelRestricted = sSpeedTable[ i ].evEvent->input_value( 2 ); - } - } - if( eSignSkip != sSpeedTable[ i ].evEvent ) { - // jeśli ten SBL nie jest do pominięcia to ma 0 odczytywać - v = sSpeedTable[ i ].evEvent->input_value( 1 ); - // TODO sprawdzić do której zmiennej jest przypisywane v i zmienić to tutaj - } - } - else if (sSpeedTable[i].IsProperSemaphor(OrderCurrentGet())) - { // to semaphor - if( sSpeedTable[ i ].fDist < 0 ) { - if( ( false == AIControllFlag ) - && ( sSpeedTable[ i ].fDist < -1 * std::max( fLength + 100, 250.0 ) ) ) { - // for human-driven vehicles ignore the signal if it was passed by sufficient distance - sSpeedTable[ i ].iFlags &= ~spEnabled; - VelSignal = -1.0; - continue; - } - else { - // for ai-driven vehicles always play by the rules - VelSignalLast = sSpeedTable[ i ].fVelNext; //minięty daje prędkość obowiązującą - } - } - else - { - iDrivigFlags |= moveSemaphorFound; //jeśli z przodu to dajemy falgę, że jest - d_to_next_sem = std::min(sSpeedTable[i].fDist, d_to_next_sem); - } - if( sSpeedTable[ i ].fDist <= d_to_next_sem ) - { - VelSignalNext = sSpeedTable[ i ].fVelNext; - } - } - else if (sSpeedTable[i].iFlags & spRoadVel) - { // to W6 - if (sSpeedTable[i].fDist < 0) - VelRoad = sSpeedTable[i].fVelNext; - } - else if (sSpeedTable[i].iFlags & spSectionVel) - { // to W27 - if (sSpeedTable[i].fDist < 0) // teraz trzeba sprawdzić inne warunki - { - if (sSpeedTable[i].fSectionVelocityDist == 0.0) - { - if (Global.iWriteLogEnabled & 8) - WriteLog("TableUpdate: Event is behind. SVD = 0: " + sSpeedTable[i].evEvent->m_name); - sSpeedTable[i].iFlags = 0; // jeśli punktowy to kasujemy i nie dajemy ograniczenia na stałe - } - else if (sSpeedTable[i].fSectionVelocityDist < 0.0) - { // ograniczenie obowiązujące do następnego - if (sSpeedTable[i].fVelNext == min_speed(sSpeedTable[i].fVelNext, VelLimitLast) && - sSpeedTable[i].fVelNext != VelLimitLast) - { // jeśli ograniczenie jest mniejsze niż obecne to obowiązuje od zaraz - VelLimitLast = sSpeedTable[i].fVelNext; - } - else if (sSpeedTable[i].fDist < -fLength) - { // jeśli większe to musi wyjechać za poprzednie - VelLimitLast = sSpeedTable[i].fVelNext; - if (Global.iWriteLogEnabled & 8) - WriteLog("TableUpdate: Event is behind. SVD < 0: " + sSpeedTable[i].evEvent->m_name); - sSpeedTable[i].iFlags = 0; // wyjechaliśmy poza poprzednie, można skasować - } - } - else - { // jeśli większe to ograniczenie ma swoją długość - if (sSpeedTable[i].fVelNext == min_speed(sSpeedTable[i].fVelNext, VelLimitLast) && - sSpeedTable[i].fVelNext != VelLimitLast) - { // jeśli ograniczenie jest mniejsze niż obecne to obowiązuje od zaraz - VelLimitLast = sSpeedTable[i].fVelNext; - } - else if (sSpeedTable[i].fDist < -fLength && sSpeedTable[i].fVelNext != VelLimitLast) - { // jeśli większe to musi wyjechać za poprzednie - VelLimitLast = sSpeedTable[i].fVelNext; - } - else if (sSpeedTable[i].fDist < -fLength - sSpeedTable[i].fSectionVelocityDist) - { // - VelLimitLast = -1.0; - if (Global.iWriteLogEnabled & 8) - WriteLog("TableUpdate: Event is behind. SVD > 0: " + sSpeedTable[i].evEvent->m_name); - sSpeedTable[i].iFlags = 0; // wyjechaliśmy poza poprzednie, można skasować - } - } - } - } - - //sprawdzenie eventów pasywnych przed nami - if( ( mvOccupied->CategoryFlag & 1 ) - && ( sSpeedTable[ i ].fDist > Obstacle.distance - 20 ) ) { - // jak sygnał jest dalej niż zawalidroga - v = 0.0; // to może być podany dla tamtego: jechać tak, jakby tam stop był - } - else - { // zawalidrogi nie ma (albo pojazd jest samochodem), sprawdzić sygnał - if (sSpeedTable[i].iFlags & spShuntSemaphor) // jeśli Tm - w zasadzie to sprawdzić komendę! - { // jeśli podana prędkość manewrowa - if( ( v == 0.0 ) && ( true == TestFlag( OrderCurrentGet(), Obey_train ) ) ) { - // jeśli tryb pociągowy a tarcze ma ShuntVelocity 0 0 - v = -1; // ignorować, chyba że prędkość stanie się niezerowa - if( true == TestFlag( sSpeedTable[ i ].iFlags, spElapsed ) ) { - // a jak przejechana to można usunąć, bo podstawowy automat usuwa tylko niezerowe - sSpeedTable[ i ].iFlags = 0; - } - } - else if( go == TCommandType::cm_Unknown ) { - // jeśli jeszcze nie ma komendy - // komenda jest tylko gdy ma jechać, bo stoi na podstawietabelki - if( v != 0.0 ) { - // jeśli nie było komendy wcześniej - pierwsza się liczy - ustawianie VelSignal - go = TCommandType::cm_ShuntVelocity; // w trybie pociągowym tylko jeśli włącza - // tryb manewrowy (v!=0.0) - // Ra 2014-06: (VelSignal) nie może być tu ustawiane, bo Tm może być - // daleko - // VelSignal=v; //nie do końca tak, to jest druga prędkość - if( VelSignal == 0.0 ) { - // aby stojący ruszył - VelSignal = v; - } - if( sSpeedTable[ i ].fDist < 0.0 ) { - // jeśli przejechany - //!!! ustawienie, gdy przejechany jest lepsze niż wcale, ale to jeszcze nie to - VelSignal = v; - // to można usunąć (nie mogą być usuwane w skanowaniu) - sSpeedTable[ i ].iFlags = 0; - } - } - } - } - else if( !( sSpeedTable[ i ].iFlags & spSectionVel ) ) { - //jeśli jakiś event pasywny ale nie ograniczenie - if( go == TCommandType::cm_Unknown ) { - // jeśli nie było komendy wcześniej - pierwsza się liczy - ustawianie VelSignal - if( ( v < 0.0 ) - || ( v >= 1.0 ) ) { - // bo wartość 0.1 służy do hamowania tylko - go = TCommandType::cm_SetVelocity; // może odjechać - // Ra 2014-06: (VelSignal) nie może być tu ustawiane, bo semafor może być daleko - // VelSignal=v; //nie do końca tak, to jest druga prędkość; -1 nie wpisywać... - if( VelSignal == 0.0 ) { - // aby stojący ruszył - VelSignal = -1.0; - } - if( sSpeedTable[ i ].fDist < 0.0 ) { - // jeśli przejechany - VelSignal = ( v == 0.0 ? 0.0 : -1.0 ); - // ustawienie, gdy przejechany jest lepsze niż wcale, ale to jeszcze nie to - if( sSpeedTable[ i ].iFlags & spEvent ) { - // jeśli event - if( ( sSpeedTable[ i ].evEvent != eSignSkip ) - || ( sSpeedTable[ i ].fVelNext != VelRestricted ) ) { - // ale inny niż ten, na którym minięto S1, chyba że się już zmieniło - // sygnał zezwalający na jazdę wyłącza jazdę na widoczność (po S1 na SBL) - iDrivigFlags &= ~moveVisibility; - // remove restricted speed - VelRestricted = -1.0; - } - } - // jeśli nie jest ograniczeniem prędkości to można usunąć - // (nie mogą być usuwane w skanowaniu) - sSpeedTable[ i ].iFlags = 0; - } - } - else if( sSpeedTable[ i ].evEvent->is_command() ) { - // jeśli prędkość jest zerowa, a komórka zawiera komendę - eSignNext = sSpeedTable[ i ].evEvent; // dla informacji - if( true == TestFlag( iDrivigFlags, moveStopHere ) ) { - // jeśli ma stać, dostaje komendę od razu - go = TCommandType::cm_Command; // komenda z komórki, do wykonania po zatrzymaniu - } - else if( sSpeedTable[ i ].fDist <= fMaxProximityDist ) { - // jeśli ma dociągnąć, to niech dociąga - // (moveStopCloser dotyczy dociągania do W4, nie semafora) - go = TCommandType::cm_Command; // komenda z komórki, do wykonania po zatrzymaniu - } - } - } - } - } // jeśli nie ma zawalidrogi - } // jeśli event - - auto const railwaytrackend { ( true == TestFlag( sSpeedTable[ i ].iFlags, spEnd ) ) && ( mvOccupied->CategoryFlag & 1 ) }; - if( ( v >= 0.0 ) - || ( railwaytrackend ) ) { - // pozycje z prędkością -1 można spokojnie pomijać - d = sSpeedTable[i].fDist; - if( v >= 0.0 ) { - if( ( d > 0.0 ) - && ( false == TestFlag( sSpeedTable[ i ].iFlags, spElapsed ) ) ) { - // sygnał lub ograniczenie z przodu (+32=przejechane) - // 2014-02: jeśli stoi, a ma do przejechania kawałek, to niech jedzie - if( ( mvOccupied->Vel < 0.01 ) - && ( true == TestFlag( sSpeedTable[ i ].iFlags, ( spEnabled | spEvent | spPassengerStopPoint ) ) ) - && ( false == IsAtPassengerStop ) ) { - // ma podjechać bliżej - czy na pewno w tym miejscu taki warunek? - a = ( ( d > passengerstopmaxdistance ) || ( ( iDrivigFlags & moveStopCloser ) != 0 ) ? - fAcc : - 0.0 ); - } - else { - // przyspieszenie: ujemne, gdy trzeba hamować - if( v >= 0.0 ) { - a = ( v * v - mvOccupied->Vel * mvOccupied->Vel ) / ( 25.92 * d ); - if( ( mvOccupied->Vel < v ) - || ( v == 0.0 ) ) { - // if we're going slower than the target velocity and there's enough room for safe stop, speed up - auto const brakingdistance { 1.2 * fBrakeDist * braking_distance_multiplier( v ) }; - if( brakingdistance > 0.0 ) { - // maintain desired acc while we have enough room to brake safely, when close enough start paying attention - // try to make a smooth transition instead of sharp change - a = interpolate( a, AccPreferred, clamp( ( d - brakingdistance ) / brakingdistance, 0.0, 1.0 ) ); - } - } - if( ( d < fMinProximityDist ) - && ( v < fVelDes ) ) { - // jak jest już blisko, ograniczenie aktualnej prędkości - fVelDes = v; - } - } - } - } - else if (sSpeedTable[i].iFlags & spTrack) // jeśli tor - { // tor ogranicza prędkość, dopóki cały skład nie przejedzie, - if( v >= 1.0 ) // EU06 się zawieszało po dojechaniu na koniec toru postojowego - if( d + sSpeedTable[ i ].trTrack->Length() < -fLength ) - if( false == railwaytrackend ) - continue; // zapętlenie, jeśli już wyjechał za ten odcinek - if( v < fVelDes ) { - // ograniczenie aktualnej prędkości aż do wyjechania za ograniczenie - fVelDes = v; - } - if( v < VelLimitLastDist.first ) { - VelLimitLastDist.second = d + sSpeedTable[ i ].trTrack->Length() + fLength; - } - if( false == railwaytrackend ) - continue; // i tyle wystarczy + auto const railwaytrackend { ( true == TestFlag( point.iFlags, spEnd ) ) && ( is_train() ) }; + if( ( v >= 0.0 ) + || ( railwaytrackend ) ) { + // pozycje z prędkością -1 można spokojnie pomijać + d = point.fDist; + if( v >= 0.0 ) { + if( ( d > 0.0 ) + && ( false == TestFlag( point.iFlags, spElapsed ) ) ) { + // sygnał lub ograniczenie z przodu (+32=przejechane) + // 2014-02: jeśli stoi, a ma do przejechania kawałek, to niech jedzie + if( ( mvOccupied->Vel < 0.01 ) + && ( true == TestFlag( point.iFlags, ( spEnabled | spEvent | spPassengerStopPoint ) ) ) + && ( false == IsAtPassengerStop ) ) { + // ma podjechać bliżej - czy na pewno w tym miejscu taki warunek? + a = ( ( d > passengerstopmaxdistance ) || ( ( iDrivigFlags & moveStopCloser ) != 0 ) ? + fAcc : + 0.0 ); } else { - // event trzyma tylko jeśli VelNext=0, nawet po przejechaniu (nie powinno dotyczyć samochodów?) - a = (v > 0.0 ? - fAcc : - mvOccupied->Vel < 0.01 ? - 0.0 : // already standing still so no need to bother with brakes - -2.0 ); // ruszanie albo hamowanie - } - } - // track can potentially end, which creates another virtual point of interest with speed limit of 0 at the end of it - // TBD, TODO: when tracing the route create a dedicated table entry for it, to simplify the code? - if( ( true == TestFlag( sSpeedTable[ i ].iFlags, spEnd ) ) - && ( mvOccupied->CategoryFlag & 1 ) ) { - // if the railway track ends here set the velnext accordingly as well - // TODO: test this with turntables and such - auto const stopatendacceleration = ( -1.0 * mvOccupied->Vel * mvOccupied->Vel ) / ( 25.92 * ( d + sSpeedTable[ i ].trTrack->Length() ) ); - if( stopatendacceleration < a ) { - a = stopatendacceleration; - v = 0.0; - d += sSpeedTable[ i ].trTrack->Length(); - if( d < fMinProximityDist ) { - // jak jest już blisko, ograniczenie aktualnej prędkości - fVelDes = v; + // przyspieszenie: ujemne, gdy trzeba hamować + if( v >= 0.0 ) { + a = ( v * v - mvOccupied->Vel * mvOccupied->Vel ) / ( 25.92 * d ); + if( ( mvOccupied->Vel < v ) + || ( v == 0.0 ) ) { + // if we're going slower than the target velocity and there's enough room for safe stop, speed up + auto const brakingdistance { 1.2 * fBrakeDist * braking_distance_multiplier( v ) }; + if( brakingdistance > 0.0 ) { + // maintain desired acc while we have enough room to brake safely, when close enough start paying attention + // try to make a smooth transition instead of sharp change + a = interpolate( a, AccPreferred, clamp( ( d - brakingdistance ) / brakingdistance, 0.0, 1.0 ) ); + } + } + if( ( d < fMinProximityDist ) + && ( v < fVelDes ) ) { + // jak jest już blisko, ograniczenie aktualnej prędkości + fVelDes = v; + } } } } - - if ((a < fAcc) && (v == min_speed(v, fNext))) { - // mniejsze przyspieszenie to mniejsza możliwość rozpędzenia się albo konieczność hamowania - // jeśli droga wolna, to może być a>1.0 i się tu nie załapuje - fAcc = a; // zalecane przyspieszenie (nie musi być uwzględniane przez AI) - fNext = v; // istotna jest prędkość na końcu tego odcinka - fDist = d; // dlugość odcinka - } - else if ((fAcc > 0) && (v >= 0) && (v <= fNext)) { - // jeśli nie ma wskazań do hamowania, można podać drogę i prędkość na jej końcu - fNext = v; // istotna jest prędkość na końcu tego odcinka - fDist = d; // dlugość odcinka (kolejne pozycje mogą wydłużać drogę, jeśli prędkość jest stała) - } - if( ( v < VelLimitLastDist.first ) /* && ( d < VelLimitLastDist.second ) */ ) { - // if we encounter another speed limit before we can clear current/last registered one, - // update our calculation where we'll be able to resume regular speed - VelLimitLastDist.second = d + fLength; - if( ( sSpeedTable[ i ].iFlags & spTrack ) != 0 ) { - VelLimitLastDist.second += sSpeedTable[ i ].trTrack->Length(); + else if ( point.iFlags & spTrack) // jeśli tor + { // tor ogranicza prędkość, dopóki cały skład nie przejedzie, + if( v >= 1.0 ) { // EU06 się zawieszało po dojechaniu na koniec toru postojowego + if( d + point.trTrack->Length() < -fLength ) { + if( false == railwaytrackend ) { + // zapętlenie, jeśli już wyjechał za ten odcinek + continue; + } + } + } + if( v < fVelDes ) { + // ograniczenie aktualnej prędkości aż do wyjechania za ograniczenie + fVelDes = v; + } + if( v < VelLimitLastDist.first ) { + VelLimitLastDist.second = d + point.trTrack->Length() + fLength; + } + if( false == railwaytrackend ) { + continue; // i tyle wystarczy } } - } // if (v>=0.0) - if (fNext >= 0.0) - { // jeśli ograniczenie - if ((sSpeedTable[i].iFlags & (spEnabled | spEvent)) == (spEnabled | spEvent)) // tylko sygnał przypisujemy - if (!eSignNext) // jeśli jeszcze nic nie zapisane tam - eSignNext = sSpeedTable[i].evEvent; // dla informacji - if (fNext == 0.0) - break; // nie ma sensu analizować tabelki dalej + else { + // event trzyma tylko jeśli VelNext=0, nawet po przejechaniu (nie powinno dotyczyć samochodów?) + a = (v > 0.0 ? + fAcc : + mvOccupied->Vel < 0.01 ? + 0.0 : // already standing still so no need to bother with brakes + -2.0 ); // ruszanie albo hamowanie + } } - } // if (sSpeedTable[i].iFlags&1) - } // for + // track can potentially end, which creates another virtual point of interest with speed limit of 0 at the end of it + // TBD, TODO: when tracing the route create a dedicated table entry for it, to simplify the code? + if( ( true == TestFlag( point.iFlags, spEnd ) ) + && ( is_train() ) ) { + // if the railway track ends here set the velnext accordingly as well + // TODO: test this with turntables and such + auto const stopatendacceleration = ( -1.0 * mvOccupied->Vel * mvOccupied->Vel ) / ( 25.92 * ( d + point.trTrack->Length() ) ); + if( stopatendacceleration < a ) { + a = stopatendacceleration; + v = 0.0; + d += point.trTrack->Length(); + if( d < fMinProximityDist ) { + // jak jest już blisko, ograniczenie aktualnej prędkości + fVelDes = v; + } + } + } + + if ((a < fAcc) && (v == min_speed(v, fNext))) { + // mniejsze przyspieszenie to mniejsza możliwość rozpędzenia się albo konieczność hamowania + // jeśli droga wolna, to może być a>1.0 i się tu nie załapuje + fAcc = a; // zalecane przyspieszenie (nie musi być uwzględniane przez AI) + fNext = v; // istotna jest prędkość na końcu tego odcinka + fDist = d; // dlugość odcinka + } + else if ((fAcc > 0) && (v >= 0) && (v <= fNext)) { + // jeśli nie ma wskazań do hamowania, można podać drogę i prędkość na jej końcu + fNext = v; // istotna jest prędkość na końcu tego odcinka + fDist = d; // dlugość odcinka (kolejne pozycje mogą wydłużać drogę, jeśli prędkość jest stała) + } + if( ( v < VelLimitLastDist.first ) /* && ( d < VelLimitLastDist.second ) */ ) { + // if we encounter another speed limit before we can clear current/last registered one, + // update our calculation where we'll be able to resume regular speed + VelLimitLastDist.second = d + fLength; + if( ( point.iFlags & spTrack ) != 0 ) { + VelLimitLastDist.second += point.trTrack->Length(); + } + } + } // if (v>=0.0) + if (fNext >= 0.0) + { // jeśli ograniczenie + if( ( point.iFlags & ( spEnabled | spEvent ) ) == ( spEnabled | spEvent ) ) { // tylko sygnał przypisujemy + if( eSignNext == nullptr ) { // jeśli jeszcze nic nie zapisane tam + eSignNext = point.evEvent; // dla informacji + } + } + if( fNext == 0.0 ) { + break; // nie ma sensu analizować tabelki dalej + } + } + } // jeśli mieliśmy ograniczenie z semafora i nie ma przed nami if( ( VelSignalLast >= 0.0 ) && ( ( iDrivigFlags & ( moveSemaphorFound | moveSwitchFound | moveStopPointFound ) ) == 0 ) - && ( true == TestFlag( OrderCurrentGet(), Obey_train ) ) ) { + && ( true == TestFlag( OrderCurrentGet(), Obey_train ) ) ) { VelSignalLast = -1.0; } + // take into account the effect switches have on duration of signal-imposed speed limit, in calculation of speed limit end point if( ( VelSignalLast >= 0.0 ) && ( SwitchClearDist >= 0.0 ) ) { - // take into account the effect switches have on duration of signal-imposed speed limit, in calculation of speed limit end point VelLimitLastDist.second = std::max( VelLimitLastDist.second, SwitchClearDist ); } //analiza spisanych z tabelki ograniczeń i nadpisanie aktualnego + // if stopped at a valid passenger stop, hold there if( ( true == IsAtPassengerStop ) && ( mvOccupied->Vel < 0.01 ) ) { - // if stopped at a valid passenger stop, hold there fVelDes = 0.0; } else { @@ -1619,6 +1053,604 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN return go; }; +bool +TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, double const Signaldistance ) { + // stop points are irrelevant when not in one of the basic modes + if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) { return true; } + // jeśli przystanek, trzeba obsłużyć wg rozkładu + iDrivigFlags |= moveStopPointFound; + // first 19 chars of the command is expected to be "PassengerStopPoint:" so we skip them + if( ToLower( Point.evEvent->input_text() ).compare( 19, sizeof( asNextStop ), ToLower( asNextStop ) ) != 0 ) + { // jeśli nazwa nie jest zgodna + if( ( false == IsScheduledPassengerStopVisible ) // check if our next scheduled stop didn't show up earlier in the scan + && ( Point.fDist < ( 1.15 * fBrakeDist + 300 ) ) + && ( Point.fDist > 0 ) ) // tylko jeśli W4 jest blisko, przy dwóch może zaczać szaleć + { + // porównuje do następnej stacji, więc trzeba przewinąć do poprzedniej + // nastepnie ustawić następną na aktualną tak żeby prawidłowo ją obsłużył w następnym kroku + if( true == TrainParams.RewindTimeTable( Point.evEvent->input_text() ) ) { + asNextStop = TrainParams.NextStop(); + iStationStart = TrainParams.StationIndex; + } + } + else if( Point.fDist < -fLength ) { + // jeśli został przejechany + Point.iFlags = 0; // to można usunąć (nie mogą być usuwane w skanowaniu) + } + return true; // ignorowanie jakby nie było tej pozycji + } + else if (iDrivigFlags & moveStopPoint) // jeśli pomijanie W4, to nie sprawdza czasu odjazdu + { // tylko gdy nazwa zatrzymania się zgadza + if( ( OrderCurrentGet() & ( Obey_train | Bank ) ) != 0 ) { + // check whether the station specifies radio channel change + // NOTE: we don't do it in shunt mode, as shunting operations tend to use dedicated radio channel + // NOTE: there's a risk radio channel change was specified by a station which we skipped during timetable rewind + // we ignore this for the time being as it's not a high priority error + auto const radiochannel { TrainParams.radio_channel() }; + if( radiochannel > 0 ) { + if( iGuardRadio != 0 ) { + iGuardRadio = radiochannel; + } + if( iRadioChannel != radiochannel ) { + cue_action( locale::string::driver_hint_radiochannel, radiochannel ); + } + } + } + IsScheduledPassengerStopVisible = true; // block potential timetable rewind if the next stop shows up later in the scan + if (false == TrainParams.IsStop()) + { // jeśli nie ma tu postoju + Point.fVelNext = -1; // maksymalna prędkość w tym miejscu + // przy 160km/h jedzie 44m/s, to da dokładność rzędu 5 sekund + if (Point.fDist < passengerstopmaxdistance * 0.5 ) { + // zaliczamy posterunek w pewnej odległości przed (choć W4 nie zasłania już semafora) +#if LOGSTOPS + WriteLog( + pVehicle->asName + " as " + TrainParams.TrainName + + ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute) + + " passed " + asNextStop); // informacja +#endif + // przy jakim dystansie (stanie licznika) ma przesunąć na następny postój + fLastStopExpDist = mvOccupied->DistCounter + 0.250 + 0.001 * fLength; + TrainParams.UpdateMTable( simulation::Time, asNextStop ); + UpdateDelayFlag(); + TrainParams.StationIndexInc(); // przejście do następnej + asNextStop = TrainParams.NextStop(); // pobranie kolejnego miejsca zatrzymania + Point.iFlags = 0; // nie liczy się już + return true; // table entry recognized and handled + } + } // koniec obsługi przelotu na W4 + else { + // zatrzymanie na W4 + if ( false == Point.bMoved ) { + // potentially shift the stop point in accordance with its defined parameters + /* + // https://rainsted.com/pl/Wersja/18.2.133#Okr.C4.99gi_dla_W4_i_W32 + Pierwszy parametr ujemny - preferowane zatrzymanie czoła składu (np. przed przejściem). + Pierwszy parametr dodatni - preferowane zatrzymanie środka składu (np. przy wiacie, przejściu podziemnym). + Drugi parametr ujemny - wskazanie zatrzymania dla krótszych składów (W32). + Drugi paramer dodatni - długość peronu (W4). + */ + auto L = 0.0; + auto Par1 = Point.evEvent->input_value(1); + auto Par2 = Point.evEvent->input_value(2); + if ((Par2 >= 0) || (fLength < -Par2)) { //użyj tego W4 + if (Par1 < 0) { + L = -Par1; + } + else { + //środek + L = Par1 - fMinProximityDist - fLength * 0.5; + } + L = std::max(0.0, std::min(L, std::abs(Par2) - fMinProximityDist - fLength)); + Point.UpdateDistance(L); + Point.bMoved = true; + } + else { + Point.iFlags = 0; + } + } + // for human-driven vehicles discard the stop point if they leave it far enough behind + if( ( false == AIControllFlag ) + && ( Point.fDist < -1 * std::max( fLength + 100, 250.0 ) ) ) { + Point.iFlags = 0; // nie liczy się już zupełnie (nie wyśle SetVelocity) + Point.fVelNext = -1; // można jechać za W4 + if( ( Point.fDist <= 0.0 ) && ( eSignNext == Point.evEvent ) ) { + // sanity check, if we're held by this stop point, let us go + VelSignalLast = -1; + } + return true; + } + + IsAtPassengerStop = ( + ( Point.fDist <= passengerstopmaxdistance ) + // Ra 2F1I: odległość plus długość pociągu musi być mniejsza od długości + // peronu, chyba że pociąg jest dłuższy, to wtedy minimalna. + // jeśli długość peronu ((sSpeedTable[i].evEvent->ValueGet(2)) nie podana, + // przyjąć odległość fMinProximityDist + && ( ( iDrivigFlags & moveStopCloser ) != 0 ? + Point.fDist + fLength <= + std::max( + std::abs( Point.evEvent->input_value( 2 ) ), + 2.0 * fMaxProximityDist + fLength ) : // fmaxproximitydist typically equals ~50 m + Point.fDist < Signaldistance ) ); + + if( !eSignNext ) { + //jeśli nie widzi następnego sygnału ustawia dotychczasową + eSignNext = Point.evEvent; + } + if( mvOccupied->Vel > 0.3 ) { + // jeśli jedzie (nie trzeba czekać, aż się drgania wytłumią - drzwi zamykane od 1.0) to będzie zatrzymanie + Point.fVelNext = 0; + // potentially announce pending stop + if( ( m_lastannouncement != announcement_t::approaching ) + && ( Point.fDist < 750 ) + && ( Point.fDist > 250 ) ) { + announce( announcement_t::approaching ); + } + } else if( true == IsAtPassengerStop ) { + // jeśli się zatrzymał przy W4, albo stał w momencie zobaczenia W4 + if( !AIControllFlag ) { + // w razie przełączenia na AI ma nie podciągać do W4, gdy użytkownik zatrzymał za daleko + iDrivigFlags &= ~moveStopCloser; + } + if (TrainParams.UpdateMTable( simulation::Time, asNextStop) ) { + // to się wykona tylko raz po zatrzymaniu na W4 + if( TrainParams.StationIndex < TrainParams.StationCount ) { + // jeśli są dalsze stacje, bez trąbienia przed odjazdem + // also ignore any horn cue that may be potentially set below 1 km/h and before the actual full stop + iDrivigFlags &= ~( moveStartHorn | moveStartHornNow ); + } + UpdateDelayFlag(); + + // perform loading/unloading + // HACK: manual check if we didn't already do load exchange at this stop + // TODO: remove the check once the station system is in place + if( m_lastexchangestop != asNextStop ) { + m_lastexchangestop = asNextStop; + m_lastexchangedirection = pVehicle->DirectionGet(); + m_lastexchangeplatforms = static_cast( std::floor( std::abs( Point.evEvent->input_value( 2 ) ) ) ) % 10; + auto const exchangetime { simulation::Station.update_load( pVehicles[ end::front ], TrainParams, m_lastexchangeplatforms ) }; + WaitingSet( exchangetime ); + // announce the stop name while at it + announce( announcement_t::current ); + m_makenextstopannouncement = true; + } + + if (TrainParams.DirectionChange()) { + // jeśli "@" w rozkładzie, to wykonanie dalszych komend + // wykonanie kolejnej komendy, nie dotyczy ostatniej stacji + if (iDrivigFlags & movePushPull) { + // SN61 ma się też nie ruszać, chyba że ma wagony + iDrivigFlags |= moveStopHere; // EZT ma stać przy peronie + if (OrderNextGet() != Change_direction) { + OrderPush(Change_direction); // zmiana kierunku + OrderPush( + TrainParams.StationIndex < TrainParams.StationCount ? + Obey_train : + Shunt); // to dalej wg rozkładu + } + } + else { + // a dla lokomotyw... + // pozwolenie na przejechanie za W4 przed czasem i nie ma stać + iDrivigFlags &= ~( moveStopPoint | moveStopHere ); + } + // przejście do kolejnego rozkazu (zmiana kierunku, odczepianie) + JumpToNextOrder(); + // ma nie podjeżdżać pod W4 po przeciwnej stronie + iDrivigFlags &= ~moveStopCloser; + // ten W4 nie liczy się już zupełnie (nie wyśle SetVelocity) + Point.iFlags = 0; + // jechać + Point.fVelNext = -1; + // nie analizować prędkości + return true; + } + } + + if( pVehicle->DirectionGet() != m_lastexchangedirection ) { + // generally means the ai driver moved to the opposite end of the consist + // TODO: investigate whether user playing with the reverser can mess this up + auto const left { ( m_lastexchangedirection > 0 ) ? 1 : 2 }; + auto const right { 3 - left }; + m_lastexchangeplatforms = + ( ( m_lastexchangeplatforms & left ) != 0 ? right : 0 ) + + ( ( m_lastexchangeplatforms & right ) != 0 ? left : 0 ); + m_lastexchangedirection = pVehicle->DirectionGet(); + } + if( ( false == TrainParams.IsMaintenance() ) + && ( ( false == TestFlag( iDrivigFlags, moveDoorOpened ) ) + || ( true == DoesAnyDoorNeedOpening ) ) ) { + iDrivigFlags |= moveDoorOpened; // nie wykonywać drugi raz + remove_hint( locale::string::driver_hint_doorleftopen ); + remove_hint( locale::string::driver_hint_doorrightopen ); + Doors( true, m_lastexchangeplatforms ); + } + + if (OrderCurrentGet() & ( Shunt | Loose_shunt )) { + OrderNext(Obey_train); // uruchomić jazdę pociągową + CheckVehicles(); // zmienić światła + } + + if (TrainParams.StationIndex < TrainParams.StationCount) { + // jeśli są dalsze stacje, czekamy do godziny odjazdu + if ( ( true == IsCargoTrain ) + || ( true == TrainParams.IsMaintenance() ) + || ( TrainParams.IsTimeToGo( simulation::Time.data().wHour, simulation::Time.data().wMinute + simulation::Time.data().wSecond*0.0167 ) ) ) { + // z dalszą akcją czekamy do godziny odjazdu + // cargo trains and passenger trains at maintenance stop don't need to wait + IsAtPassengerStop = false; + // przy jakim dystansie (stanie licznika) ma przesunąć na następny postój + fLastStopExpDist = mvOccupied->DistCounter + 0.050 + 0.001 * fLength; + TrainParams.StationIndexInc(); // przejście do następnej + asNextStop = TrainParams.NextStop(); // pobranie kolejnego miejsca zatrzymania +#if LOGSTOPS + WriteLog( + pVehicle->asName + " as " + TrainParams.TrainName + + ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute) + + " next " + asNextStop); // informacja +#endif + // update consist weight, brake settings and ai braking tables + // NOTE: this calculation is expected to run after completing loading/unloading + CheckVehicles(); // nastawianie hamulca do jazdy pociągowej + + if( static_cast( std::floor( std::abs( Point.evEvent->input_value( 1 ) ) ) ) % 2 ) { + // nie podjeżdżać do semafora, jeśli droga nie jest wolna + iDrivigFlags |= moveStopHere; + } + else { + //po czasie jedź dalej + iDrivigFlags &= ~moveStopHere; + } + iDrivigFlags |= moveStopCloser; // do następnego W4 podjechać blisko (z dociąganiem) + Point.iFlags = 0; // nie liczy się już zupełnie (nie wyśle SetVelocity) + Point.fVelNext = -1; // można jechać za W4 + if( ( Point.fDist <= 0.0 ) && ( eSignNext == Point.evEvent ) ) { + // sanity check, if we're held by this stop point, let us go + VelSignalLast = -1; + } + if( Command == TCommandType::cm_Unknown ) { // jeśli nie było komendy wcześniej + Command = TCommandType::cm_Ready; // gotów do odjazdu z W4 (semafor może zatrzymać) + } + if( false == tsGuardSignal.empty() ) { + // jeśli mamy głos kierownika, to odegrać + iDrivigFlags |= moveGuardSignal; + } + return true; // nie analizować prędkości + } // koniec startu z zatrzymania + else { + cue_action( locale::string::driver_hint_waitdeparturetime ); + } + } // koniec obsługi początkowych stacji + else { + // jeśli dojechaliśmy do końca rozkładu +#if LOGSTOPS + WriteLog( + pVehicle->asName + " as " + TrainParams.TrainName + + ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute) + + " end of route."); // informacja +#endif + asNextStop = TrainParams.NextStop(); // informacja o końcu trasy + TrainParams.NewName("none"); // czyszczenie nieaktualnego rozkładu + // ma nie podjeżdżać pod W4 i ma je pomijać + iDrivigFlags &= ~( moveStopCloser ); + if( false == TestFlag( iDrivigFlags, movePushPull ) ) { + // if the consist can change direction through a simple cab change it doesn't need fiddling with recognition of passenger stops + iDrivigFlags &= ~( moveStopPoint ); + } + fLastStopExpDist = -1.0f; // nie ma rozkładu, nie ma usuwania stacji + Point.iFlags = 0; // W4 nie liczy się już (nie wyśle SetVelocity) + Point.fVelNext = -1; // można jechać za W4 + if( ( Point.fDist <= 0.0 ) && ( eSignNext == Point.evEvent ) ) { + // sanity check, if we're held by this stop point, let us go + VelSignalLast = -1; + } + // wykonanie kolejnego rozkazu (Change_direction albo Shunt) + JumpToNextOrder(); + // ma się nie ruszać aż do momentu podania sygnału + iDrivigFlags |= moveStopHere | moveStartHorn; + return true; // nie analizować prędkości + } // koniec obsługi ostatniej stacji + } // vel 0, at passenger stop + else { + // HACK: momentarily deactivate W4 to trick the controller into moving closer + Point.fVelNext = -1; + } // vel 0, outside of passenger stop + } // koniec obsługi zatrzymania na W4 + } // koniec warunku pomijania W4 podczas zmiany czoła + else + { // skoro pomijanie, to jechać i ignorować W4 + Point.iFlags = 0; // W4 nie liczy się już (nie zatrzymuje jazdy) + Point.fVelNext = -1; + return true; // nie analizować prędkości + } + + return false; // +} + +bool +TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPos &Point, double &Signaldistance, int const Pointindex ) { + + // sprawdzanie eventów pasywnych miniętych + if( Point.fDist < 0.0 ) { + if( SemNextIndex == Pointindex ) { + if( Global.iWriteLogEnabled & 8 ) { + WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextIndex ].GetName() ); + } + SemNextIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód + } + if( SemNextStopIndex == Pointindex ) { + if( Global.iWriteLogEnabled & 8 ) { + WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextStopIndex ].GetName() ); + } + SemNextStopIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód + } + switch( Point.evEvent->input_command() ) { + case TCommandType::cm_EmergencyBrake: { + pVehicle->RadioStop(); + Point.Clear(); // signal received, deactivate + } + default: { + break; + } + } + } + // check signals ahead + if( Point.fDist > 0.0 ) { + + if( Point.IsProperSemaphor( OrderCurrentGet() ) ) { + // special rule for cars: ignore stop signals at distance too short to come to a stop + // as trying to stop in such situation is likely to place the car on train tracks + if( ( is_car() ) + && ( Point.fVelNext != -1.0 ) + && ( Point.fVelNext < 1.0 ) + && ( Point.fDist < -0.5 + std::min( fBrakeDist * 0.2, mvOccupied->Vel * 0.2 ) ) ) { + Point.Clear(); + return true; + } + // jeśli jest mienięty poprzedni semafor a wcześniej + // byl nowy to go dorzucamy do zmiennej, żeby cały czas widział najbliższy + if( SemNextIndex == -1 ) { + SemNextIndex = Pointindex; + if( Global.iWriteLogEnabled & 8 ) { + WriteLog( "Speed table update for " + OwnerName() + ", next semaphor is " + sSpeedTable[ SemNextIndex ].GetName() ); + } + } + if( ( SemNextStopIndex == -1 ) + || ( ( sSpeedTable[ SemNextStopIndex ].fVelNext != 0 ) + && ( Point.fVelNext == 0 ) ) ) { + SemNextStopIndex = Pointindex; + } + } + } + if (Point.iFlags & spOutsideStation) + { // jeśli W5, to reakcja zależna od trybu jazdy + if (OrderCurrentGet() & Obey_train) + { // w trybie pociągowym: można przyspieszyć do wskazanej prędkości (po zjechaniu z rozjazdów) + Velocity = -1.0; // ignorować? + if ( Point.fDist < 0.0) { // jeśli wskaźnik został minięty + VelSignalLast = Velocity; //ustawienie prędkości na -1 + } + else if( ( iDrivigFlags & moveSwitchFound ) == 0 ) { // jeśli rozjazdy już minięte + VelSignalLast = Velocity; //!!! to też koniec ograniczenia + } + } + else + { // w trybie manewrowym: skanować od niego wstecz, stanąć po wyjechaniu za sygnalizator i zmienić kierunek + Velocity = 0.0; // zmiana kierunku może być podanym sygnałem, ale wypadało by zmienić światło wcześniej + if( ( iDrivigFlags & moveSwitchFound ) == 0 ) { // jeśli nie ma rozjazdu + // check for presence of a signal facing the opposite direction + // if there's one, we'll want to pass it before changing direction + basic_event *foundevent = nullptr; + if( Point.fDist - fMaxProximityDist > 0 ) { + auto scandistance{ Point.fDist + fLength - fMaxProximityDist }; + auto *scanvehicle{ pVehicles[ end::rear ] }; + auto scandirection{ scanvehicle->DirectionGet() * scanvehicle->RaDirectionGet() }; + auto *foundtrack = BackwardTraceRoute( scandistance, scandirection, scanvehicle, foundevent, -1, end::front, false ); + } + if( foundevent == nullptr ) { + iDrivigFlags |= moveTrackEnd; // to dalsza jazda trwale ograniczona (W5, koniec toru) + } + } + } + } + else if ( Point.iFlags & spStopOnSBL) { + // jeśli S1 na SBL + if( mvOccupied->Vel < 2.0 ) { + // stanąć nie musi, ale zwolnić przynajmniej + // jest w maksymalnym zasięgu to można go pominąć (wziąć drugą prędkosć) + // as long as there isn't any obstacle in arbitrary view range + if( ( Point.fDist < fMaxProximityDist ) + && ( Obstacle.distance > 1000 ) ) { + eSignSkip = Point.evEvent; + // jazda na widoczność - skanować możliwość kolizji i nie podjeżdżać zbyt blisko + // usunąć flagę po podjechaniu blisko semafora zezwalającego na jazdę + // ostrożnie interpretować sygnały - semafor może zezwalać na jazdę pociągu z przodu! + iDrivigFlags |= moveVisibility; + // store the ordered restricted speed and don't exceed it until the flag is cleared + VelRestricted = Point.evEvent->input_value( 2 ); + } + } + if( eSignSkip != Point.evEvent ) { + // jeśli ten SBL nie jest do pominięcia to ma 0 odczytywać + Velocity = Point.evEvent->input_value( 1 ); + // TODO sprawdzić do której zmiennej jest przypisywane v i zmienić to tutaj + } + } + else if ( Point.IsProperSemaphor(OrderCurrentGet())) + { // to semaphor + if( Point.fDist < 0 ) { + // for human-driven vehicles ignore the signal if it was passed by sufficient distance + if( ( false == AIControllFlag ) + && ( Point.fDist < -1 * std::max( fLength + 100, 250.0 ) ) ) { + VelSignal = -1.0; + Point.Clear(); + return true; + } + // for ai-driven vehicles always play by the rules + else { + VelSignalLast = Point.fVelNext; //minięty daje prędkość obowiązującą + } + } + else { + //jeśli z przodu to dajemy flagę, że jest + iDrivigFlags |= moveSemaphorFound; + Signaldistance = std::min( Point.fDist, Signaldistance ); + // if there's another vehicle closer to the signal, then it's likely its intended recipient + // HACK: if so, make it a stop point, to prevent non-signals farther down affect us + auto const isforsomeoneelse { ( is_train() ) && ( Obstacle.distance < Point.fDist ) }; + if( Point.fDist <= Signaldistance ) { + VelSignalNext = ( isforsomeoneelse ? 0.0 : Point.fVelNext ); + } + if( isforsomeoneelse ) { + Velocity = 0.0; +// VelNext = 0.0; +// return true; + } + } + } + else if ( Point.iFlags & spRoadVel) + { // to W6 + if ( Point.fDist < 0) + VelRoad = Point.fVelNext; + } + else if ( Point.iFlags & spSectionVel) + { // to W27 + if ( Point.fDist < 0) // teraz trzeba sprawdzić inne warunki + { + if ( Point.fSectionVelocityDist == 0.0) { + if( Global.iWriteLogEnabled & 8 ) { + WriteLog( "TableUpdate: Event is behind. SVD = 0: " + Point.evEvent->m_name ); + } + Point.Clear(); // jeśli punktowy to kasujemy i nie dajemy ograniczenia na stałe + } + else if ( Point.fSectionVelocityDist < 0.0) { + // ograniczenie obowiązujące do następnego + if ( (Point.fVelNext == min_speed( Point.fVelNext, VelLimitLast)) + && (Point.fVelNext != VelLimitLast)) { + // jeśli ograniczenie jest mniejsze niż obecne to obowiązuje od zaraz + VelLimitLast = Point.fVelNext; + } + else if ( Point.fDist < -fLength) { + // jeśli większe to musi wyjechać za poprzednie + VelLimitLast = Point.fVelNext; + if( Global.iWriteLogEnabled & 8 ) { + WriteLog( "TableUpdate: Event is behind. SVD < 0: " + Point.evEvent->m_name ); + } + Point.Clear(); // wyjechaliśmy poza poprzednie, można skasować + } + } + else + { // jeśli większe to ograniczenie ma swoją długość + if ( (Point.fVelNext == min_speed( Point.fVelNext, VelLimitLast)) + && (Point.fVelNext != VelLimitLast)) { + // jeśli ograniczenie jest mniejsze niż obecne to obowiązuje od zaraz + VelLimitLast = Point.fVelNext; + } + else if ( (Point.fDist < -fLength) + && (Point.fVelNext != VelLimitLast)) { + // jeśli większe to musi wyjechać za poprzednie + VelLimitLast = Point.fVelNext; + } + else if (Point.fDist < -fLength - Point.fSectionVelocityDist) { + VelLimitLast = -1.0; + if( Global.iWriteLogEnabled & 8 ) { + WriteLog( "TableUpdate: Event is behind. SVD > 0: " + Point.evEvent->m_name ); + } + Point.Clear(); // wyjechaliśmy poza poprzednie, można skasować + } + } + } + } + + //sprawdzenie eventów pasywnych przed nami + { // zawalidrogi nie ma (albo pojazd jest samochodem), sprawdzić sygnał + if ( Point.iFlags & spShuntSemaphor) // jeśli Tm - w zasadzie to sprawdzić komendę! + { // jeśli podana prędkość manewrowa + if( ( Velocity == 0.0 ) + && ( true == TestFlag( OrderCurrentGet(), Obey_train ) ) ) { + // jeśli tryb pociągowy a tarcze ma ShuntVelocity 0 0 + Velocity = -1; // ignorować, chyba że prędkość stanie się niezerowa + if( true == TestFlag( Point.iFlags, spElapsed ) ) { + // a jak przejechana to można usunąć, bo podstawowy automat usuwa tylko niezerowe + Point.Clear(); + } + } + else if( Command == TCommandType::cm_Unknown ) { + // jeśli jeszcze nie ma komendy + // komenda jest tylko gdy ma jechać, bo stoi na podstawie tabelki + if( Velocity != 0.0 ) { + // jeśli nie było komendy wcześniej - pierwsza się liczy - ustawianie VelSignal + Command = TCommandType::cm_ShuntVelocity; // w trybie pociągowym tylko jeśli włącza tryb manewrowy (v!=0.0) + // Ra 2014-06: (VelSignal) nie może być tu ustawiane, bo Tm może być daleko + if( VelSignal == 0.0 ) { + // aby stojący ruszył + VelSignal = Velocity; + } + if( Point.fDist < 0.0 ) { + // jeśli przejechany + //!!! ustawienie, gdy przejechany jest lepsze niż wcale, ale to jeszcze nie to + VelSignal = Velocity; + // to można usunąć (nie mogą być usuwane w skanowaniu) + Point.Clear(); + } + } + } + } + else if( ( Point.iFlags & spSectionVel ) == 0 ) { + //jeśli jakiś event pasywny ale nie ograniczenie + if( Command == TCommandType::cm_Unknown ) { + // jeśli nie było komendy wcześniej - pierwsza się liczy - ustawianie VelSignal + if( ( Velocity < 0.0 ) + || ( Velocity >= 1.0 ) ) { + // bo wartość 0.1 służy do hamowania tylko + Command = TCommandType::cm_SetVelocity; // może odjechać + // Ra 2014-06: (VelSignal) nie może być tu ustawiane, bo semafor może być daleko + // VelSignal=v; //nie do końca tak, to jest druga prędkość; -1 nie wpisywać... + if( VelSignal == 0.0 ) { + // aby stojący ruszył + VelSignal = -1.0; + } + if( Point.fDist < 0.0 ) { + // jeśli przejechany + VelSignal = ( Velocity == 0.0 ? 0.0 : -1.0 ); + // ustawienie, gdy przejechany jest lepsze niż wcale, ale to jeszcze nie to + if( Point.iFlags & spEvent ) { + // jeśli event + if( ( Point.evEvent != eSignSkip ) + || ( Point.fVelNext != VelRestricted ) ) { + // ale inny niż ten, na którym minięto S1, chyba że się już zmieniło + // sygnał zezwalający na jazdę wyłącza jazdę na widoczność (po S1 na SBL) + iDrivigFlags &= ~moveVisibility; + // remove restricted speed + VelRestricted = -1.0; + } + } + // jeśli nie jest ograniczeniem prędkości to można usunąć + // (nie mogą być usuwane w skanowaniu) + Point.Clear(); + } + } + else if( Point.evEvent->is_command() ) { + // jeśli prędkość jest zerowa, a komórka zawiera komendę + eSignNext = Point.evEvent; // dla informacji + if( true == TestFlag( iDrivigFlags, moveStopHere ) ) { + // jeśli ma stać, dostaje komendę od razu + Command = TCommandType::cm_Command; // komenda z komórki, do wykonania po zatrzymaniu + } + else if( Point.fDist <= fMaxProximityDist ) { + // jeśli ma dociągnąć, to niech dociąga + // (moveStopCloser dotyczy dociągania do W4, nie semafora) + Command = TCommandType::cm_Command; // komenda z komórki, do wykonania po zatrzymaniu + } + } + } + } + } // jeśli nie ma zawalidrogi + + return false; +} + // modifies brake distance for low target speeds, to ease braking rate in such situations float TController::braking_distance_multiplier( float const Targetvelocity ) const { @@ -2273,11 +2305,11 @@ bool TController::CheckVehicles(TOrders user) { // sprawdzenie stanu posiadanych pojazdów w składzie i zapalenie świateł TDynamicObject *p; // roboczy wskaźnik na pojazd iVehicles = 0; // ilość pojazdów w składzie - int d = CheckDirection(); + auto d = CheckDirection(); d = d >= 0 ? 0 : 1; // kierunek szukania czoła (numer sprzęgu) - pVehicles[0] = p = pVehicle->FirstFind(d); // pojazd na czele składu + pVehicles[end::front] = p = pVehicle->FirstFind(d); // pojazd na czele składu // liczenie pojazdów w składzie i ustalenie parametrów - int dir = d = 1 - d; // a dalej będziemy zliczać od czoła do tyłu + auto dir = d = 1 - d; // a dalej będziemy zliczać od czoła do tyłu fLength = 0.0; // długość składu do badania wyjechania za ograniczenie fMass = 0.0; // całkowita masa do liczenia stycznej składowej grawitacji fVelMax = -1; // ustalenie prędkości dla składu @@ -2286,26 +2318,27 @@ bool TController::CheckVehicles(TOrders user) // Ra 2014-09: ustawić moveMultiControl, jeśli wszystkie są w ukrotnieniu (i skrajne mają kabinę?) while (p) { // sprawdzanie, czy jest głównym sterującym, żeby nie było konfliktu - if (p->Mechanik) // jeśli ma obsadę - if (p->Mechanik != this) // ale chodzi o inny pojazd, niż aktualnie sprawdzający - if( p->Mechanik->iDrivigFlags & movePrimary ) { - // a tamten ma priorytet - // TODO: take into account drivers' operating modes, one or more of them might be on banking duty - if( ( iDrivigFlags & movePrimary ) - && ( mvOccupied->DirAbsolute ) - && ( mvOccupied->BrakeCtrlPos >= -1 ) ) { - // jeśli rządzi i ma kierunek - p->Mechanik->primary( false ); // dezaktywuje tamtego - p->Mechanik->ZeroLocalBrake(); - p->MoverParameters->BrakeLevelSet( p->MoverParameters->Handle->GetPos( bh_NP ) ); // odcięcie na zaworze maszynisty - p->Mechanik->BrakeLevelSet( p->MoverParameters->BrakeCtrlPos ); //ustawienie zmiennej GBH - } - else { - main = false; // nici z rządzenia - } + if( ( p->Mechanik ) // jeśli ma obsadę + && ( p->Mechanik != this ) ) { // ale chodzi o inny pojazd, niż aktualnie sprawdzający + if( p->Mechanik->iDrivigFlags & movePrimary ) { + // a tamten ma priorytet + // TODO: take into account drivers' operating modes, one or more of them might be on banking duty + if( ( iDrivigFlags & movePrimary ) + && ( mvOccupied->DirAbsolute ) + && ( mvOccupied->BrakeCtrlPos >= -1 ) ) { + // jeśli rządzi i ma kierunek + p->Mechanik->primary( false ); // dezaktywuje tamtego + p->Mechanik->ZeroLocalBrake(); + p->MoverParameters->BrakeLevelSet( p->MoverParameters->Handle->GetPos( bh_NP ) ); // odcięcie na zaworze maszynisty + p->Mechanik->BrakeLevelSet( p->MoverParameters->BrakeCtrlPos ); //ustawienie zmiennej GBH } + else { + main = false; // nici z rządzenia + } + } + } ++iVehicles; // jest jeden pojazd więcej - pVehicles[1] = p; // zapamiętanie ostatniego + pVehicles[end::rear] = p; // zapamiętanie ostatniego fLength += p->MoverParameters->Dim.L; // dodanie długości pojazdu fMass += p->MoverParameters->TotalMass; // dodanie masy łącznie z ładunkiem fVelMax = min_speed( fVelMax, p->MoverParameters->Vmax ); // ustalenie maksymalnej prędkości dla składu @@ -2316,13 +2349,16 @@ bool TController::CheckVehicles(TOrders user) } p = p->Neighbour(dir); // pojazd podłączony od wskazanej strony } - if (main) + if( main ) { iDrivigFlags |= movePrimary; // nie znaleziono innego, można się porządzić + } + ControllingSet(); // ustalenie członu do sterowania (może być inny niż zasiedziany) - int pantmask = 1; + if (iDrivigFlags & movePrimary) { // jeśli jest aktywnie prowadzącym pojazd, może zrobić własny porządek - p = pVehicles[0]; + auto pantmask = 1; + p = pVehicles[end::front]; // establish ownership and vehicle order while (p) { @@ -2337,7 +2373,7 @@ bool TController::CheckVehicles(TOrders user) p = p->Next(); // pojazd podłączony od tyłu (licząc od czoła) } // with the order established the virtual train manager can do their work - p = pVehicles[0]; + p = pVehicles[ end::front ]; ControlledEnginesCount = ( p->MoverParameters->Power > 1.0 ? 1 : 0 ); while (p) { @@ -2359,10 +2395,12 @@ bool TController::CheckVehicles(TOrders user) } } - if (p->asDestination == "none") - p->DestinationSet(TrainParams.Relation2, TrainParams.TrainName); // relacja docelowa, jeśli nie było - if (AIControllFlag) // jeśli prowadzi komputer - p->RaLightsSet(0, 0); // gasimy światła + if( p->asDestination == "none" ) { + p->DestinationSet( TrainParams.Relation2, TrainParams.TrainName ); // relacja docelowa, jeśli nie było + } + if( AIControllFlag ) { // jeśli prowadzi komputer + p->RaLightsSet( 0, 0 ); // gasimy światła + } p = p->Next(); // pojazd podłączony od tyłu (licząc od czoła) } @@ -2383,131 +2421,53 @@ bool TController::CheckVehicles(TOrders user) if( mvOccupied->LightsPosNo > 0 ) { pVehicle->SetLights(); } - - if (AIControllFlag) - { // jeśli prowadzi komputer - if( true == TestFlag( OrderCurrentGet(), Obey_train ) ) { - // jeśli jazda pociągowa - // światła pociągowe (Pc1) i końcówki (Pc5) - auto const frontlights { ( - ( m_lighthints[ end::front ] != -1 ) ? - m_lighthints[ end::front ] : - light::headlight_left | light::headlight_right | light::headlight_upper ) }; - auto const rearlights { ( - ( m_lighthints[ end::rear ] != -1 ) ? - m_lighthints[ end::rear ] : - light::redmarker_left | light::redmarker_right | light::rearendsignals ) }; - Lights( - frontlights, - rearlights ); - } - else if (OrderCurrentGet() & (Shunt | Loose_shunt | Connect)) - { - // HACK: the 'front' and 'rear' of the consist is determined by current consist direction - // since direction shouldn't affect Tb1 light configuration, we 'counter' this behaviour by virtually swapping end vehicles - if( mvOccupied->DirActive > 0 ) { - Lights( - light::headlight_right, - ( pVehicles[ 1 ]->MoverParameters->CabActive != 0 ? - light::headlight_left : - 0 ) ); //światła manewrowe (Tb1) na pojeździe z napędem - } - else { - Lights( - ( pVehicles[ 1 ]->MoverParameters->CabActive != 0 ? - light::headlight_left : - 0 ), - light::headlight_right ); //światła manewrowe (Tb1) na pojeździe z napędem - } - } - else if( true == TestFlag( OrderCurrentGet(), Disconnect ) ) { - if( mvOccupied->DirActive > 0 ) { - // jak ma kierunek do przodu + // potentially adjust light state + control_lights(); + // custom ai action for disconnect mode: switch off lights on disconnected vehicle(s) + if( is_train() ) { + if( true == TestFlag( OrderCurrentGet(), Disconnect ) ) { + if( AIControllFlag ) { // światła manewrowe (Tb1) tylko z przodu, aby nie pozostawić odczepionego ze światłem - Lights( light::headlight_right, 0 ); - } - else { - // jak dociska - // światła manewrowe (Tb1) tylko z przodu, aby nie pozostawić odczepionego ze światłem - Lights( 0, light::headlight_right ); + if( mvOccupied->DirActive >= 0 ) { // jak ma kierunek do przodu + pVehicles[ end::rear ]->RaLightsSet( -1, 0 ); + } + else { // jak dociska + pVehicles[ end::front ]->RaLightsSet( 0, -1 ); + } } } - - if( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) { - // nastawianie hamulca do jazdy pociągowej - AutoRewident(); - // enable door locks - mvOccupied->LockDoors( true ); - // enable train heating - // HACK: to account for su-45/-46 shortcomings diesel-powered engines only activate heating in cold conditions - // TODO: take instead into account presence of converters in attached cars, once said presence is possible to specify - mvControlling->HeatingAllow = ( - IsCargoTrain ? false : - ( ( mvControlling->EngineType == TEngineType::DieselElectric ) - || ( mvControlling->EngineType == TEngineType::DieselEngine ) ) ? ( Global.AirTemperature < 10 ) : - true ); + } + // enable door locks + cue_action( locale::string::driver_hint_consistdoorlockson ); + // potentially enable train heating + { + // HACK: to account for su-45/46 shortcomings diesel-powered engines only activate heating in cold conditions + // TODO: take instead into account presence of converters in attached cars, once said presence is possible to specify + auto const isheatingneeded { ( + IsCargoTrain ? false : + has_diesel_engine() ? ( Global.AirTemperature < 10 ) : + true ) }; + if( mvControlling->HeatingAllow != isheatingneeded ) { + cue_action( + isheatingneeded ? + locale::string::driver_hint_consistheatingon : + locale::string::driver_hint_consistheatingoff ); } - + } + if( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) { + // nastawianie hamulca do jazdy pociągowej + AutoRewident(); +/* if( ( true == TestFlag( iDrivigFlags, moveConnect ) ) - && ( true == TestFlag( OrderCurrentGet(), Connect ) ) ) { + && ( true == TestFlag( OrderCurrentGet(), Connect ) ) ) { iCoupler = 0; // dalsza jazda manewrowa już bez łączenia iDrivigFlags &= ~moveConnect; // zdjęcie flagi doczepiania SetVelocity( 0, 0, stopJoin ); // wyłączyć przyspieszanie JumpToNextOrder(); // wykonanie następnej komendy } +*/ } - else { // gdy człowiek i gdy nastąpiło połącznie albo rozłączenie - // Ra 2014-02: lepiej tu niż w pętli obsługującej komendy, bo tam się zmieni informacja o składzie - switch (user) { - case Change_direction: { - while (OrderCurrentGet() & (Change_direction)) { - // zmianę kierunku też można olać, ale zmienić kierunek skanowania! - JumpToNextOrder(); - } - break; - } - case Connect: { - while (OrderCurrentGet() & (Change_direction)) { - // zmianę kierunku też można olać, ale zmienić kierunek skanowania! - JumpToNextOrder(); - } - if (OrderCurrentGet() & (Connect)) { - // jeśli miało być łączenie, zakładamy, że jest dobrze (sprawdzić?) - iCoupler = 0; // koniec z doczepianiem - iDrivigFlags &= ~moveConnect; // zdjęcie flagi doczepiania - JumpToNextOrder(); // wykonanie następnej komendy - if (OrderCurrentGet() & (Change_direction)) { - // zmianę kierunku też można olać, ale zmienić kierunek skanowania! - JumpToNextOrder(); - } - - } - break; - } - case Disconnect: { - while (OrderCurrentGet() & (Change_direction)) { - // zmianę kierunku też można olać, ale zmienić kierunek skanowania! - JumpToNextOrder(); - } - if (OrderCurrentGet() & (Disconnect)) { - // wypadało by sprawdzić, czy odczepiono wagony w odpowiednim miejscu (iVehicleCount) - JumpToNextOrder(); // wykonanie następnej komendy - if (OrderCurrentGet() & (Change_direction)) { - // zmianę kierunku też można olać, ale zmienić kierunek skanowania! - JumpToNextOrder(); - } - } - break; - } - default: { - break; - } - } // switch - if( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) { - // nastawianie hamulca do jazdy pociągowej - AutoRewident(); - } - } + // Ra 2014-09: tymczasowo prymitywne ustawienie warunku pod kątem SN61 if( ( mvOccupied->TrainType == dt_EZT ) || ( mvOccupied->TrainType == dt_DMU ) @@ -2531,8 +2491,8 @@ bool TController::CheckVehicles(TOrders user) void TController::Lights(int head, int rear) { // zapalenie świateł w skłądzie - pVehicles[0]->RaLightsSet(head, -1); // zapalenie przednich w pierwszym - pVehicles[1]->RaLightsSet(-1, rear); // zapalenie końcówek w ostatnim + pVehicles[ end::front ]->RaLightsSet(head, -1); // zapalenie przednich w pierwszym + pVehicles[ end::rear ]->RaLightsSet(-1, rear); // zapalenie końcówek w ostatnim } void TController::DirectionInitial() @@ -2560,8 +2520,7 @@ int TController::OrderDirectionChange(int newdir, TMoverParameters *Vehicle) { // zmiana kierunku jazdy, niezależnie od kabiny int testd = newdir; if (Vehicle->Vel < 0.5) - { // jeśli prawie stoi, można zmienić kierunek, musi być wykonane dwukrotnie, bo za pierwszym - // razem daje na zero + { // jeśli prawie stoi, można zmienić kierunek, musi być wykonane dwukrotnie, bo za pierwszym razem daje na zero switch (newdir * Vehicle->CabActive) { // DirectionBackward() i DirectionForward() to zmiany względem kabiny case -1: // if (!Vehicle->DirectionBackward()) testd=0; break; @@ -2595,27 +2554,21 @@ void TController::WaitingSet(double Seconds) void TController::SetVelocity(double NewVel, double NewVelNext, TStopReason r) { // ustawienie nowej prędkości WaitingTime = -WaitingExpireTime; // przypisujemy -WaitingExpireTime, a potem porównujemy z zerem - if (NewVel == 0.0) // jeśli ma stanąć - { + if (NewVel == 0.0) { // jeśli ma stanąć if (r != stopNone) // a jest powód podany eStopReason = r; // to zapamiętać nowy powód } - else - { + else { eStopReason = stopNone; // podana prędkość, to nie ma powodów do stania // to całe poniżej to warunki zatrąbienia przed ruszeniem - if( (OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank | Connect | Prepare_engine ) ) != 0 ) // jeśli jedzie w dowolnym trybie - if ((mvOccupied->Vel < 1.0)) // jesli stoi (na razie, bo chyba powinien też, gdy hamuje przed semaforem) - if (iDrivigFlags & moveStartHorn) // jezeli trąbienie włączone - if (!(iDrivigFlags & (moveStartHornDone | moveConnect))) - // jeśli nie zatrąbione i nie jest to moment podłączania składu - if (mvOccupied->CategoryFlag & 1) - // tylko pociągi trąbią (unimogi tylko na torach, więc trzeba raczej sprawdzać tor) - if ((NewVel >= 1.0) || (NewVel < 0.0)) { - // o ile prędkość jest znacząca - // zatrąb po odhamowaniu - iDrivigFlags |= moveStartHornNow; - } + if( ( is_train() ) // tylko pociągi trąbią (unimogi tylko na torach, więc trzeba raczej sprawdzać tor) + && ( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank | Connect | Prepare_engine ) ) != 0 ) // jeśli jedzie w dowolnym trybie + && ( ( iDrivigFlags & moveStartHorn ) != 0 ) // jezeli trąbienie włączone + && ( ( iDrivigFlags & ( moveStartHornDone | moveConnect ) ) == 0 ) // jeśli nie zatrąbione i nie jest to moment podłączania składu + && ( mvOccupied->Vel < 1.0 ) // jesli stoi (na razie, bo chyba powinien też, gdy hamuje przed semaforem) + && ( ( NewVel >= 1.0 ) || ( NewVel < 0.0 ) ) ) { // o ile prędkość jest znacząca zatrąb po odhamowaniu + iDrivigFlags |= moveStartHornNow; + } } VelSignal = NewVel; // prędkość zezwolona na aktualnym odcinku VelNext = NewVelNext; // prędkość przy następnym obiekcie @@ -2629,7 +2582,7 @@ double TController::BrakeAccFactor() const && ( AccDesired < 0.0 ) && ( ( ActualProximityDist > fMinProximityDist ) || ( mvOccupied->Vel > VelDesired + fVelPlus ) ) ) { - Factor += ( fBrakeReaction * ( /*mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition < 0.5 ? 1.5 : 1 ) ) * mvOccupied->Vel / ( std::max( 0.0, ActualProximityDist ) + 1 ) * ( ( AccDesired - AbsAccS_pub ) / fAccThreshold ); + Factor += ( fBrakeReaction * ( /*mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition < 0.5 ? 1.5 : 1 ) ) * mvOccupied->Vel / ( std::max( 0.0, ActualProximityDist ) + 1 ) * ( ( AccDesired - AbsAccS ) / fAccThreshold ); } /* if (mvOccupied->TrainType == dt_DMU && mvOccupied->Vel > 40 && VelNext<40) @@ -2638,224 +2591,177 @@ double TController::BrakeAccFactor() const return Factor; } -void TController::SetDriverPsyche() -{ - if ((Psyche == Aggressive) && (OrderCurrentGet() == Obey_train)) - { +void TController::SetDriverPsyche() { + + if ((Psyche == Aggressive) && (OrderCurrentGet() == Obey_train)) { ReactionTime = HardReactionTime; // w zaleznosci od charakteru maszynisty - if (mvOccupied->CategoryFlag & 2) - { + if (is_car()) { WaitingExpireTime = 1; // tyle ma czekać samochód, zanim się ruszy AccPreferred = 3.0; //[m/ss] agresywny } - else - { + else { WaitingExpireTime = 61; // tyle ma czekać, zanim się ruszy AccPreferred = HardAcceleration; // agresywny } } - else - { + else { ReactionTime = EasyReactionTime; // spokojny - if (mvOccupied->CategoryFlag & 2) - { + if (is_car()) { WaitingExpireTime = 3; // tyle ma czekać samochód, zanim się ruszy AccPreferred = 2.0; //[m/ss] } - else - { + else { WaitingExpireTime = 65; // tyle ma czekać, zanim się ruszy AccPreferred = EasyAcceleration; } } - if (mvControlling && mvOccupied) - { // with Controlling do - if (mvControlling->MainCtrlPowerPos() < 3) - ReactionTime = mvControlling->InitialCtrlDelay + ReactionTime; - if (/* GBH mvOccupied->BrakeCtrlPos*/BrakeCtrlPosition > 1) - ReactionTime = 0.5 * ReactionTime; - } -}; +} bool TController::PrepareEngine() { // odpalanie silnika // HACK: don't immediately activate inert vehicle in case the simulation is about to replace us with human driver if( ( mvOccupied->Vel < 1.0 ) && ( fActionTime < 0.0 ) ) { return false; } - bool OK = false, - voltfront = false, - voltrear = false; LastReactionTime = 0.0; - ReactionTime = PrepareTime; + ReactionTime = ( mvOccupied->Vel < 5 ? PrepareTime : EasyReactionTime ); // react faster with rolling start - if ( mvControlling->EnginePowerSource.SourceType == TPowerSource::CurrentCollector ) { - voltfront = true; - voltrear = true; - } - else { - if( mvOccupied->TrainType != dt_EZT ) { - // Ra 2014-06: to jest wirtualny prąd dla spalinowych??? - voltfront = true; + cue_action( locale::string::driver_hint_batteryon ); + cue_action( locale::string::driver_hint_radioon ); + + if( has_diesel_engine() ) { + cue_action( locale::string::driver_hint_oilpumpon ); + PrepareHeating(); + if( true == IsHeatingTemperatureOK ) { + cue_action( locale::string::driver_hint_fuelpumpon ); } - } - auto workingtemperature { true }; - if (AIControllFlag) { - // część wykonawcza dla sterowania przez komputer - mvOccupied->BatterySwitch( true ); - mvOccupied->Radio = true; - if( ( mvControlling->EngineType == TEngineType::DieselElectric ) - || ( mvControlling->EngineType == TEngineType::DieselEngine ) ) { - mvControlling->OilPumpSwitch( true ); - workingtemperature = UpdateHeating(); - if( true == workingtemperature ) { - mvControlling->FuelPumpSwitch( true ); - } - } - if( ( mvPantographUnit->EnginePowerSource.SourceType == TPowerSource::CurrentCollector ) - &&( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 0 ) ) { - // if our pantograph unit isn't a pantograph-devoid fallback - if (mvPantographUnit->PantPress < 4.2) { - // załączenie małej sprężarki - if( false == mvPantographUnit->PantAutoValve ) { - // odłączenie zbiornika głównego, bo z nim nie da rady napompować - mvPantographUnit->bPantKurek3 = false; - } - mvPantographUnit->PantCompFlag = true; // załączenie sprężarki pantografów - } - else { - // jeżeli jest wystarczające ciśnienie w pantografach - if ((!mvPantographUnit->bPantKurek3) - || (mvPantographUnit->PantPress <= mvPantographUnit->ScndPipePress)) // kurek przełączony albo główna już pompuje - mvPantographUnit->PantCompFlag = false; // sprężarkę pantografów można już wyłączyć - } - if( ( fOverhead2 == -1.0 ) && ( iOverheadDown == 0 ) ) { - mvOccupied->OperatePantographsValve( operation_t::enable ); - mvOccupied->OperatePantographValve( end::front, operation_t::enable ); - mvOccupied->OperatePantographValve( end::rear, operation_t::enable ); - } + else { + cue_action( locale::string::driver_hint_waittemperaturetoolow ); } } - if ((mvPantographUnit->PantographVoltage != 0.0) || voltfront || voltrear) - { // najpierw ustalamy kierunek, jeśli nie został ustalony - if( !iDirection ) { - // jeśli nie ma ustalonego kierunku - if( mvOccupied->Vel < 0.01 ) { // ustalenie kierunku, gdy stoi - iDirection = mvOccupied->CabActive; // wg wybranej kabiny - if( !iDirection ) { - // jeśli nie ma ustalonego kierunku - if( ( mvPantographUnit->PantographVoltage != 0.0 ) || voltfront || voltrear ) { - if( mvOccupied->Couplers[ end::rear ].Connected == nullptr ) { - // jeśli z tyłu nie ma nic - iDirection = -1; // jazda w kierunku sprzęgu 1 - } - if( mvOccupied->Couplers[ end::front ].Connected == nullptr ) { - // jeśli z przodu nie ma nic - iDirection = 1; // jazda w kierunku sprzęgu 0 - } - } - } + if( ( mvPantographUnit->EnginePowerSource.SourceType == TPowerSource::CurrentCollector ) + && ( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 0 ) ) { + // if our pantograph unit isn't a pantograph-devoid fallback + if (mvPantographUnit->PantPress < 4.2) { + // załączenie małej sprężarki + if( false == mvPantographUnit->PantAutoValve ) { + // odłączenie zbiornika głównego, bo z nim nie da rady napompować + cue_action( locale::string::driver_hint_pantographairsourcesetauxiliary ); } - else { - // ustalenie kierunku, gdy jedzie - if( ( mvControlling->PantographVoltage != 0.0 ) || voltfront || voltrear ) { - if( mvOccupied->V < 0 ) { - // jedzie do tyłu - iDirection = -1; // jazda w kierunku sprzęgu 1 - } - else { - // jak nie do tyłu, to do przodu - iDirection = 1; // jazda w kierunku sprzęgu 0 - } - } + cue_action( locale::string::driver_hint_pantographcompressoron ); // załączenie sprężarki pantografów + if( mvPantographUnit->PantCompFlag ) { + cue_action( locale::string::driver_hint_waitpantographpressuretoolow ); } } - if (AIControllFlag) // jeśli prowadzi komputer - { // część wykonawcza dla sterowania przez komputer - if( IsAnyConverterOverloadRelayOpen ) { - // wywalił bezpiecznik nadmiarowy przetwornicy - mvOccupied->ConverterSwitch( false ); - mvOccupied->RelayReset( relay_t::primaryconverteroverload ); // reset nadmiarowego + else { + // jeżeli jest wystarczające ciśnienie w pantografach + if( ( false == mvPantographUnit->bPantKurek3 ) + || ( mvPantographUnit->PantPress <= mvPantographUnit->ScndPipePress ) ) { // kurek przełączony albo główna już pompuje + cue_action( locale::string::driver_hint_pantographcompressoroff ); // sprężarkę pantografów można już wyłączyć } - if (IsAnyLineBreakerOpen) { - ZeroSpeed(); - if( mvOccupied->DirActive == 0 ) { - OrderDirectionChange( iDirection, mvOccupied ); + } + if( ( fOverhead2 == -1.0 ) && ( iOverheadDown == 0 ) ) { + cue_action( locale::string::driver_hint_pantographsvalveon ); + cue_action( locale::string::driver_hint_frontpantographvalveon ); + cue_action( locale::string::driver_hint_rearpantographvalveon ); + } + } + + auto const ispoweravailable = + ( mvControlling->EnginePowerSource.SourceType != TPowerSource::CurrentCollector ) + || ( std::max( mvControlling->GetTrainsetHighVoltage(), mvControlling->PantographVoltage ) > mvControlling->EnginePowerSource.CollectorParameters.MinV ); + + bool isready = false; + + if( ( IsHeatingTemperatureOK ) + && ( ispoweravailable ) ) { + // najpierw ustalamy kierunek, jeśli nie został ustalony + PrepareDirection(); + + if( IsAnyConverterOverloadRelayOpen ) { + // wywalił bezpiecznik nadmiarowy przetwornicy + cue_action( locale::string::driver_hint_converteroff ); + cue_action( locale::string::driver_hint_primaryconverteroverloadreset ); // reset nadmiarowego + } + if( IsAnyLineBreakerOpen ) { + // activate main circuit or engine + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + // consist-wide ground relay reset + cue_action( locale::string::driver_hint_maincircuitgroundreset ); + cue_action( locale::string::driver_hint_tractionnmotoroverloadreset ); + if( mvOccupied->EngineType == TEngineType::DieselEngine ) { + // specjalnie dla SN61 żeby nie zgasł + cue_action( locale::string::driver_hint_mastercontrollersetidle ); + } + cue_action( locale::string::driver_hint_linebreakerclose ); + } + else { + // main circuit or engine is on, set up vehicle devices and controls + if( false == IsAnyConverterOverloadRelayOpen ) { + cue_action( locale::string::driver_hint_converteron ); + // w EN57 sprężarka w ra jest zasilana z silnikowego + // TODO: change condition to presence of required voltage type + if( IsAnyConverterEnabled ) { + cue_action( locale::string::driver_hint_compressoron ); } - mvControlling->FuseOn(); // consist-wide ground relay reset - if( mvOccupied->TrainType == dt_SN61 ) { - // specjalnie dla SN61 żeby nie zgasł - while( ( mvControlling->RList[ mvControlling->MainCtrlPos ].Mn == 0 ) - && ( mvControlling->IncMainCtrl( 1 ) ) ) { - ; - } + if( ( mvControlling->ScndPipePress < 4.5 ) && ( mvControlling->VeselVolume > 0.0 ) ) { + cue_action( locale::string::driver_hint_waitpressuretoolow ); } - if( ( mvControlling->EnginePowerSource.SourceType != TPowerSource::CurrentCollector ) - || ( std::max( mvControlling->GetTrainsetHighVoltage(), mvControlling->PantographVoltage ) > mvControlling->EnginePowerSource.CollectorParameters.MinV ) ) { - mvOccupied->MainSwitch( true ); + // enable motor blowers + if( mvOccupied->MotorBlowers[ end::front ].speed != 0 ) { + cue_action( locale::string::driver_hint_frontmotorblowerson ); + } + if( mvOccupied->MotorBlowers[ end::rear ].speed != 0 ) { + cue_action( locale::string::driver_hint_rearmotorblowerson ); } } - else { - if( false == IsAnyConverterOverloadRelayOpen ) { - mvOccupied->ConverterSwitch( true ); - // w EN57 sprężarka w ra jest zasilana z silnikowego - mvOccupied->CompressorSwitch( true ); - // enable motor blowers - mvOccupied->MotorBlowersSwitchOff( false, end::front ); - mvOccupied->MotorBlowersSwitch( true, end::front ); - mvOccupied->MotorBlowersSwitchOff( false, end::rear ); - mvOccupied->MotorBlowersSwitch( true, end::rear ); - } - // enable train brake if it's off - if( mvOccupied->fBrakeCtrlPos == mvOccupied->Handle->GetPos( bh_NP ) ) { - mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_RP ) ); - } - // sync virtual brake state with the 'real' one - std::unordered_map const brakepositions { + // set up train brake + if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_RP ) ) { + cue_action( locale::string::driver_hint_trainbrakerelease ); + } + // sync virtual brake state with the 'real' one + if( mvOccupied->BrakeHandle != TBrakeHandle::NoHandle ) { + std::unordered_map const brakepositions{ { static_cast( mvOccupied->Handle->GetPos( bh_RP ) ), gbh_RP }, { static_cast( mvOccupied->Handle->GetPos( bh_NP ) ), gbh_NP }, { static_cast( mvOccupied->Handle->GetPos( bh_FS ) ), gbh_FS } }; - auto const lookup { brakepositions.find( static_cast( mvOccupied->fBrakeCtrlPos ) ) }; + auto const lookup{ brakepositions.find( static_cast( mvOccupied->fBrakeCtrlPos ) ) }; if( lookup != brakepositions.end() ) { BrakeLevelSet( lookup->second ); // GBH } - // sync spring brake state across consist - mvOccupied->SpringBrakeActivate( mvOccupied->SpringBrake.Activate ); - - OK = ( false == IsAnyConverterOverloadRelayOpen ) - && ( VelforDriver == -1 ); } + // sync spring brake state across consist + cue_action( + mvOccupied->SpringBrake.Activate ? + locale::string::driver_hint_springbrakeon : + locale::string::driver_hint_springbrakeoff ); } - else - OK = mvControlling->Mains; + isready = ( false == IsAnyConverterOverloadRelayOpen ) + && ( mvOccupied->DirActive != 0 ) + && ( false == IsAnyLineBreakerOpen ) + && ( true == IsAnyConverterEnabled ) + && ( true == IsAnyCompressorEnabled ) + && ( ( mvControlling->ScndPipePress > 4.5 ) || ( mvControlling->VeselVolume == 0.0 ) ) + && ( ( mvOccupied->fBrakeCtrlPos == mvOccupied->Handle->GetPos( bh_RP ) || ( mvOccupied->BrakeHandle == TBrakeHandle::NoHandle ) ) ); } - else - OK = false; - if( ( true == OK ) - && ( mvOccupied->DirActive != 0 ) - && ( true == workingtemperature ) - && ( ( mvControlling->ScndPipePress > 4.5 ) || ( mvControlling->VeselVolume == 0.0 ) ) ) { + iEngineActive = isready; + if( true == iEngineActive ) { + // jeśli dotychczas spał teraz nie ma powodu do stania + eAction = TAction::actUnknown; if( eStopReason == stopSleep ) { - // jeśli dotychczas spał teraz nie ma powodu do stania eStopReason = stopNone; } - eAction = TAction::actUnknown; - iEngineActive = true; iDrivigFlags |= moveActive; // może skanować sygnały i reagować na komendy + } - return true; - } - else { - iEngineActive = false; - return false; - } -}; + return iEngineActive; +} // wyłączanie silnika (test wyłączenia, a część wykonawcza tylko jeśli steruje komputer) bool TController::ReleaseEngine() { - +/* if( mvOccupied->Vel > 0.01 ) { VelDesired = 0.0; AccDesired = std::min( AccDesired, -1.25 ); // hamuj solidnie @@ -2868,89 +2774,74 @@ bool TController::ReleaseEngine() { // don't bother with the rest until we're standing still return false; } +*/ + // don't bother with the rest until we're standing still + if( mvOccupied->Vel > 0.01 ) { return false; } LastReactionTime = 0.0; ReactionTime = PrepareTime; - bool OK { false }; - - if( false == AIControllFlag ) { - // tylko to testujemy dla pojazdu człowieka - OK = ( ( mvOccupied->DirActive == 0 ) && ( mvControlling->Mains ) ); + cue_action( locale::string::driver_hint_releaseroff ); + // release train brake if on flats... + if( std::abs( fAccGravity ) < 0.01 ) { + apply_independent_brake_only(); } - else { - // jeśli steruje komputer - mvOccupied->BrakeReleaser( 0 ); - if( std::abs( fAccGravity ) < 0.01 ) { - // release train brake if on flats... - if( mvOccupied->LocalBrake != TLocalBrake::ManualBrake ) { - // ...as long as it's a vehicle with independent brake, anyway - // TODO: check if we shouldn't leave it engaged instead - while( true == DecBrake() ) { - ; - } - // ...and engage independent brake - mvOccupied->IncLocalBrakeLevel( LocalBrakePosNo ); - } - } - else { - // on slopes engage train brake - AccDesired = std::min( AccDesired, -0.9 ); - while( true == IncBrake() ) { - ; - } - } - ZeroSpeed(); - ZeroDirection(); - - // zamykanie drzwi - mvOccupied->OperateDoors( side::right, false ); - mvOccupied->OperateDoors( side::left, false ); - - if( true == mvControlling->Mains ) { - // heating - mvControlling->HeatingAllow = false; - // devices - mvOccupied->ConverterSwitch( false ); - // line breaker/engine - OK = mvOccupied->MainSwitch( false ); - - mvOccupied->OperatePantographValve( end::front, operation_t::disable ); - mvOccupied->OperatePantographValve( end::rear, operation_t::disable ); - } - else { - OK = true; - } - - if( OK ) { - // finish vehicle shutdown - if( ( mvControlling->EngineType == TEngineType::DieselElectric ) - || ( mvControlling->EngineType == TEngineType::DieselEngine ) ) { - // heating/cooling subsystem - mvControlling->WaterHeaterSwitch( false ); - // optionally turn off the water pump as well - if( mvControlling->WaterPump.start_type != start_t::battery ) { - mvControlling->WaterPumpSwitch( false ); - } - // fuel and oil subsystems - mvControlling->FuelPumpSwitch( false ); - mvControlling->OilPumpSwitch( false ); - } - // gasimy światła - Lights( 0, 0 ); - // activate parking brake - // TBD: do it earlier? - mvOccupied->SpringBrakeActivate(true); - if( ( mvOccupied->LocalBrake == TLocalBrake::ManualBrake ) - || ( mvOccupied->MBrake == true ) ) { - mvOccupied->IncManualBrakeLevel( ManualBrakePosNo ); - } - // switch off remaining power - mvOccupied->Radio = false; - mvOccupied->BatterySwitch( false ); - } + // ... but on slopes engage train brake + else { +// AccDesired = std::min( AccDesired, -0.9 ); + cue_action( locale::string::driver_hint_trainbrakeapply ); } + cue_action( locale::string::driver_hint_mastercontrollersetreverserunlock ); + cue_action( locale::string::driver_hint_directionnone ); + // zamykanie drzwi + cue_action( locale::string::driver_hint_doorrightclose ); + cue_action( locale::string::driver_hint_doorleftclose ); + // heating + cue_action( locale::string::driver_hint_consistheatingoff ); + // devices + cue_action( locale::string::driver_hint_compressoroff ); + cue_action( locale::string::driver_hint_converteroff ); + // line breaker/engine + cue_action( locale::string::driver_hint_linebreakeropen ); + // pantographs + cue_action( locale::string::driver_hint_frontpantographvalveoff ); + cue_action( locale::string::driver_hint_rearpantographvalveoff ); + + if( false == mvControlling->Mains ) { + // finish vehicle shutdown + if( has_diesel_engine() ) { + // heating/cooling subsystem + cue_action( locale::string::driver_hint_waterheateroff ); + // optionally turn off the water pump as well + if( mvControlling->WaterPump.start_type != start_t::battery ) { + cue_action( locale::string::driver_hint_waterpumpoff ); + } + // fuel and oil subsystems + cue_action( locale::string::driver_hint_fuelpumpoff ); + cue_action( locale::string::driver_hint_oilpumpoff ); + } + // gasimy światła + cue_action( locale::string::driver_hint_lightsoff ); + // activate parking brake + // TBD: do it earlier? + cue_action( locale::string::driver_hint_springbrakeon ); + if( ( mvOccupied->LocalBrake == TLocalBrake::ManualBrake ) + || ( mvOccupied->MBrake == true ) ) { + cue_action( locale::string::driver_hint_manualbrakon ); + } + // switch off remaining power + cue_action( locale::string::driver_hint_radiooff ); + cue_action( locale::string::driver_hint_batteryoff ); + } + + auto const OK { + ( mvOccupied->DirActive == 0 ) +// && ( false == IsAnyCompressorEnabled ) +// && ( false == IsAnyConverterEnabled ) + && ( false == mvControlling->Mains ) + && ( false == mvOccupied->Power24vIsAvailable ) }; + if (OK) { // jeśli się zatrzymał iEngineActive = false; @@ -3082,17 +2973,16 @@ bool TController::IncBrake() 1.0 ); } } - else - { + else { OK = /*mvOccupied->*/BrakeLevelAdd( BrakingLevelIncrease ); //GBH // brake harder if the acceleration is much higher than desired /*if( ( deltaAcc > 2 * fBrake_a1[ 0 ] ) && ( mvOccupied->BrakeCtrlPosR + BrakingLevelIncrease <= 5.0 ) ) { mvOccupied->BrakeLevelAdd( BrakingLevelIncrease ); } GBH */ - if ((deltaAcc > 2 * fBrake_a1[0]) - && (BrakeCtrlPosition + BrakingLevelIncrease <= 5.0)) { - /*mvOccupied->*/BrakeLevelAdd(BrakingLevelIncrease); + if( ( deltaAcc > 2 * fBrake_a1[ 0 ] ) + && ( BrakeCtrlPosition + BrakingLevelIncrease <= 5.0 ) ) { + /*mvOccupied->*/BrakeLevelAdd( BrakingLevelIncrease ); } } } @@ -3191,85 +3081,104 @@ bool TController::IncBrakeEIM() return OK; } -bool TController::DecBrake() -{ // zmniejszenie siły hamowania - bool OK = false; - double deltaAcc = -1.0; - double pos_diff = 1.0; - switch (mvOccupied->BrakeSystem) - { - case TBrakeSystem::Individual: - if (mvOccupied->LocalBrake == TLocalBrake::ManualBrake) - OK = mvOccupied->DecManualBrakeLevel(1 + floor(0.5 + fabs(AccDesired))); - else - OK = mvOccupied->DecLocalBrakeLevel(1 + floor(0.5 + fabs(AccDesired))); - break; - case TBrakeSystem::Pneumatic: - if( ( fBrake_a0[ 0 ] != 0.0 ) - || ( fBrake_a1[ 0 ] != 0.0 ) ) { - if( mvOccupied->TrainType == dt_DMU ) - pos_diff = 0.25; - deltaAcc = -AccDesired*BrakeAccFactor() - (fBrake_a0[0] + 4 * (/*GBH mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition - pos_diff)*fBrake_a1[0]); +// zmniejszenie siły hamowania +bool TController::DecBrake() { + + auto OK { false }; + + switch( mvOccupied->BrakeSystem ) { + case TBrakeSystem::Individual: { + auto const positionchange { 1 + std::floor( 0.5 + std::abs( AccDesired ) ) }; + OK = ( + mvOccupied->LocalBrake == TLocalBrake::ManualBrake ? + mvOccupied->DecManualBrakeLevel( positionchange ) : + mvOccupied->DecLocalBrakeLevel( positionchange ) ); + break; } - if (deltaAcc < 0) - { - if (/*GBH mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition > 0) - { - OK = /*mvOccupied->*/BrakeLevelAdd(-0.25); - //if ((deltaAcc < 5 * fBrake_a1[0]) && (mvOccupied->BrakeCtrlPosR >= 1.2)) - // mvOccupied->BrakeLevelAdd(-1.0); - /* if (mvOccupied->BrakeCtrlPosR < 0.74) GBH */ - if (BrakeCtrlPosition < 0.74) - /*mvOccupied->*/BrakeLevelSet(gbh_RP); - } - } - if( !OK ) { - OK = mvOccupied->DecLocalBrakeLevel(2); + case TBrakeSystem::Pneumatic: { + auto deltaAcc { -1.0 }; + if( ( fBrake_a0[ 0 ] != 0.0 ) + || ( fBrake_a1[ 0 ] != 0.0 ) ) { + auto const pos_diff { ( mvOccupied->TrainType == dt_DMU ? 0.25 : 1.0 ) }; + deltaAcc = -AccDesired * BrakeAccFactor() - ( fBrake_a0[ 0 ] + 4 * (/*GBH mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition - pos_diff )*fBrake_a1[ 0 ] ); + } + if (deltaAcc < 0) { + if(/*GBH mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition > 0 ) { + OK = /*mvOccupied->*/BrakeLevelAdd( -0.25 ); + //if ((deltaAcc < 5 * fBrake_a1[0]) && (mvOccupied->BrakeCtrlPosR >= 1.2)) + // mvOccupied->BrakeLevelAdd(-1.0); + /* if (mvOccupied->BrakeCtrlPosR < 0.74) GBH */ + if( BrakeCtrlPosition < 0.74 ) + /*mvOccupied->*/BrakeLevelSet( gbh_RP ); + } + } + if( !OK ) { + OK = mvOccupied->DecLocalBrakeLevel(2); + } + if( !OK ) { + OK = DecBrakeEIM(); + } + /* + // NOTE: disabled, duplicate of AI's behaviour in UpdateSituation() + if (mvOccupied->PipePress < 3.0) + Need_BrakeRelease = true; + */ + break; } - if (!OK) { - OK = DecBrakeEIM(); - } -/* -// NOTE: disabled, duplicate of AI's behaviour in UpdateSituation() - if (mvOccupied->PipePress < 3.0) - Need_BrakeRelease = true; -*/ - break; - case TBrakeSystem::ElectroPneumatic: - if (mvOccupied->EngineType == TEngineType::ElectricInductionMotor) { - if (mvOccupied->BrakeHandle == TBrakeHandle::MHZ_EN57) { - if (mvOccupied->BrakeCtrlPos > mvOccupied->Handle->GetPos(bh_RP)) - OK = mvOccupied->BrakeLevelAdd(-1.0); - } - else { - OK = DecBrakeEIM(); - } - } - else if( mvOccupied->Handle->TimeEP == false ) { - auto const initialbrakeposition { mvOccupied->fBrakeCtrlPos }; - auto const AccMax { std::min(fBrake_a0[ 0 ] + 12 * fBrake_a1[ 0 ], mvOccupied->MED_amax) }; - mvOccupied->BrakeLevelSet( - interpolate( - mvOccupied->Handle->GetPos( bh_EPR ), - mvOccupied->Handle->GetPos( bh_EPB ), - clamp( -AccDesired / AccMax * mvOccupied->AIHintLocalBrakeAccFactor, 0.0, 1.0 ) ) ); - OK = ( mvOccupied->fBrakeCtrlPos != initialbrakeposition ); + case TBrakeSystem::ElectroPneumatic: { + if( mvOccupied->EngineType == TEngineType::ElectricInductionMotor ) { + if( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_EN57 ) { + if( mvOccupied->BrakeCtrlPos > mvOccupied->Handle->GetPos( bh_RP ) ) { + OK = mvOccupied->BrakeLevelAdd( -1.0 ); + } + } + else { + OK = DecBrakeEIM(); + } + } + else if( mvOccupied->Handle->TimeEP == false ) { + auto const initialbrakeposition { mvOccupied->fBrakeCtrlPos }; + auto const AccMax { std::min( fBrake_a0[ 0 ] + 12 * fBrake_a1[ 0 ], mvOccupied->MED_amax ) }; + mvOccupied->BrakeLevelSet( + interpolate( + mvOccupied->Handle->GetPos( bh_EPR ), + mvOccupied->Handle->GetPos( bh_EPB ), + clamp( -AccDesired / AccMax * mvOccupied->AIHintLocalBrakeAccFactor, 0.0, 1.0 ) ) ); + OK = ( mvOccupied->fBrakeCtrlPos != initialbrakeposition ); + } + else if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_EPR ) ) { + mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_EPR)); + if( mvOccupied->Handle->GetPos( bh_EPR ) - mvOccupied->Handle->GetPos( bh_EPN ) < 0.1 ) { + mvOccupied->SwitchEPBrake( 1 ); + } + OK = true; + } + else { + OK = false; + } + if( !OK ) { + OK = mvOccupied->DecLocalBrakeLevel( 2 ); + } + break; } - else if (mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos(bh_EPR)) - { - mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_EPR)); - if (mvOccupied->Handle->GetPos(bh_EPR) - mvOccupied->Handle->GetPos(bh_EPN) < 0.1) - mvOccupied->SwitchEPBrake(1); - OK = true; + default: { + break; } - else - OK = false; - if (!OK) - OK = mvOccupied->DecLocalBrakeLevel(2); - break; } return OK; -}; +} + +void TController::LapBrake() { + + if( mvOccupied->Handle->TimeEP ) { + if( mvOccupied->Handle->GetPos( bh_EPR ) - mvOccupied->Handle->GetPos( bh_EPN ) < 0.1 ) { + mvOccupied->SwitchEPBrake( 0 ); + } + else { + mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_EPN ) ); + } + } +} void TController::ZeroLocalBrake() { @@ -3324,34 +3233,7 @@ bool TController::DecBrakeEIM() bool TController::IncSpeed() { // zwiększenie prędkości; zwraca false, jeśli dalej się nie da zwiększać - if( fActionTime < 0.0 ) { - // gdy jest nakaz poczekać z jazdą, to nie ruszać - return false; - } - if( IsAnyCouplerStretched ) { - // train is already stretched past its limits, don't pull even harder - return false; - } - bool OK = true; - if( ( VelDesired > 0.0 ) // to prevent door shuffle on stop - && ( doors_open() || doors_permit_active() ) ) { - // zamykanie drzwi - tutaj wykonuje tylko AI (zmienia fActionTime) - Doors( false ); - } - if (mvOccupied->SpringBrake.Activate) { - mvOccupied->SpringBrakeActivate(false); - } - // Doors() call can potentially adjust fActionTime - if( fActionTime < 0.0 ) { - // gdy jest nakaz poczekać z jazdą, to nie ruszać - return false; - } - if( true == mvOccupied->DepartureSignal ) { - // shut off departure warning - mvOccupied->signal_departure( false ); - } - if (mvControlling->SlippingWheels) - return false; // jak poślizg, to nie przyspieszamy + auto OK { false }; switch (mvOccupied->EngineType) { case TEngineType::None: // McZapkie-041003: wagon sterowniczy @@ -3378,7 +3260,7 @@ bool TController::IncSpeed() // if it generates enough traction force // to build up speed to 30/40 km/h for passenger/cargo train (10 km/h less if going uphill) auto const sufficienttractionforce { std::abs( mvControlling->Ft ) > ( IsHeavyCargoTrain ? 75 : 50 ) * 1000.0 }; - auto const sufficientacceleration { AbsAccS_pub >= ( IsHeavyCargoTrain ? 0.03 : IsCargoTrain ? 0.06 : 0.09 ) }; + auto const sufficientacceleration { AbsAccS >= ( IsHeavyCargoTrain ? 0.03 : IsCargoTrain ? 0.06 : 0.09 ) }; auto const seriesmodefieldshunting { ( mvControlling->ScndCtrlPos > 0 ) && ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn == 1 ) }; auto const parallelmodefieldshunting { ( mvControlling->ScndCtrlPos > 0 ) && ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) }; auto const useseriesmodevoltage { @@ -3466,7 +3348,7 @@ bool TController::IncSpeed() } } } - mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania +// mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania break; case TEngineType::Dumb: if (!IsAnyMotorOverloadRelayOpen) @@ -3568,12 +3450,13 @@ bool TController::IncSpeed() } } } + // TODO: move this check to a more suitable place, or scrap it if( false == mvControlling->Mains ) { SpeedCntrl(0.0); mvControlling->DecScndCtrl( 2 ); - mvOccupied->MainSwitch( true ); - mvOccupied->ConverterSwitch( true ); - mvOccupied->CompressorSwitch( true ); + cue_action( locale::string::driver_hint_linebreakerclose ); + cue_action( locale::string::driver_hint_converteron ); + cue_action( locale::string::driver_hint_compressoron ); } break; } @@ -3597,13 +3480,13 @@ bool TController::DecSpeed(bool force) if (force) // przy aktywacji kabiny jest potrzeba natychmiastowego wyzerowania if (mvControlling->MainCtrlPosNo > 0) // McZapkie-041003: wagon sterowniczy, np. EZT mvControlling->DecMainCtrl( std::min( mvControlling->MainCtrlPowerPos(), 2 ) ); - mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania +// mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania return false; case TEngineType::ElectricSeriesMotor: OK = mvControlling->DecScndCtrl(2); // najpierw bocznik na zero if (!OK) OK = mvControlling->DecMainCtrl( std::min( mvControlling->MainCtrlPowerPos(), 2 ) ); - mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania +// mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania break; case TEngineType::Dumb: case TEngineType::DieselElectric: @@ -3667,8 +3550,9 @@ bool TController::IncSpeedEIM() { break; case 1: OK = mvControlling->MainCtrlPos < 6; - if( OK ) + if( OK ) { mvControlling->MainCtrlPos = 6; + } /* // TBD, TODO: set position based on desired acceleration? OK = mvControlling->MainCtrlPos < mvControlling->MainCtrlPosNo; @@ -3679,8 +3563,9 @@ bool TController::IncSpeedEIM() { break; case 2: OK = mvControlling->MainCtrlPos < 4; - if( OK ) + if( OK ) { mvControlling->MainCtrlPos = 4; + } break; } return OK; @@ -3724,12 +3609,15 @@ bool TController::BrakeLevelAdd(double b) (BrakeCtrlPosition > -1.0); // true, jeśli można kontynuować } -void TController::SpeedSet() -{ // Ra: regulacja prędkości, wykonywana w każdym przebłysku świadomości AI - // ma dokręcać do bezoporowych i zdejmować pozycje w przypadku przekroczenia prądu +// Ra: regulacja prędkości, wykonywana w każdym przebłysku świadomości AI +// ma dokręcać do bezoporowych i zdejmować pozycje w przypadku przekroczenia prądu +void TController::SpeedSet() { + + if( false == AIControllFlag ) { return; } + switch (mvOccupied->EngineType) { - case TEngineType::None: // McZapkie-041003: wagon sterowniczy + case TEngineType::None: { // McZapkie-041003: wagon sterowniczy if (mvControlling->MainCtrlPosNo > 0) { // jeśli ma czym kręcić // TODO: sprawdzanie innego czlonu //if (!FuseFlagCheck()) @@ -3742,7 +3630,7 @@ void TController::SpeedSet() if( fActionTime >= 0.0 ) { fActionTime = -5.0; // niech trochę potrzyma } - mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania +// mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania } else { // jak ma jechać @@ -3787,18 +3675,14 @@ void TController::SpeedSet() if( mvControlling->MainCtrlPos ) // jak załączył pozycję { fActionTime = -5.0; // niech trochę potrzyma - mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania +// mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania } } } break; - case TEngineType::ElectricSeriesMotor: - if( ( false == mvControlling->StLinFlag ) - && ( false == mvControlling->DelayCtrlFlag ) ) { - // styczniki liniowe rozłączone yBARC - ZeroSpeed(); - } - else if (Ready || (iDrivigFlags & movePress)) // o ile może jechać + } + case TEngineType::ElectricSeriesMotor: { + if (Ready || (iDrivigFlags & movePress)) { // o ile może jechać if (fAccGravity < -0.10) // i jedzie pod górę większą niż 10 promil { // procedura wjeżdżania na ekstremalne wzniesienia if (fabs(mvControlling->Im) > 0.85 * mvControlling->Imax) // a prąd jest większy niż 85% nadmiarowego @@ -3847,61 +3731,60 @@ void TController::SpeedSet() if (fAccGravity > -0.02) // a i pochylenie mnijsze niż 2‰ mvControlling->CurrentSwitch(false); // rozruch wysoki wyłącz } - break; - case TEngineType::Dumb: - break; - case TEngineType::DieselElectric: - if( ( false == mvControlling->StLinFlag ) - && ( mvControlling->MainCtrlPowerPos() > 1 ) ) { - // styczniki liniowe rozłączone yBARC - ZeroSpeed(); } break; - case TEngineType::ElectricInductionMotor: + } + case TEngineType::Dumb: { break; - case TEngineType::DieselEngine: + } + case TEngineType::DieselElectric: { + break; + } + case TEngineType::ElectricInductionMotor: { + break; + } + case TEngineType::DieselEngine: { // Ra 2014-06: "automatyczna" skrzynia biegów... - if( false == mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].AutoSwitch ) { + auto const &motorparams { mvControlling->MotorParam[ mvControlling->ScndCtrlPos ] }; + auto const velocity { ( mvControlling->ShuntMode ? mvControlling->AnPos : 1.0 ) * mvControlling->Vel }; + if( false == motorparams.AutoSwitch ) { // gdy biegi ręczne - if( ( mvControlling->ShuntMode ? mvControlling->AnPos : 1.0 ) * mvControlling->Vel > - 0.75 * mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].mfi ) - // if (mvControlling->enrot>0.95*mvControlling->dizel_nMmax) //youBy: jeśli obroty > - // 0,95 nmax, wrzuć wyższy bieg - Ra: to nie działa - { // jak prędkość większa niż 0.6 maksymalnej na danym biegu, wrzucić wyższy + // jak prędkość większa niż procent maksymalnej na danym biegu, wrzucić wyższy + if( velocity > 0.75 * motorparams.mfi ) { + // ...presuming there is a higher gear if( mvControlling->ScndCtrlPos < mvControlling->ScndCtrlPosNo ) { - // ...presuming there is a higher gear mvControlling->DecMainCtrl( 2 ); - if( mvControlling->IncScndCtrl( 1 ) ) { - while( ( mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].mIsat == 0.0 ) // jeśli bieg jałowy - && ( mvControlling->IncScndCtrl( 1 ) ) ) { // to kolejny - ; - } + while( ( mvControlling->IncScndCtrl( 1 ) ) + && ( mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].mIsat == 0.0 ) ) { // jeśli bieg jałowy to kolejny + ; } } } - else if( ( mvControlling->ShuntMode ? mvControlling->AnPos : 1.0 ) * mvControlling->Vel < - mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].fi ) { // jak prędkość mniejsza niż minimalna na danym biegu, wrzucić niższy - mvControlling->DecMainCtrl( 2 ); - mvControlling->DecScndCtrl( 1 ); - if( mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].mIsat == 0.0 ) // jeśli bieg jałowy - if( mvControlling->ScndCtrlPos ) // a jeszcze zera nie osiągnięto - mvControlling->DecScndCtrl( 1 ); // to kolejny wcześniejszy - else - mvControlling->IncScndCtrl( 1 ); // a jak zeszło na zero, to powrót + // jak prędkość mniejsza niż minimalna na danym biegu, wrzucić niższy + else if( velocity < motorparams.fi ) { + // ... but ensure we don't switch all way down to 0 + if( mvControlling->ScndCtrlPos > 1 ) { + mvControlling->DecMainCtrl( 2 ); + while( ( mvControlling->ScndCtrlPos > 1 ) // repeat the entry check as we're working in a loop + && ( mvControlling->DecScndCtrl( 1 ) ) + && ( mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].mIsat == 0.0 ) ) { // jeśli bieg jałowy to kolejny + ; + } + } } } break; } + default: { + break; + } + } // enginetype }; void TController::SpeedCntrl(double DesiredSpeed) { - if (mvControlling->SpeedCtrlUnit.PowerStep > 0) { - while (mvControlling->SpeedCtrlUnit.DesiredPower < mvControlling->SpeedCtrlUnit.MaxPower) - { - mvControlling->SpeedCtrlPowerInc(); - } - } + if( false == mvControlling->SpeedCtrl ) { return; } + if (mvControlling->EngineType == TEngineType::DieselEngine) { if (DesiredSpeed < 0.1) { @@ -3925,10 +3808,20 @@ void TController::SpeedCntrl(double DesiredSpeed) while( ( mvControlling->ScndCtrlPos > DesiredPos ) && ( true == mvControlling->DecScndCtrl( 1 ) ) ) { ; } // all work is done in the condition loop while( ( mvControlling->ScndCtrlPos < DesiredPos ) && ( true == mvControlling->IncScndCtrl( 1 ) ) ) { ; } // all work is done in the condition loop } + + if( ( mvControlling->SpeedCtrlUnit.PowerStep > 0 ) && ( mvControlling->ScndCtrlPos > 0 ) ) { + while (mvControlling->SpeedCtrlUnit.DesiredPower < mvControlling->SpeedCtrlUnit.MaxPower) + { + mvControlling->SpeedCtrlPowerInc(); + } + } }; void TController::SetTimeControllers() { + // TBD, TODO: rework this method to use hint system and regardless of driver type + if( false == AIControllFlag ) { return; } + //1. Check the type of Main Brake Handle if (mvOccupied->BrakeSystem == TBrakeSystem::Pneumatic) { @@ -4106,8 +3999,8 @@ void TController::SetTimeControllers() } auto const DesiredPos { ( - AccDesired > AbsAccS_pub + 0.05 ? PosInc : - AccDesired < AbsAccS_pub - 0.05 ? PosDec : + AccDesired > AbsAccS + 0.05 ? PosInc : + AccDesired < AbsAccS - 0.05 ? PosDec : PosKeep ) }; while( ( mvControlling->MainCtrlPos > DesiredPos ) && mvControlling->DecMainCtrl( 1 ) ) { ; } @@ -4130,6 +4023,9 @@ void TController::SetTimeControllers() void TController::CheckTimeControllers() { + // TODO: rework this method to use hint system and regardless of driver type + if( false == AIControllFlag ) { return; } + //1. Check the type of Main Brake Handle if (mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic && mvOccupied->Handle->TimeEP) { @@ -4192,50 +4088,48 @@ void TController::CheckTimeControllers() // otwieranie/zamykanie drzwi w składzie albo (tylko AI) EZT void TController::Doors( bool const Open, int const Side ) { - + // otwieranie drzwi if( true == Open ) { - // otwieranie drzwi - // otwieranie drzwi w składach wagonowych - docelowo wysyłać komendę zezwolenia na otwarcie drzwi - // tu będzie jeszcze długość peronu zaokrąglona do 10m (20m bezpieczniej, bo nie modyfikuje bitu 1) + auto const lewe = ( pVehicle->DirectionGet() > 0 ) ? 1 : 2; auto const prawe = 3 - lewe; - - if( ( true == pVehicle->MoverParameters->Doors.permit_needed ) - && ( true == AIControllFlag ) ) { - // grant door control permission if it's not automatic - // TBD: stricter requirements? - if( Side & prawe ) - pVehicle->MoverParameters->PermitDoors( side::right ); - if( Side & lewe ) - pVehicle->MoverParameters->PermitDoors( side::left ); + // grant door control permission if it's not automatic + // TBD: stricter requirements? + if( true == pVehicle->MoverParameters->Doors.permit_needed ) { + if( Side & prawe ) { + cue_action( locale::string::driver_hint_doorrightpermiton ); + } + if( Side & lewe ) { + cue_action( locale::string::driver_hint_doorleftpermiton ); + } } - + // consist-wide remote signals to open doors doors if( ( pVehicle->MoverParameters->Doors.open_control == control_t::conductor ) - || ( ( true == AIControllFlag ) - && ( ( pVehicle->MoverParameters->Doors.open_control == control_t::driver ) - || ( pVehicle->MoverParameters->Doors.open_control == control_t::mixed ) ) ) ) { - // if the door can be operated by the driver we let the user operate them unless this user is an ai - // the train conductor, if present, handles door operation also for human-driven trains - if( Side & prawe ) - pVehicle->MoverParameters->OperateDoors( side::right, true ); - if( Side & lewe ) - pVehicle->MoverParameters->OperateDoors( side::left, true ); + || ( pVehicle->MoverParameters->Doors.open_control == control_t::driver ) + // NOTE: disabled for mixed controls, leave it up to passengers to open doors by themselves +/* || ( pVehicle->MoverParameters->Doors.open_control == control_t::mixed ) */ ) { + if( Side & prawe ) { + cue_action( locale::string::driver_hint_doorrightopen ); + } + if( Side & lewe ) { + cue_action( locale::string::driver_hint_doorleftopen ); + } } } + // zamykanie drzwi else { - // zamykanie + // if the doors are already closed and locked then there's nothing to do if( ( false == doors_permit_active() ) && ( false == doors_open() ) ) { - // the doors are already closed and we don't have to revoke control permit, we can skip all hard work iDrivigFlags &= ~moveDoorOpened; return; } - if( ( AIControllFlag ) && ( doors_open() ) ) { + if( true == doors_open() ) { if( ( true == mvOccupied->Doors.has_warning ) && ( false == mvOccupied->DepartureSignal ) && ( true == TestFlag( iDrivigFlags, moveDoorOpened ) ) ) { - mvOccupied->signal_departure( true ); // załącenie bzyczka + cue_action( locale::string::driver_hint_departuresignalon ); // załącenie bzyczka fActionTime = -1.5 - 0.1 * Random( 10 ); // 1.5-2.5 second wait } } @@ -4243,44 +4137,46 @@ void TController::Doors( bool const Open, int const Side ) { if( ( false == AIControllFlag ) || ( fActionTime > -0.5 ) ) { // ai doesn't close the door until it's free to depart, but human driver has free reign to do stupid things if( true == doors_open() ) { + // consist-wide remote signals to close doors if( ( pVehicle->MoverParameters->Doors.close_control == control_t::conductor ) - || ( ( true == AIControllFlag ) ) ) { - // if the door are controlled by the driver, we let the user operate them unless this user is an ai - // the train conductor, if present, handles door operation also for human-driven trains - if( ( pVehicle->MoverParameters->Doors.close_control == control_t::driver ) - || ( pVehicle->MoverParameters->Doors.close_control == control_t::mixed ) ) { - pVehicle->MoverParameters->OperateDoors( side::right, false ); - pVehicle->MoverParameters->OperateDoors( side::left, false ); - } + || ( pVehicle->MoverParameters->Doors.close_control == control_t::driver ) + || ( pVehicle->MoverParameters->Doors.close_control == control_t::mixed ) ) { + cue_action( locale::string::driver_hint_doorrightclose ); + cue_action( locale::string::driver_hint_doorleftclose ); } } if( true == doors_permit_active() ) { - if( true == AIControllFlag ) { - pVehicle->MoverParameters->PermitDoors( side::right, false ); - pVehicle->MoverParameters->PermitDoors( side::left, false ); - } + cue_action( locale::string::driver_hint_doorrightpermitoff ); + cue_action( locale::string::driver_hint_doorleftpermitoff ); } + // if applicable close manually-operated doors in vehicles which may ignore remote signals + { + auto *vehicle = pVehicles[ end::front ]; // pojazd na czole składu + while( vehicle != nullptr ) { + // zamykanie drzwi w pojazdach - flaga zezwolenia była by lepsza + auto const ismanualdoor { + ( vehicle->MoverParameters->Doors.auto_velocity == -1.f ) + && ( ( vehicle->MoverParameters->Doors.close_control == control_t::passenger ) + || ( vehicle->MoverParameters->Doors.close_control == control_t::mixed ) ) }; - auto *vehicle = pVehicles[ 0 ]; // pojazd na czole składu - while( vehicle != nullptr ) { - // zamykanie drzwi w pojazdach - flaga zezwolenia była by lepsza - if( ( vehicle->MoverParameters->Doors.auto_velocity < 0.f ) - && ( ( vehicle->LoadExchangeTime() == 0.f ) - || ( vehicle->MoverParameters->Vel > 0.1 ) ) ) { - vehicle->MoverParameters->OperateDoors( side::right, false, range_t::local ); // w lokomotywie można by nie zamykać... - vehicle->MoverParameters->OperateDoors( side::left, false, range_t::local ); + if( ( true == ismanualdoor ) + && ( ( vehicle->LoadExchangeTime() == 0.f ) + || ( vehicle->MoverParameters->Vel > 0.1 ) ) ) { + vehicle->MoverParameters->OperateDoors( side::right, false, range_t::local ); + vehicle->MoverParameters->OperateDoors( side::left, false, range_t::local ); + } + vehicle = vehicle->Next(); // pojazd podłączony z tyłu (patrząc od czoła) } - vehicle = vehicle->Next(); // pojazd podłączony z tyłu (patrząc od czoła) } fActionTime = -2.0 - 0.1 * Random( 15 ); // 2.0-3.5 sec wait, +potentially 0.5 remaining iDrivigFlags &= ~moveDoorOpened; // zostały zamknięte - nie wykonywać drugi raz - if( Random( 100 ) < Random( 100 ) ) { + if( ( true == mvOccupied->DepartureSignal ) + && ( Random( 100 ) < Random( 100 ) ) ) { // potentially turn off the warning before actual departure - // TBD, TODO: dedicated buzzer duration counter - mvOccupied->signal_departure( false ); + // TBD, TODO: dedicated buzzer duration counter? + cue_action( locale::string::driver_hint_departuresignaloff ); } - } } } @@ -4532,7 +4428,6 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N CheckVehicles(); // zabrać to do OrderCheck() } // dla prędkości ujemnej przestawić nawrotnik do tyłu? ale -1=brak ograniczenia !!!! - // if (iDrivigFlags&movePress) WriteLog("Skasowano docisk w ShuntVelocity!"); iDrivigFlags &= ~movePress; // bez dociskania SetVelocity(NewValue1, NewValue2, reason); if (NewValue1 != 0.0) @@ -4583,7 +4478,9 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N -1; // do przodu, gdy iloczyn skalarny i prędkość dodatnie // iDirectionOrder=1; else if (NewValue1<0.0) iDirectionOrder=-1; } - if (iDirectionOrder != iDirection) + if ( + + iDirectionOrder != iDirection) OrderNext(Change_direction); // zadanie komendy do wykonania if (o >= Shunt) // jeśli jazda manewrowa albo pociągowa OrderNext(o); // to samo robić po zmianie @@ -4629,11 +4526,12 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N // x,-y - podłączyć się do składu (sprzęgiem y>=1), a następnie odczepić i zabrać (x) wagonów // 1, 0 - odczepienie lokomotywy z jednym wagonem iDrivigFlags &= ~moveStopHere; // podjeżanie do semaforów zezwolone - if (false == iEngineActive) - OrderNext(Prepare_engine); // trzeba odpalić silnik najpierw + if( false == iEngineActive ) { + OrderNext( Prepare_engine ); // trzeba odpalić silnik najpierw + } if (NewValue2 != 0) // jeśli podany jest sprzęg { - iCoupler = floor(fabs(NewValue2)); // jakim sprzęgiem + iCoupler = std::floor( std::abs( NewValue2 ) ); // jakim sprzęgiem OrderNext(Connect); // najpierw połącz pojazdy if (NewValue1 >= 0.0) // jeśli ilość wagonów inna niż wszystkie { // to po podpięciu należy się odczepić @@ -4668,32 +4566,32 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N else if( mvOccupied->Couplers[ mvOccupied->DirAbsolute > 0 ? end::rear : end::front ].Connected != nullptr ) { // z tyłu coś OrderNext( Disconnect ); // jak ciągnie, to tylko odczep (NewValue1) wagonów } + else { + // both couplers empty + // NOTE: this condition implements legacy behavior (mis)used by some scenarios to keep vehicle idling without moving + SetVelocity( 0, 0, stopJoin ); + iDrivigFlags |= moveStopHere; // nie podjeżdżać do semafora, jeśli droga nie jest wolna + } WaitingTime = 0.0; // nie ma co dalej czekać, można zatrąbić i jechać, chyba że już jedzie } } - if (NewValue1 == -1.0) - { + if (NewValue1 == -1.0) { iDrivigFlags &= ~moveStopHere; // ma jechać WaitingTime = 0.0; // nie ma co dalej czekać, można zatrąbić i jechać } - if (NewValue1 < -1.5) // jeśli -2/-3, czyli czekanie z ruszeniem na sygnał + if( NewValue1 < -1.5 ) { // jeśli -2/-3, czyli czekanie z ruszeniem na sygnał iDrivigFlags |= moveStopHere; // nie podjeżdżać do semafora, jeśli droga nie jest wolna + } // (nie dotyczy Connect) - if (NewValue1 < -2.5) // jeśli - OrderNext(( NewCommand == "Shunt" ? Obey_train : Bank )); // to potem jazda pociągowa - else - OrderNext(( NewCommand == "Shunt" ? Shunt : Loose_shunt )); // to potem manewruj dalej + if( NewValue1 < -2.5 ) { // jeśli -3 to potem jazda pociągowa + OrderNext( ( NewCommand == "Shunt" ? Obey_train : Bank ) ); + } + else { // otherwise continue shunting + OrderNext( ( NewCommand == "Shunt" ? Shunt : Loose_shunt ) ); + } CheckVehicles(); // sprawdzić światła - // if ((iVehicleCount>=0)&&(NewValue1<0)) WriteLog("Skasowano ilosć wagonów w Shunt!"); - if (NewValue1 != iVehicleCount) - iVehicleCount = floor(NewValue1); // i co potem ? - trzeba zaprogramowac odczepianie - /* - if (NewValue1!=-1.0) - if (NewValue2!=0.0) + iVehicleCount = std::floor( NewValue1 ); - if (VelDesired==0) - SetVelocity(20,0); //to niech jedzie - */ return true; } @@ -4723,12 +4621,9 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N if (NewCommand == "Warning_signal") { - if( AIControllFlag ) { - // poniższa komenda nie jest wykonywana przez użytkownika - if( NewValue1 > 0 ) { - fWarningDuration = NewValue1; // czas trąbienia - mvOccupied->WarningSignal = NewValue2; // horn combination flag - } + if( ( NewValue1 > 0 ) && ( NewValue2 > 0 ) ) { + fWarningDuration = NewValue1; // czas trąbienia + cue_action( locale::string::driver_hint_hornon, NewValue2 ); // horn combination flag } return true; } @@ -4817,2053 +4712,73 @@ void TController::PhysicsLog() }; void -TController::UpdateSituation(double dt) { +TController::Update( double const Timedelta ) { // uruchamiać przynajmniej raz na sekundę - if( false == simulation::is_ready ) { return; } if( ( iDrivigFlags & movePrimary ) == 0 ) { return; } // pasywny nic nie robi + if( false == simulation::is_ready ) { return; } - // update timers - ElapsedTime += dt; - WaitingTime += dt; - fBrakeTime -= dt; // wpisana wartość jest zmniejszana do 0, gdy ujemna należy zmienić nastawę hamulca - if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_FS ) ) { - // brake charging timeout starts after charging ends - BrakeChargingCooldown += dt; - } - fStopTime += dt; // zliczanie czasu postoju, nie ruszy dopóki ujemne - fActionTime += dt; // czas używany przy regulacji prędkości i zamykaniu drzwi - LastReactionTime += dt; - LastUpdatedTime += dt; - if( ( mvOccupied->Vel < 0.05 ) - && ( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) != 0 ) ) { - IdleTime += dt; - } - else { - IdleTime = 0.0; - } - // HACK: activate route scanning if an idling vehicle is activated by a human user - if( ( OrderCurrentGet() == Wait_for_orders ) - && ( false == AIControllFlag ) - && ( false == iEngineActive ) - && ( true == mvOccupied->Power24vIsAvailable ) ) { - OrderNext( Prepare_engine ); - } + update_timers( Timedelta ); + update_logs( Timedelta ); - // log vehicle data - if( ( WriteLogFlag ) - && ( LastUpdatedTime > deltalog ) ) { - // zapis do pliku DAT - PhysicsLog(); - LastUpdatedTime -= deltalog; - } + auto const reactiontime { std::min( ReactionTime, 2.0 ) }; - if( ( fLastStopExpDist > 0.0 ) - && ( mvOccupied->DistCounter > fLastStopExpDist ) ) { - // zaktualizować wyświetlanie rozkładu - iStationStart = TrainParams.StationIndex; - fLastStopExpDist = -1.0; // usunąć licznik - if( true == m_makenextstopannouncement ) { - announce( announcement_t::next ); - m_makenextstopannouncement = false; // keep next stop announcements suppressed until another scheduled stop - } - } - - // ABu-160305 testowanie gotowości do jazdy - // Ra: przeniesione z DynObj, skład użytkownika też jest testowany, żeby mu przekazać, że ma odhamować - int index = double(BrakeAccTableSize) * (mvOccupied->Vel / mvOccupied->Vmax); - index = std::min(BrakeAccTableSize, std::max(1, index)); - fBrake_a0[0] = fBrake_a0[index]; - fBrake_a1[0] = fBrake_a1[index]; - - if ((mvOccupied->TrainType == dt_EZT) || (mvOccupied->TrainType == dt_DMU)) { - auto Coeff = clamp( mvOccupied->Vel*0.015 , 0.5 , 1.0); - fAccThreshold = fNominalAccThreshold * Coeff - fBrake_a0[BrakeAccTableSize] * (1.0 - Coeff); - } - - Ready = true; // wstępnie gotowy - fReady = 0.0; // założenie, że odhamowany - fAccGravity = 0.0; // przyspieszenie wynikające z pochylenia - double dy; // składowa styczna grawitacji, w przedziale <0,1> - double AbsAccS = 0; - IsAnyCouplerStretched = false; - IsAnyDoorOpen[ side::right ] = IsAnyDoorOpen[ side::left ] = false; - IsAnyDoorPermitActive[ side::right ] = IsAnyDoorPermitActive[ side::left ] = false; - ConsistShade = 0.0; - TDynamicObject *p = pVehicles[0]; // pojazd na czole składu - while (p) - { // sprawdzenie odhamowania wszystkich połączonych pojazdów - auto *vehicle { p->MoverParameters }; - double bp = vehicle->BrakePress - (vehicle->SpeedCtrlUnit.Parking ? vehicle->MaxBrakePress[0] * vehicle->StopBrakeDecc : 0.0); - if (bp < 0) bp = 0; - if (Ready) { - // bo jak coś nie odhamowane, to dalej nie ma co sprawdzać - if( bp >= ( - vehicle->EngineType == TEngineType::ElectricSeriesMotor ? 0.4 : // motor relay activation threshold - mvOccupied->Vel > 5.0 ? 0.5 : // a bit of leeway if already in motion - 0.4 ) ) { - Ready = false; - } - if (bp >= 0.4) // wg UIC określone sztywno na 0.04 - { - // Ra: odluźnianie przeładowanych lokomotyw, ciągniętych na zimno - prowizorka... - if (AIControllFlag) // skład jak dotąd był wyluzowany - { - if( ( /*GBH mvOccupied->BrakeCtrlPos*/BrakeCtrlPosition == 0 ) // jest pozycja jazdy - && ( ( vehicle->Hamulec->GetBrakeStatus() & b_dmg ) == 0 ) // brake isn't broken - && ( vehicle->PipePress - 5.0 > -0.1 ) // jeśli ciśnienie jak dla jazdy - && ( vehicle->Hamulec->GetCRP() > vehicle->PipePress + 0.12 ) ) { // za dużo w zbiorniku - // indywidualne luzowanko - vehicle->BrakeReleaser( 1 ); - } - } - } - } - if (fReady < bp) - fReady = bp; // szukanie najbardziej zahamowanego - if( ( dy = p->VectorFront().y ) != 0.0 ) { - // istotne tylko dla pojazdów na pochyleniu - // ciężar razy składowa styczna grawitacji - fAccGravity -= vehicle->TotalMassxg * dy * ( p->DirectionGet() == iDirection ? 1 : -1 ); - } - // check coupler state - IsAnyCouplerStretched = - IsAnyCouplerStretched - || ( vehicle->Couplers[ end::front ].stretch_duration > 0.0 ) - || ( vehicle->Couplers[ end::rear ].stretch_duration > 0.0 ); - // check door state - { - auto const switchsides { p->DirectionGet() != iDirection }; - auto const &rightdoor { vehicle->Doors.instances[ ( switchsides ? side::left : side::right ) ] }; - auto const &leftdoor { vehicle->Doors.instances[ ( switchsides ? side::right : side::left ) ] }; - if( vehicle->Doors.close_control != control_t::autonomous ) { - IsAnyDoorOpen[ side::right ] |= ( false == rightdoor.is_closed ); - IsAnyDoorOpen[ side::left ] |= ( false == leftdoor.is_closed ); - } - if( vehicle->Doors.permit_needed ) { - IsAnyDoorPermitActive[ side::right ] |= rightdoor.open_permit; - IsAnyDoorPermitActive[ side::left ] |= leftdoor.open_permit; - } - } - // measure lighting level - // TBD: apply weight (multiplier) to partially lit vehicles? - ConsistShade += ( p->fShade > 0.0 ? p->fShade : 1.0 ); - p = p->Next(); // pojazd podłączony z tyłu (patrząc od czoła) - } - // calculate average amount of received sunlight - ConsistShade /= iVehicles; - - // test state of main switch in all powered vehicles under control - IsAnyConverterOverloadRelayOpen = mvOccupied->ConvOvldFlag; - IsAnyMotorOverloadRelayOpen = mvOccupied->FuseFlag; - IsAnyGroundRelayOpen = !mvOccupied->GroundRelay; - IsAnyLineBreakerOpen = ( mvOccupied->Power > 0.01 ? !mvOccupied->Mains : false ); - IsAnyCompressorEnabled = mvOccupied->CompressorAllow; - p = pVehicle; - while( ( p = p->PrevC( coupling::control) ) != nullptr ) { - auto const *vehicle { p->MoverParameters }; - IsAnyConverterOverloadRelayOpen |= vehicle->ConvOvldFlag; - IsAnyMotorOverloadRelayOpen |= vehicle->FuseFlag; - IsAnyGroundRelayOpen |= !vehicle->GroundRelay; - IsAnyCompressorEnabled |= vehicle->CompressorAllow; - if( vehicle->Power > 0.01 ) { - IsAnyLineBreakerOpen |= !vehicle->Mains; - } - } - p = pVehicle; - while( ( p = p->NextC( coupling::control ) ) != nullptr ) { - auto const *vehicle { p->MoverParameters }; - IsAnyConverterOverloadRelayOpen |= vehicle->ConvOvldFlag; - IsAnyMotorOverloadRelayOpen |= vehicle->FuseFlag; - IsAnyGroundRelayOpen |= !vehicle->GroundRelay; - IsAnyCompressorEnabled |= vehicle->CompressorAllow; - if( vehicle->Power > 0.01 ) { - IsAnyLineBreakerOpen |= !vehicle->Mains; - } - } - - if( iDirection ) { - // siłę generują pojazdy na pochyleniu ale działa ona całość składu, więc a=F/m - fAccGravity *= iDirection; - fAccGravity /= fMass; - } - if (!Ready) // v367: jeśli wg powyższych warunków skład nie jest odhamowany - if (fAccGravity < -0.05) // jeśli ma pod górę na tyle, by się stoczyć - // if (mvOccupied->BrakePress<0.08) //to wystarczy, że zadziałają liniowe (nie ma ich jeszcze!!!) - if (fReady < 0.8) // delikatniejszy warunek, obejmuje wszystkie wagony - Ready = true; //żeby uznać za odhamowany - // second pass, for diesel engines verify the (live) engines are fully started - // TODO: cache presence of diesel engines in the consist, to skip this test if there isn't any - p = pVehicles[ 0 ]; // pojazd na czole składu - while( ( true == Ready ) - && ( p != nullptr ) ) { - - auto const *vehicle { p->MoverParameters }; - - if( ( vehicle->EngineType == TEngineType::DieselEngine ) - || ( vehicle->EngineType == TEngineType::DieselElectric ) ) { - - Ready = ( - ( vehicle->Vel > 0.5 ) // already moving - || ( false == vehicle->Mains ) // deadweight vehicle - || ( vehicle->enrot > 0.8 * ( - vehicle->EngineType == TEngineType::DieselEngine ? - vehicle->dizel_nmin : - vehicle->DElist[ 0 ].RPM / 60.0 ) ) ); - } - p = p->Next(); // pojazd podłączony z tyłu (patrząc od czoła) - } - - // HACK: crude way to deal with automatic door opening on W4 preventing further ride - // for human-controlled vehicles with no door control and dynamic brake auto-activating with door open - // TODO: check if this situation still happens and the hack is still needed - if( ( false == AIControllFlag ) -// && ( iDrivigFlags & moveDoorOpened ) -// && ( mvOccupied->Doors.close_control != control_t::driver ) - && ( doors_open() ) ) { - // for diesel engines react when engine is put past idle revolutions - // for others straightforward master controller check - if( ( mvControlling->EngineType == TEngineType::DieselEngine ? - mvControlling->RList[ mvControlling->MainCtrlPos ].Mn > 0 : - mvControlling->MainCtrlPowerPos() > 0 ) ) { - Doors( false ); - } - } - - // basic situational ai operations - // TBD, TODO: move these to main routine, if it's not neccessary for them to fire every time? - if( AIControllFlag ) { - - // wheel slip - if( mvControlling->SlippingWheels ) { - mvControlling->Sandbox( true ); // piasku! - } - else { - // deactivate sandbox if we aren't slipping - if( mvControlling->SandDose ) { - mvControlling->Sandbox( false ); - } - } - - if (mvControlling->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) { - - if( mvOccupied->ScndPipePress > 4.3 ) { - // gdy główna sprężarka bezpiecznie nabije ciśnienie to można przestawić kurek na zasilanie pantografów z głównej pneumatyki - mvControlling->bPantKurek3 = true; - } - - // uśrednione napięcie sieci: przy spadku poniżej wartości minimalnej opóźnić rozruch o losowy czas - fVoltage = 0.5 * (fVoltage + std::max( mvControlling->GetTrainsetHighVoltage(), mvControlling->PantographVoltage ) ); - if( fVoltage < mvControlling->EnginePowerSource.CollectorParameters.MinV ) { - // gdy rozłączenie WS z powodu niskiego napięcia - if( fActionTime >= PrepareTime ) { - // jeśli czas oczekiwania nie został ustawiony, losowy czas oczekiwania przed ponownym załączeniem jazdy - fActionTime = -2.0 - Random( 10 ); - } - } - } - - if( mvOccupied->Vel > 1.0 ) { - // jeżeli jedzie - if( doors_open() ) { - // jeśli drzwi otwarte - // nie zamykać drzwi przy drganiach, bo zatrzymanie na W4 akceptuje niewielkie prędkości - Doors( false ); - } -/* - // NOTE: this section moved all cars to the edge of their respective roads - // it may have some use eventually for collision avoidance, - // but when enabled all the time it produces silly effect - // przy prowadzeniu samochodu trzeba każdą oś odsuwać oddzielnie, inaczej kicha wychodzi - if (mvOccupied->CategoryFlag & 2) // jeśli samochód - // if (fabs(mvOccupied->OffsetTrackH)Dim.W) //Ra: szerokość drogi tu powinna być? - if (!mvOccupied->ChangeOffsetH(-0.01 * mvOccupied->Vel * dt)) // ruch w poprzek drogi - mvOccupied->ChangeOffsetH(0.01 * mvOccupied->Vel * dt); // Ra: co to miało być, to nie wiem -*/ - } - - if (mvControlling->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) { - - auto const useregularpantographlayout { - ( pVehicle->NextC( coupling::control ) == nullptr ) // standalone - || ( mvControlling->TrainType == dt_EZT ) // special case - || ( mvControlling->TrainType == dt_ET41 ) }; // special case - - if( mvOccupied->Vel > 0.05 ) { - // is moving - if( ( fOverhead2 >= 0.0 ) || iOverheadZero ) { - // jeśli jazda bezprądowa albo z opuszczonym pantografem - ZeroSpeed(); - } - - if( ( fOverhead2 > 0.0 ) || iOverheadDown ) { - // jazda z opuszczonymi pantografami - if( mvPantographUnit->Pantographs[ end::front ].is_active ) { - mvOccupied->OperatePantographValve( end::front, operation_t::disable ); - } - if( mvPantographUnit->Pantographs[ end::rear ].is_active ) { - mvOccupied->OperatePantographValve( end::rear, operation_t::disable ); - } - } - else { - // jeśli nie trzeba opuszczać pantografów - if( fActionTime > 0.0 ) { - if( mvOccupied->AIHintPantstate == 0 ) { - // jazda na tylnym - if( ( iDirection >= 0 ) && ( useregularpantographlayout ) ) { - // jak jedzie w kierunku sprzęgu 0 - if( ( mvPantographUnit->PantRearVolt == 0.0 ) - // filter out cases with single _other_ working pantograph so we don't try to raise something we can't - && ( ( mvPantographUnit->PantographVoltage == 0.0 ) - || ( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) ) ) { - mvOccupied->OperatePantographValve( end::rear, operation_t::enable ); - } - } - else { - // jak jedzie w kierunku sprzęgu 0 - if( ( mvPantographUnit->PantFrontVolt == 0.0 ) - // filter out cases with single _other_ working pantograph so we don't try to raise something we can't - && ( ( mvPantographUnit->PantographVoltage == 0.0 ) - || ( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) ) ) { - mvOccupied->OperatePantographValve( end::front, operation_t::enable ); - } - } - - if( mvOccupied->Vel > 5 ) { - // opuszczenie przedniego po rozpędzeniu się o ile jest więcej niż jeden - if( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) { - if( ( iDirection >= 0 ) && ( useregularpantographlayout ) ) // jak jedzie w kierunku sprzęgu 0 - { // poczekać na podniesienie tylnego - if( ( mvPantographUnit->PantFrontVolt != 0.0 ) - && ( mvPantographUnit->PantRearVolt != 0.0 ) ) { // czy jest napięcie zasilające na tylnym? - mvOccupied->OperatePantographValve( end::front, operation_t::disable ); // opuszcza od sprzęgu 0 - } - } - else { // poczekać na podniesienie przedniego - if( ( mvPantographUnit->PantRearVolt != 0.0 ) - && ( mvPantographUnit->PantFrontVolt != 0.0 ) ) { // czy jest napięcie zasilające na przednim? - mvOccupied->OperatePantographValve( end::rear, operation_t::disable ); // opuszcza od sprzęgu 1 - } - } - } - } - - } - else { - // use suggested pantograph setup - if( mvOccupied->Vel > 5 ) { - auto const pantographsetup{ mvOccupied->AIHintPantstate }; - mvOccupied->OperatePantographValve( - end::front, - ( pantographsetup & ( 1 << 0 ) ? - operation_t::enable : - operation_t::disable ) ); - mvOccupied->OperatePantographValve( - end::rear, - ( pantographsetup & ( 1 << 1 ) ? - operation_t::enable : - operation_t::disable ) ); - } - } - } - } - - // TODO: refactor this calculation into a subroutine - auto const useseriesmodevoltage { - interpolate( - mvControlling->EnginePowerSource.CollectorParameters.MinV, - mvControlling->EnginePowerSource.CollectorParameters.MaxV, - ( IsHeavyCargoTrain ? 0.35 : 0.40 ) ) }; - - if( fVoltage <= useseriesmodevoltage ) { - // if the power station is heavily burdened try to reduce the load - switch( mvControlling->EngineType ) { - - case TEngineType::ElectricSeriesMotor: { - if( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) { - // limit yourself to series mode - if( mvControlling->ScndCtrlPos ) { - mvControlling->DecScndCtrl( 2 ); - } - while( ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) - && ( mvControlling->DecMainCtrl( 1 ) ) ) { - ; // all work is performed in the header - } - } - break; - } - default: { - break; - } - } - } - } - else { - if( ( mvOccupied->AIHintPantUpIfIdle ) - && ( IdleTime > 45.0 ) - // NOTE: abs(stoptime) covers either at least 15 sec remaining for a scheduled stop, or 15+ secs spent at a basic stop - && ( std::abs( fStopTime ) > 15.0 ) ) { - // spending a longer at a stop, raise also front pantograph - if( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) { - if( ( iDirection >= 0 ) && ( useregularpantographlayout ) ) { - // jak jedzie w kierunku sprzęgu 0 - if( mvPantographUnit->PantFrontVolt == 0.0 ) { - mvOccupied->OperatePantographValve( end::front, operation_t::enable ); - } - } - else { - if( mvPantographUnit->PantRearVolt == 0.0 ) { - mvOccupied->OperatePantographValve( end::rear, operation_t::enable ); - } - } - } - } - } - } - - // horn control - if( fWarningDuration > 0.0 ) { - // jeśli pozostało coś do wytrąbienia trąbienie trwa nadal - fWarningDuration -= dt; - if( fWarningDuration < 0.05 ) - mvOccupied->WarningSignal = 0; // a tu się kończy - } - if( mvOccupied->Vel >= 5.0 ) { - // jesli jedzie, można odblokować trąbienie, bo się wtedy nie włączy - iDrivigFlags &= ~moveStartHornDone; // zatrąbi dopiero jak następnym razem stanie - iDrivigFlags |= moveStartHorn; // i trąbić przed następnym ruszeniem - } - - if( ( true == TestFlag( iDrivigFlags, moveStartHornNow ) ) - && ( true == Ready ) - && ( true == iEngineActive ) - && ( mvControlling->MainCtrlPowerPos() > 0 ) ) { - // uruchomienie trąbienia przed ruszeniem - fWarningDuration = 0.3; // czas trąbienia - mvOccupied->WarningSignal = pVehicle->iHornWarning; // wysokość tonu (2=wysoki) - iDrivigFlags |= moveStartHornDone; // nie trąbić aż do ruszenia - iDrivigFlags &= ~moveStartHornNow; // trąbienie zostało zorganizowane - } - } - - // main ai update routine - auto const reactiontime = std::min( ReactionTime, 2.0 ); if( LastReactionTime < reactiontime ) { return; } - - if (AIControllFlag) { - CheckTimeControllers(); - - if( fActionTime > 0.0 ) { - // low priority operations - // compartment lights - if( mvOccupied->CompartmentLights.start_type == start_t::manual ) { - auto const currentlightstate{ mvOccupied->CompartmentLights.is_enabled }; - auto const lightlevel{ Global.fLuminance * ConsistShade }; - auto const desiredlightstate{ ( - currentlightstate ? - lightlevel < 0.40 : // turn off if lighting level goes above 0.4 - lightlevel < 0.35 ) }; // turn on if lighting level goes below 0.35 - if( desiredlightstate != currentlightstate ) { - mvOccupied->CompartmentLightsSwitch( desiredlightstate ); - mvOccupied->CompartmentLightsSwitchOff( !desiredlightstate ); - } - } - } - } - LastReactionTime -= reactiontime; - // Ra: nie wiem czemu ReactionTime potrafi dostać 12 sekund, to jest przegięcie, bo przeżyna STÓJ - // yB: otóż jest to jedna trzecia czasu napełniania na towarowym; może się przydać przy - // wdrażaniu hamowania, żeby nie ruszało kranem jak głupie - // Ra: ale nie może się budzić co pół minuty, bo przeżyna semafory - // Ra: trzeba by tak: - // 1. Ustalić istotną odległość zainteresowania (np. 3×droga hamowania z V.max). - // dla hamowania -0.2 [m/ss] droga wynosi 0.389*Vel*Vel [km/h], czyli 600m dla 40km/h, 3.8km - // dla 100km/h i 9.8km dla 160km/h - // dla hamowania -0.4 [m/ss] droga wynosi 0.096*Vel*Vel [km/h], czyli 150m dla 40km/h, 1.0km - // dla 100km/h i 2.5km dla 160km/h - // ogólnie droga hamowania przy stałym opóźnieniu to Vel*Vel/(3.6*3.6*a) [m] - // fBrakeDist powinno być wyznaczane dla danego składu za pomocą sieci neuronowych, w - // zależności od prędkości i siły (ciśnienia) hamowania - // następnie w drugą stronę, z drogi hamowania i chwilowej prędkości powinno być wyznaczane zalecane ciśnienie - - // przybliżona droga hamowania - // NOTE: we're ensuring some minimal braking distance to reduce ai flipping states between starting and braking - auto const velceil { std::max( 2.0, std::ceil( mvOccupied->Vel ) ) }; - fBrakeDist = fDriverBraking * velceil * ( 40.0 + velceil ); - if( fMass > 1000000.0 ) { - // korekta dla ciężkich, bo przeżynają - da to coś? - fBrakeDist *= 2.0; - } - if( ( -fAccThreshold > 0.05 ) - && ( mvOccupied->CategoryFlag == 1 ) ) { - fBrakeDist = velceil * velceil / 25.92 / -fAccThreshold; - } - if( mvOccupied->BrakeDelayFlag == bdelay_G ) { - // dla nastawienia G koniecznie należy wydłużyć drogę na czas reakcji - fBrakeDist += 2 * velceil; - } - if( ( mvOccupied->Vel > 15.0 ) - && ( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) - && ( ( mvControlling->TrainType & dt_EZT ) != 0 ) ) { - // HACK: make the induction motor powered EMUs start braking slightly earlier - fBrakeDist += 10.0; - } /* - // take into account effect of gravity (but to stay on safe side of calculations, only downhill) - if( fAccGravity > 0.025 ) { - fBrakeDist *= ( 1.0 + fAccGravity ); - // TBD: use version which shortens route going uphill, too - //fBrakeDist = std::max( fBrakeDist, fBrakeDist * ( 1.0 + fAccGravity ) ); + // TBD, TODO: put this in an appropriate place, or get rid of it + // NOTE: this section moved all cars to the edge of their respective roads + // it may have some use eventually for collision avoidance, + // but when enabled all the time it produces silly effect + if( mvOccupied->Vel > 1.0 ) { + // przy prowadzeniu samochodu trzeba każdą oś odsuwać oddzielnie, inaczej kicha wychodzi + if (mvOccupied->CategoryFlag & 2) // jeśli samochód + // if (fabs(mvOccupied->OffsetTrackH)Dim.W) //Ra: szerokość drogi tu powinna być? + if (!mvOccupied->ChangeOffsetH(-0.01 * mvOccupied->Vel * dt)) // ruch w poprzek drogi + mvOccupied->ChangeOffsetH(0.01 * mvOccupied->Vel * dt); // Ra: co to miało być, to nie wiem } */ - // route scan - auto const routescanrange { + update_hints(); + // main ai update routine + determine_consist_state(); + determine_braking_distance(); + determine_proximity_ranges(); + // vicinity check + auto const awarenessrange { std::max( 750.0, mvOccupied->Vel > 5.0 ? 400 + fBrakeDist : 30.0 * fDriverDist ) }; // 1500m dla stojących pociągów; - // Ra 2015-01: przy dłuższej drodze skanowania AI jeździ spokojniej - // 2. Sprawdzić, czy tabelka pokrywa założony odcinek (nie musi, jeśli jest STOP). - // 3. Sprawdzić, czy trajektoria ruchu przechodzi przez zwrotnice - jeśli tak, to sprawdzić, - // czy stan się nie zmienił. - // 4. Ewentualnie uzupełnić tabelkę informacjami o sygnałach i ograniczeniach, jeśli się - // "zużyła". - TableCheck( routescanrange ); // wypełnianie tabelki i aktualizacja odległości - // 5. Sprawdzić stany sygnalizacji zapisanej w tabelce, wyznaczyć prędkości. - // 6. Z tabelki wyznaczyć krytyczną odległość i prędkość (najmniejsze przyspieszenie). - // 7. Jeśli jest inny pojazd z przodu, ewentualnie skorygować odległość i prędkość. - // 8. Ustalić częstotliwość świadomości AI (zatrzymanie precyzyjne - częściej, brak atrakcji - // - rzadziej). - - // check for potential collisions - { - // HACK: vehicle order in the consist is based on intended travel direction - // if our actual travel direction doesn't match that, we should be scanning from the other end of the consist - // we cast to int to avoid getting confused by microstutters - auto *frontvehicle { pVehicles[ ( static_cast( mvOccupied->V ) * iDirection >= 0 ? end::front : end::rear ) ] }; - - int routescandirection; - // for moving vehicle determine heading from velocity; for standing fall back on the set direction - if( ( std::abs( frontvehicle->MoverParameters->V ) > 0.5 ? // ignore potential micro-stutters in oposite direction during "almost stop" - frontvehicle->MoverParameters->V > 0.0 : - ( pVehicle->DirectionGet() == frontvehicle->DirectionGet() ? - iDirection > 0 : - iDirection < 0 ) ) ) { - // towards coupler 0 - routescandirection = end::front; - } - else { - // towards coupler 1 - routescandirection = end::rear; - } -/* - if( pVehicle->MoverParameters->CabOccupied < 0 ) { - // flip the scan direction in the rear cab - routescandirection ^= routescandirection; - } -*/ - Obstacle = neighbour_data(); - auto const obstaclescanrange { std::max( ( mvOccupied->CategoryFlag == 2 ? 250.0 : 1000.0 ), routescanrange ) }; - auto const lookup { frontvehicle->find_vehicle( routescandirection, obstaclescanrange ) }; - - if( std::get( lookup ) == true ) { - - Obstacle.vehicle = std::get( lookup ); - Obstacle.vehicle_end = std::get( lookup ); - Obstacle.distance = std::get( lookup ); - - if( Obstacle.distance < ( mvOccupied->CategoryFlag == 2 ? 50 : 100 ) ) { - // at short distances (re)calculate range between couplers directly - Obstacle.distance = TMoverParameters::CouplerDist( frontvehicle->MoverParameters, Obstacle.vehicle->MoverParameters ); - } - } + scan_route( awarenessrange ); + scan_obstacles( awarenessrange ); + // generic actions + control_wheelslip(); + control_security_system( reactiontime ); + control_horns( reactiontime ); + control_pantographs(); + CheckTimeControllers(); + if( fActionTime > 0.0 ) { + // potentially delayed and/or low priority actions + control_lights(); + control_doors(); + control_compartment_lights(); } - // tu bedzie logika sterowania - if (AIControllFlag) { - - if (mvOccupied->CommandIn.Command != "") - if( !mvOccupied->RunInternalCommand() ) { - // rozpoznaj komende bo lokomotywa jej nie rozpoznaje - RecognizeCommand(); // samo czyta komendę wstawioną do pojazdu? - } - if( mvOccupied->SecuritySystem.Status != s_off ) { - // jak zadziałało CA/SHP - if( !mvOccupied->SecuritySystemReset() ) { // to skasuj - if( ( /*mvOccupied->BrakeCtrlPos*/BrakeCtrlPosition == 0 ) - && ( AccDesired > 0.0 ) - && ( ( TestFlag( mvOccupied->SecuritySystem.Status, s_SHPebrake ) ) - || ( TestFlag( mvOccupied->SecuritySystem.Status, s_CAebrake ) ) ) ) { - //!!! hm, może po prostu normalnie sterować hamulcem? - //mvOccupied->BrakeLevelSet( 0 ); GBH - BrakeLevelSet(gbh_RP); - } - } - } - // basic emergency stop handling, while at it - if( ( true == mvOccupied->RadioStopFlag ) // radio-stop - && ( mvOccupied->Vel == 0.0 ) // and actual stop - && ( true == mvOccupied->Radio ) ) { // and we didn't touch the radio yet - // turning off the radio should reset the flag, during security system check - if( m_radiocontroltime > 5.0 ) { - // arbitrary delay between stop and disabling the radio - mvOccupied->Radio = false; - m_radiocontroltime = 0.0; - } - else { - m_radiocontroltime += reactiontime; - } - } - if( ( false == mvOccupied->Radio ) - && ( false == mvOccupied->RadioStopFlag ) ) { - // otherwise if it's safe to do so, turn the radio back on - if( m_radiocontroltime > 10.0 ) { - // arbitrary 5 sec delay before switching radio back on - mvOccupied->Radio = true; - m_radiocontroltime = 0.0; - } - else { - m_radiocontroltime += reactiontime; - } - } - } - - switch (OrderCurrentGet()) - { // ustalenie prędkości przy doczepianiu i odczepianiu, dystansów w pozostałych przypadkach - case Connect: { - // podłączanie do składu - if (iDrivigFlags & moveConnect) { - // jeśli stanął już blisko, unikając zderzenia i można próbować podłączyć - fMinProximityDist = -1.0; - fMaxProximityDist = 0.0; //[m] dojechać maksymalnie - fVelPlus = 1.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania - fVelMinus = 0.5; // margines prędkości powodujący załączenie napędu - if (AIControllFlag) - { // to robi tylko AI, wersję dla człowieka trzeba dopiero zrobić - // sprzęgi sprawdzamy w pierwszej kolejności, bo jak połączony, to koniec - auto *vehicle { pVehicles[ end::front ] }; - auto *vehicleparameters { vehicle->MoverParameters }; - int const end { ( vehicle->DirectionGet() > 0 ? end::front : end::rear ) }; - auto const &neighbour { vehicleparameters->Neighbours[ end ] }; - if( neighbour.vehicle != nullptr ) { - if( neighbour.distance < 10 ) { - // check whether we don't need to attach coupler adapter - auto &coupler{ vehicleparameters->Couplers[ end ] }; - if( coupler.type() != TCouplerType::Automatic ) { - auto &othercoupler = neighbour.vehicle->MoverParameters->Couplers[ ( neighbour.vehicle_end != 2 ? neighbour.vehicle_end : coupler.ConnectedNr ) ]; - if( othercoupler.type() == TCouplerType::Automatic ) { - vehicle->attach_coupler_adapter( end ); - } - } - if( neighbour.distance < 2 ) { - // próba podczepienia - vehicleparameters->Attach( - end, neighbour.vehicle_end, - neighbour.vehicle->MoverParameters, - iCoupler ); - if( vehicleparameters->Couplers[ end ].CouplingFlag == iCoupler ) { - // jeżeli został podłączony - CheckVehicles( Connect ); // sprawdzić światła nowego składu - } - } - } - } - } // if (AIControllFlag) //koniec zblokowania, bo była zmienna lokalna - } - else { - // jak daleko, to jazda jak dla Shunt na kolizję - fMinProximityDist = 2.0; - fMaxProximityDist = 5.0; //[m] w takim przedziale odległości powinien stanąć - fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania - fVelMinus = 1.0; // margines prędkości powodujący załączenie napędu - if( Obstacle.distance <= 20.0 ) { - // przy zderzeniu fTrackBlock nie jest miarodajne - // początek podczepiania, z wyłączeniem sprawdzania fTrackBlock - iDrivigFlags |= moveConnect; - } - } - break; - } - case Disconnect: { - // 20.07.03 - manewrowanie wagonami - fMinProximityDist = 1.0; - fMaxProximityDist = 10.0; //[m] - fVelPlus = 1.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania - fVelMinus = 0.5; // margines prędkości powodujący załączenie napędu - break; - } - case Shunt: { - // na jaką odleglość i z jaką predkością ma podjechać - // TODO: test if we can use the distances calculation from obey_train - fMinProximityDist = std::min( 5 + iVehicles, 25 ); - fMaxProximityDist = std::min( 10 + iVehicles, 50 ); - // HACK: modern vehicles might brake slower at low speeds, increase safety margin as crude counter - if( mvControlling->EIMCtrlType > 0 ) { - fMinProximityDist += 5.0; - fMaxProximityDist += 5.0; - } - // take into account weather conditions - if( ( Global.FrictionWeatherFactor < 1.f ) - && ( iVehicles > 1 ) ) { - fMinProximityDist += 5.0; - fMaxProximityDist += 5.0; - } - fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania - // margines prędkości powodujący załączenie napędu - // były problemy z jazdą np. 3km/h podczas ładowania wagonów - fVelMinus = std::min( 0.1 * fShuntVelocity, 3.0 ); - break; - } - case Loose_shunt: { - fMinProximityDist = -1.0; - fMaxProximityDist = 0.0; //[m] dojechać maksymalnie - fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania - fVelMinus = 0.5; // margines prędkości powodujący załączenie napędu - break; - } - case Obey_train: { - // na jaka odleglosc i z jaka predkoscia ma podjechac do przeszkody - if( mvOccupied->CategoryFlag & 1 ) { - // jeśli pociąg - fMinProximityDist = clamp( 5 + iVehicles, 10, 15 ); - fMaxProximityDist = clamp( 10 + iVehicles, 15, 40 ); - - if( IsCargoTrain ) { - // increase distances for cargo trains to take into account slower reaction to brakes - fMinProximityDist += 10.0; - fMaxProximityDist += 10.0; -/* - if( IsHeavyCargoTrain ) { - // cargo trains with high braking threshold may require even larger safety margin - fMaxProximityDist += 20.0; - } -*/ - } - - if( ( Global.FrictionWeatherFactor < 1.f ) - && ( iVehicles > 1 ) ) { - // take into account weather conditions - fMinProximityDist += 5.0; - fMaxProximityDist += 5.0; - } - - if( mvOccupied->Vel < 0.1 ) { - // jak stanie za daleko, to niech nie dociąga paru metrów - fMaxProximityDist = 50.0; - } - - if( iDrivigFlags & moveLate ) { - // jeśli spóźniony, to gna - fVelMinus = 1.0; - // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania - fVelPlus = 5.0; - } - else { - // gdy nie musi się sprężać - // margines prędkości powodujący załączenie napędu; min 1.0 żeby nie ruszał przy 0.1 - fVelMinus = clamp( std::round( 0.05 * VelDesired ), 1.0, 5.0 ); - // normalnie dopuszczalne przekroczenie to 5% prędkości ale nie więcej niż 5km/h - // bottom margin raised to 2 km/h to give the AI more leeway at low speed limits - fVelPlus = clamp( std::ceil( 0.05 * VelDesired ), 2.0, 5.0 ); - } - } - else { - // samochod (sokista też) - fMinProximityDist = std::max( 3.5, mvOccupied->Vel * 0.2 ); - fMaxProximityDist = std::max( 9.5, mvOccupied->Vel * 0.375 ); //[m] - if( Global.FrictionWeatherFactor < 1.f ) { - // take into account weather conditions - fMinProximityDist += 0.75; - fMaxProximityDist += 0.75; - } - // margines prędkości powodujący załączenie napędu - fVelMinus = 2.0; - // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania - fVelPlus = std::min( 10.0, mvOccupied->Vel * 0.1 ); - } - break; - } - case Bank: { - // TODO: implement - break; - } - default: { - fMinProximityDist = 5.0; - fMaxProximityDist = 10.0; //[m] - fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania - fVelMinus = 5.0; // margines prędkości powodujący załączenie napędu - } - } // switch OrderList[OrderPos] - - switch (OrderCurrentGet()) - { // co robi maszynista - case Prepare_engine: // odpala silnik - // if (AIControllFlag) - if (PrepareEngine()) // dla użytkownika tylko sprawdza, czy uruchomił - { // gotowy do drogi? - SetDriverPsyche(); - // OrderList[OrderPos]:=Shunt; //Ra: to nie może tak być, bo scenerie robią - // Jump_to_first_order i przechodzi w manewrowy - JumpToNextOrder(); // w następnym jest Shunt albo Obey_train, moze też być - // Change_direction, Connect albo Disconnect - // if OrderList[OrderPos]<>Wait_for_Orders) - // if BrakeSystem=Pneumatic) //napelnianie uderzeniowe na wstepie - // if BrakeSubsystem=Oerlikon) - // if (BrakeCtrlPos=0)) - // DecBrakeLevel; - } - break; - case Release_engine: - if( ReleaseEngine() ) // zdana maszyna? - JumpToNextOrder(); - break; - case Jump_to_first_order: - if (OrderPos > 1) - OrderPos = 1; // w zerowym zawsze jest czekanie - else - ++OrderPos; -#if LOGORDERS - OrdersDump("Jump_to_first_order"); -#endif - break; - case Wait_for_orders: // jeśli czeka, też ma skanować, żeby odpalić się od semafora - case Shunt: - case Loose_shunt: - case Obey_train: - case Bank: - case Connect: - case Disconnect: - case Change_direction: // tryby wymagające jazdy - case Change_direction | Shunt: // zmiana kierunku podczas manewrów - case Change_direction | Loose_shunt: - case Change_direction | Connect: // zmiana kierunku podczas podłączania - if ((OrderCurrentGet() & ( Obey_train | Bank )) == 0) // spokojne manewry - { - VelSignal = min_speed( VelSignal, 40.0 ); // jeśli manewry, to ograniczamy prędkość - - // NOTE: this section should be moved to disconnect or plain removed, but it seems to be (mis)used by some scenarios - // to keep vehicle idling without moving :| - if( ( true == AIControllFlag ) - && ( iVehicleCount >= 0 ) - && ( false == TestFlag( iDrivigFlags, movePress ) ) - && ( iCoupler == 0 ) -// && ( mvOccupied->Vel > 0.0 ) - && ( pVehicle->MoverParameters->Couplers[ end::front ].Connected == nullptr ) - && ( pVehicle->MoverParameters->Couplers[ end::rear ].Connected == nullptr ) ) { - SetVelocity(0, 0, stopJoin); // 1. faza odczepiania: zatrzymanie - // WriteLog("Zatrzymanie w celu odczepienia"); - AccPreferred = std::min( 0.0, AccPreferred ); - } - - } - else - SetDriverPsyche(); // Ra: było w PrepareEngine(), potrzebne tu? - - if (OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank | Connect )) { - // odjechać sam może tylko jeśli jest w trybie jazdy - // automatyczne ruszanie po odstaniu albo spod SBL - if( ( VelSignal == 0.0 ) - && ( WaitingTime > 0.0 ) - && ( mvOccupied->RunningTrack.Velmax != 0.0 ) ) { - // jeśli stoi, a upłynął czas oczekiwania i tor ma niezerową prędkość - if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) - && ( iDrivigFlags & moveStopHere ) ) { - // zakaz ruszania z miejsca bez otrzymania wolnej drogi - WaitingTime = -WaitingExpireTime; - } - else if (mvOccupied->CategoryFlag & 1) - { // jeśli pociąg - if (AIControllFlag) - { - PrepareEngine(); // zmieni ustawiony kierunek - SetVelocity(20, 20); // jak się nastał, to niech jedzie 20km/h - WaitingTime = 0.0; - fWarningDuration = 1.5; // a zatrąbić trochę - mvOccupied->WarningSignal = 1; - } - else - SetVelocity(20, 20); // użytkownikowi zezwalamy jechać - } - else - { // samochód ma stać, aż dostanie odjazd, chyba że stoi przez kolizję - if (eStopReason == stopBlock) - if (Obstacle.distance > fDriverDist) - if (AIControllFlag) - { - PrepareEngine(); // zmieni ustawiony kierunek - SetVelocity(-1, -1); // jak się nastał, to niech jedzie - WaitingTime = 0.0; - } - else { - // użytkownikowi pozwalamy jechać (samochodem?) - SetVelocity( -1, -1 ); - } - } - } - else if( ( VelSignal == 0.0 ) && ( VelNext > 0.0 ) && ( mvOccupied->Vel < 1.0 ) ) { - if( iCoupler ? true : ( iDrivigFlags & moveStopHere ) == 0 ) { - // Ra: tu jest coś nie tak, bo bez tego warunku ruszało w manewrowym !!!! - SetVelocity( VelNext, VelNext, stopSem ); // omijanie SBL - } - } - } // koniec samoistnego odjeżdżania - - if( ( true == AIControllFlag) - && ( true == TestFlag( OrderCurrentGet(), Change_direction ) ) ) { - // sprobuj zmienic kierunek (może być zmieszane z jeszcze jakąś komendą) - if( mvOccupied->Vel < 0.1 ) { - // jeśli się zatrzymał, to zmieniamy kierunek jazdy, a nawet kabinę/człon - Activation(); // ustawienie zadanego wcześniej kierunku i ewentualne przemieszczenie AI - PrepareEngine(); - JumpToNextOrder(); // następnie robimy, co jest do zrobienia (Shunt albo Obey_train) - if( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect ) ) { - // jeśli dalej mamy manewry - if( false == TestFlag( iDrivigFlags, moveStopHere ) ) { - // o ile nie ma stać w miejscu, - // jechać od razu w przeciwną stronę i nie trąbić z tego tytułu: - iDrivigFlags &= ~moveStartHorn; - SetVelocity( fShuntVelocity, fShuntVelocity ); - } - } - } - } // Change_direction (tylko dla AI) - - if( ( true == AIControllFlag ) - && ( false == iEngineActive ) - && ( OrderCurrentGet() & ( Change_direction | Connect | Disconnect | Shunt | Loose_shunt | Obey_train | Bank ) ) ) { - // jeśli coś ma robić to niech odpala do skutku - PrepareEngine(); - } - - // ustalanie zadanej predkosci - if (iDrivigFlags & moveActive) { - - SetDriverPsyche(); // ustawia AccPreferred (potrzebne tu?) - - // jeśli może skanować sygnały i reagować na komendy - // jeśli jest wybrany kierunek jazdy, można ustalić prędkość jazdy - // Ra: tu by jeszcze trzeba było wstawić uzależnienie (VelDesired) od odległości od przeszkody - // no chyba żeby to uwzgldnić już w (ActualProximityDist) - VelDesired = fVelMax; // wstępnie prędkość maksymalna dla pojazdu(-ów), będzie - // następnie ograniczana - if( TrainParams.TTVmax > 0.0 ) { - // jeśli ma rozkład i ograniczenie w rozkładzie to nie przekraczać rozkladowej - VelDesired = min_speed( VelDesired, TrainParams.TTVmax ); - } - // szukanie optymalnych wartości - AccDesired = AccPreferred; // AccPreferred wynika z osobowości mechanika - VelNext = VelDesired; // maksymalna prędkość wynikająca z innych czynników niż trajektoria ruchu - ActualProximityDist = routescanrange; // funkcja Update() może pozostawić wartości bez zmian - VelLimitLastDist = { VelDesired, -1 }; - SwitchClearDist = -1; - // Ra: odczyt (ActualProximityDist), (VelNext) i (AccPreferred) z tabelki prędkosci - - TCommandType comm = TableUpdate(VelDesired, ActualProximityDist, VelNext, AccDesired); - - switch (comm) { - // ustawienie VelSignal - trochę proteza = do przemyślenia - case TCommandType::cm_Ready: // W4 zezwolił na jazdę - // ewentualne doskanowanie trasy za W4, który zezwolił na jazdę - TableCheck( routescanrange); - TableUpdate(VelDesired, ActualProximityDist, VelNext, AccDesired); // aktualizacja po skanowaniu - if (VelNext == 0.0) - break; // ale jak coś z przodu zamyka, to ma stać - if (iDrivigFlags & moveStopCloser) - VelSignal = -1.0; // ma czekać na sygnał z sygnalizatora! - case TCommandType::cm_SetVelocity: // od wersji 357 semafor nie budzi wyłączonej lokomotywy - if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) // jedzie w dowolnym trybie albo Wait_for_orders - if (fabs(VelSignal) >= 1.0) // 0.1 nie wysyła się do samochodow, bo potem nie ruszą - PutCommand("SetVelocity", VelSignal, VelNext, nullptr); // komenda robi dodatkowe operacje - break; - case TCommandType::cm_ShuntVelocity: // od wersji 357 Tm nie budzi wyłączonej lokomotywy - if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) // jedzie w dowolnym trybie albo Wait_for_orders - PutCommand("ShuntVelocity", VelSignal, VelNext, nullptr); - else if (iCoupler) // jeśli jedzie w celu połączenia - SetVelocity(VelSignal, VelNext); - break; - case TCommandType::cm_Command: // komenda z komórki - if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) { - // jedzie w dowolnym trybie albo Wait_for_orders - if( mvOccupied->Vel < 0.1 ) { - // dopiero jak stanie -/* - PutCommand( eSignNext->text(), eSignNext->value( 1 ), eSignNext->value( 2 ), nullptr ); - eSignNext->StopCommandSent(); // się wykonało już -*/ // replacement of the above - eSignNext->send_command( *this ); - } - } - break; - default: - break; - } - // disconnect mode potentially overrides scan results - // TBD: when in this mode skip scanning altogether? - if( ( true == AIControllFlag ) - && ( ( OrderCurrentGet() & Disconnect ) != 0 ) ) { - - if (iVehicleCount >= 0) { - // jeśli była podana ilość wagonów - if (iDrivigFlags & movePress) { - // jeśli dociskanie w celu odczepienia - // 3. faza odczepiania. - SetVelocity(2, 0); // jazda w ustawionym kierunku z prędkością 2 - if( iVehicleCount >= 0 ) { - // zmieni się po odczepieniu - WriteLog( mvOccupied->Name + " dociskanie..." ); - ZeroLocalBrake(); - IncSpeed(); - } - WriteLog(mvOccupied->Name + " odczepianie w kierunku " + std::to_string(mvOccupied->DirAbsolute)); - TDynamicObject *p = pVehicle; // pojazd do odczepienia, w (pVehicle) siedzi AI - int d; // numer sprzęgu, który sprawdzamy albo odczepiamy - int n = iVehicleCount; // ile wagonów ma zostać - do - { // szukanie pojazdu do odczepienia - d = p->DirectionGet() > 0 ? - end::front : - end::rear; // numer sprzęgu od strony czoła składu - // if (p->MoverParameters->Couplers[d].CouplerType==Articulated) - // //jeśli sprzęg typu wózek (za mało) - if (p->MoverParameters->Couplers[d].CouplingFlag & ctrain_depot) // jeżeli sprzęg zablokowany - // if (p->GetTrack()->) //a nie stoi na torze warsztatowym - // (ustalić po czym poznać taki tor) - ++n; // to liczymy człony jako jeden - p->MoverParameters->BrakeReleaser(1); // wyluzuj pojazd, aby dało się dopychać - // GBH p->MoverParameters->BrakeLevelSet(0); // hamulec na zero, aby nie hamował - BrakeLevelSet(gbh_RP); - if (n) - { // jeśli jeszcze nie koniec - p = p->Prev(); // kolejny w stronę czoła składu (licząc od tyłu), bo dociskamy - if (!p) - iVehicleCount = -2, - n = 0; // nie ma co dalej sprawdzać, doczepianie zakończone - } - } while (n--); - if( ( p == nullptr ) - || ( p->MoverParameters->Couplers[ d ].Connected == nullptr ) ) { - // no target, or already just virtual coupling - WriteLog( mvOccupied->Name + " didn't find anything to disconnect." ); - iVehicleCount = -2; // odczepiono, co było do odczepienia - } else if ( p->Dettach(d) == coupling::faux ) { - // tylko jeśli odepnie - WriteLog( mvOccupied->Name + " odczepiony." ); - iVehicleCount = -2; - // potentially remove coupler adapter - if( p->MoverParameters->Couplers[ d ].has_adapter() ) { - p->remove_coupler_adapter( d ); - } - // update trainset state - CheckVehicles( Disconnect ); - } // a jak nie, to dociskać dalej - } - if ((mvOccupied->Vel < 0.01) && !(iDrivigFlags & movePress)) - { // 2. faza odczepiania: zmień kierunek na przeciwny i dociśnij - // za radą yB ustawiamy pozycję 3 kranu (ruszanie kranem w innych miejscach - // powino zostać wyłączone) - // WriteLog("Zahamowanie składu"); - AccDesired = std::min( AccDesired, -0.9 ); // HACK: make sure the ai doesn't try to release the brakes to accelerate - if( mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ) { - mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_EPB ) ); - } - else { - BrakeCtrlPosition = 3; - } - double p = mvOccupied->BrakePressureActual.PipePressureVal; - if( p < 3.9 ) { - // tu może być 0 albo -1 nawet - // TODO: zabezpieczenie przed dziwnymi CHK do czasu wyjaśnienia sensu 0 oraz -1 w tym miejscu - p = 3.9; - } - if (mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ? - mvOccupied->BrakePress > 2 : - mvOccupied->PipePress < p + 0.1) - { // jeśli w miarę został zahamowany (ciśnienie mniejsze niż podane na pozycji 3, zwyle 0.37) - if( mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ) { - // wyłączenie EP, gdy wystarczy (może nie być potrzebne, bo na początku jest) - mvOccupied->BrakeLevelSet( 0 ); - } - WriteLog("Luzowanie lokomotywy i zmiana kierunku"); - mvOccupied->BrakeReleaser(1); // wyluzuj lokomotywę; a ST45? - mvOccupied->DecLocalBrakeLevel(LocalBrakePosNo); // zwolnienie hamulca - iDrivigFlags |= movePress; // następnie będzie dociskanie - DirectionForward(mvOccupied->DirActive < 0); // zmiana kierunku jazdy na przeciwny (dociskanie) - CheckVehicles(); // od razu zmienić światła (zgasić) - bez tego się nie odczepi - } - } - else { - if( mvOccupied->Vel > 0.01 ) { - // 1st phase(?) - // bring it to stop if it's not already stopped - SetVelocity( 0, 0, stopJoin ); // wyłączyć przyspieszanie - } - } - } // odczepiania - else // to poniżej jeśli ilość wagonów ujemna - if (iDrivigFlags & movePress) - { // 4. faza odczepiania: zwolnij i zmień kierunek - SetVelocity(0, 0, stopJoin); // wyłączyć przyspieszanie - ZeroSpeed(); - // ponowna zmiana kierunku - WriteLog( mvOccupied->Name + " ponowna zmiana kierunku" ); - DirectionForward(mvOccupied->DirActive < 0); // zmiana kierunku jazdy na właściwy - iDrivigFlags &= ~movePress; // koniec dociskania - JumpToNextOrder(); // zmieni światła - TableClear(); // skanowanie od nowa - iDrivigFlags &= ~moveStartHorn; // bez trąbienia przed ruszeniem - SetVelocity(fShuntVelocity, fShuntVelocity); // ustawienie prędkości jazdy - mvOccupied->BrakeReleaser(0); // wyłączyć luzowanie - } - } - - // when loose shunting try to detect situations where engaged brakes in a consist to be pushed prevent movement - if( ( true == AIControllFlag ) - && ( ( OrderCurrentGet() & Loose_shunt ) != 0 ) - && ( Obstacle.distance < 1.0 ) - && ( AccDesired > 0.1 ) - && ( mvOccupied->Vel < 1.0 ) ){ - - auto *vehicle { Obstacle.vehicle }; - auto const direction { ( vehicle->Prev() != nullptr ? end::front : end::rear ) }; - while( vehicle != nullptr ) { - if( vehicle->MoverParameters->BrakePress > 0.2 ) { - vehicle->MoverParameters->BrakeLevelSet( 0 ); // hamulec na zero, aby nie hamował - vehicle->MoverParameters->BrakeReleaser( 1 ); // wyluzuj pojazd, aby dało się dopychać - } - // NOTE: we trust the consist to be arranged in a valid chain - // TBD, TODO: traversal direction validation? - vehicle = ( direction == end::front ? vehicle->Prev() : vehicle->Next() ); - } - } - - if( true == TestFlag( OrderCurrentGet(), Change_direction ) ) { - // if ordered to change direction, try to stop - SetVelocity( 0, 0, stopDir ); - } - - if( VelNext == 0.0 ) { - if( !( OrderCurrentGet() & ~( Shunt | Loose_shunt | Connect ) ) ) { - // jedzie w Shunt albo Connect, albo Wait_for_orders - // w trybie Connect skanować do tyłu tylko jeśli przed kolejnym sygnałem nie ma taboru do podłączenia - // Ra 2F1H: z tym (fTrackBlock) to nie jest najlepszy pomysł, bo lepiej by - // było porównać z odległością od sygnalizatora z przodu - if( ( ( OrderCurrentGet() & Connect ) == 0 ) - || ( Obstacle.distance > std::min( 2000.0, FirstSemaphorDist ) ) ) { - - if( ( comm = BackwardScan() ) != TCommandType::cm_Unknown ) { - // jeśli w drugą można jechać - // należy sprawdzać odległość od znalezionego sygnalizatora, - // aby w przypadku prędkości 0.1 wyciągnąć najpierw skład za sygnalizator - // i dopiero wtedy zmienić kierunek jazdy, oczekując podania prędkości >0.5 - if( comm == TCommandType::cm_Command ) { - // jeśli komenda Shunt to ją odbierz bez przemieszczania się (np. odczep wagony po dopchnięciu do końca toru) - iDrivigFlags |= moveStopHere; - } - iDirectionOrder = -iDirection; // zmiana kierunku jazdy - // zmiana kierunku bez psucia kolejnych komend - OrderList[ OrderPos ] = TOrders( OrderCurrentGet() | Change_direction ); - } - } - } - } - - double vel = mvOccupied->Vel; // prędkość w kierunku jazdy - if (iDirection * mvOccupied->V < 0) - vel = -vel; // ujemna, gdy jedzie w przeciwną stronę, niż powinien - if (VelDesired < 0.0) - VelDesired = fVelMax; // bo VelDesired<0 oznacza prędkość maksymalną - - // Ra: jazda na widoczność - if( Obstacle.distance < 5000 ) { - // mamy coś z przodu - // prędkość pojazdu z przodu (zakładając, że jedzie w tę samą stronę!!!) - auto const k { Obstacle.vehicle->MoverParameters->Vel }; - - if( k - vel < 5 ) { - // porównanie modułów prędkości [km/h] - // zatroszczyć się trzeba, jeśli tamten nie jedzie znacząco szybciej - ActualProximityDist = std::min( - ActualProximityDist, - Obstacle.distance ); - - if( Obstacle.distance <= ( - ( mvOccupied->CategoryFlag & 2 ) ? - 100.0 : // cars - 250.0 ) ) { // others - // regardless of driving mode at close distance take precaution measures: - // match the other vehicle's speed or slow down if the other vehicle is stopped - VelDesired = - min_speed( - VelDesired, - std::max( - k, - ( mvOccupied->CategoryFlag & 2 ) ? - 40.0 : // cars - 20.0 ) ); // others - if( vel > VelDesired + fVelPlus ) { - // if going too fast force some prompt braking - AccPreferred = std::min( - ( ( mvOccupied->CategoryFlag & 2 ) ? - -0.65 : // cars - -0.30 ), // others - AccPreferred ); - } - } - - double const distance = Obstacle.distance - fMaxProximityDist - ( fBrakeDist * 1.15 ); // odległość bezpieczna zależy od prędkości - if( distance < 0.0 ) { - // jeśli odległość jest zbyt mała - if( k < 10.0 ) // k - prędkość tego z przodu - { // jeśli tamten porusza się z niewielką prędkością albo stoi - // keep speed difference within a safer margin - VelDesired = std::floor( - min_speed( - VelDesired, - ( Obstacle.distance > 100 ? - k + 20.0: - std::min( 8.0, k + 4.0 ) ) ) ); - - if( ( OrderCurrentGet() & ( Connect | Loose_shunt ) ) != 0 ) { - // jeśli spinanie, to jechać dalej - AccPreferred = std::min( 0.35, AccPreferred ); // nie hamuj - VelNext = std::floor( std::min( 8.0, k + 2.0 ) ); // i pakuj się na tamtego - } - else { - // a normalnie to hamować - VelNext = 0.0; - if( Obstacle.distance <= fMinProximityDist ) { - VelDesired = 0.0; - } - - if( ( mvOccupied->CategoryFlag & 1 ) - && ( OrderCurrentGet() & Obey_train ) ) { - // trains which move normally should try to stop at safe margin - ActualProximityDist -= fDriverDist; - } - } - } - else { - // jeśli oba jadą, to przyhamuj lekko i ogranicz prędkość - if( Obstacle.distance < ( - ( mvOccupied->CategoryFlag & 2 ) ? - fMaxProximityDist + 0.5 * vel : // cars - 2.0 * fMaxProximityDist + 2.0 * vel ) ) { //others - // jak tamten jedzie wolniej a jest w drodze hamowania - AccPreferred = std::min( -0.9, AccPreferred ); - VelNext = min_speed( std::round( k ) - 5.0, VelDesired ); - if( Obstacle.distance <= ( - ( mvOccupied->CategoryFlag & 2 ) ? - fMaxProximityDist : // cars - 2.0 * fMaxProximityDist ) ) { //others - // try to force speed change if obstacle is really close - VelDesired = VelNext; - } - } - } - ReactionTime = ( - mvOccupied->Vel > 0.01 ? - 0.1 : // orientuj się, bo jest goraco - 2.0 ); // we're already standing still, so take it easy - } - else { - if( OrderCurrentGet() & Connect ) { - // if there's something nearby in the connect mode don't speed up too much - VelDesired = - min_speed( - VelDesired, - ( Obstacle.distance > 100 ? - 20.0 : - 4.0 ) ); - } - } - } - } - - // sprawdzamy możliwe ograniczenia prędkości - if( VelSignal >= 0 ) { - // jeśli skład był zatrzymany na początku i teraz już może jechać - VelDesired = - min_speed( - VelDesired, - VelSignal ); - } - if( mvOccupied->RunningTrack.Velmax >= 0 ) { - // ograniczenie prędkości z trajektorii ruchu - VelDesired = - min_speed( - VelDesired, - mvOccupied->RunningTrack.Velmax ); // uwaga na ograniczenia szlakowej! - } - if( VelforDriver >= 0 ) { - // tu jest zero przy zmianie kierunku jazdy - // Ra: tu może być 40, jeśli mechanik nie ma znajomości szlaaku, albo kierowca jeździ 70 - VelDesired = - min_speed( - VelDesired, - VelforDriver ); - } - // recalculate potential load exchange duration - DoesAnyDoorNeedOpening = false; - ExchangeTime = 0.f; - if( fStopTime < 0 ) { - // czas postoju przed dalszą jazdą (np. na przystanku) - VelDesired = 0.0; // jak ma czekać, to nie ma jazdy - // verify progress of load exchange - auto *vehicle { pVehicles[ 0 ] }; - while( vehicle != nullptr ) { - auto const vehicleexchangetime { vehicle->LoadExchangeTime() }; - DoesAnyDoorNeedOpening |= ( ( vehicleexchangetime > 0 ) && ( vehicle->LoadExchangeSpeed() == 0 ) ); - ExchangeTime = std::max( ExchangeTime, vehicleexchangetime ); - vehicle = vehicle->Next(); - } - if( ( ExchangeTime > 0 ) - || ( mvOccupied->Vel > 2.0 ) ) { // HACK: force timer reset if the load exchange is cancelled due to departure - WaitingSet( ExchangeTime ); - } - } - - if( ( OrderCurrentGet() & Obey_train ) != 0 ) { - - if( ( TrainParams.CheckTrainLatency() < 5.0 ) - && ( TrainParams.TTVmax > 0.0 ) ) { - // jesli nie spozniony to nie przekraczać rozkladowej - VelDesired = - min_speed( - VelDesired, - TrainParams.TTVmax ); - } - } - - if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) != 0 ) { - // w Connect nie, bo moveStopHere odnosi się do stanu po połączeniu - if( ( ( iDrivigFlags & moveStopHere ) != 0 ) - && ( vel < 0.01 ) - && ( VelSignalNext == 0.0 ) ) { - // jeśli ma czekać na wolną drogę, stoi a wyjazdu nie ma, to ma stać - VelDesired = 0.0; - } - } - - if( OrderCurrentGet() == Wait_for_orders ) { - // wait means sit and wait - VelDesired = 0.0; - } - // end of speed caps checks - - if( ( ( OrderCurrentGet() & Obey_train ) != 0 ) - && ( ( iDrivigFlags & moveGuardSignal ) != 0 ) - && ( VelDesired > 0.0 ) ) { - // komunikat od kierownika tu, bo musi być wolna droga i odczekany czas stania - iDrivigFlags &= ~moveGuardSignal; // tylko raz nadać - if( false == tsGuardSignal.empty() ) { - tsGuardSignal.stop(); - // w zasadzie to powinien mieć flagę, czy jest dźwiękiem radiowym, czy bezpośrednim - // albo trzeba zrobić dwa dźwięki, jeden bezpośredni, słyszalny w - // pobliżu, a drugi radiowy, słyszalny w innych lokomotywach - // na razie zakładam, że to nie jest dźwięk radiowy, bo trzeba by zrobić - // obsługę kanałów radiowych itd. - if( iGuardRadio == 0 ) { - // jeśli nie przez radio - tsGuardSignal.owner( pVehicle ); - // place virtual conductor some distance away - tsGuardSignal.offset( { pVehicle->MoverParameters->Dim.W * -0.75f, 1.7f, std::min( -20.0, -0.2 * fLength ) } ); - tsGuardSignal.play( sound_flags::exclusive ); - // NOTE: we can't rely on is_playing() check as sound playback is based on distance from local camera - fActionTime = -5.0; // niech trochę potrzyma - } - else { - // radio message - tsGuardSignal.owner( pVehicle ); -/* - tsGuardSignal.offset( { 0.f, 2.f, pVehicle->MoverParameters->Dim.L * 0.4f * ( pVehicle->MoverParameters->CabOccupied < 0 ? -1 : 1 ) } ); - tsGuardSignal.play( sound_flags::exclusive ); -*/ - simulation::radio_message( &tsGuardSignal, iGuardRadio ); - // NOTE: we can't rely on is_playing() check as sound playback is based on distance from local camera - fActionTime = -5.0; // niech trochę potrzyma - } - } - } - - if( mvOccupied->Vel < 0.01 ) { - // Ra 2014-03: jesli skład stoi, to działa na niego składowa styczna grawitacji - AbsAccS = fAccGravity; - } - else { - AbsAccS = 0; - TDynamicObject *d = pVehicles[0]; // pojazd na czele składu - while (d) - { - AbsAccS += d->MoverParameters->TotalMass * d->MoverParameters->AccS * ( d->DirectionGet() == iDirection ? 1 : -1 ); - d = d->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła) - } - AbsAccS *= iDirection; - AbsAccS /= fMass; - } - AbsAccS_pub = AbsAccS; - -#if LOGVELOCITY - // WriteLog("VelDesired="+AnsiString(VelDesired)+", - // VelSignal="+AnsiString(VelSignal)); - WriteLog("Vel=" + AnsiString(vel) + ", AbsAccS=" + AnsiString(AbsAccS) + - ", AccGrav=" + AnsiString(fAccGravity)); -#endif - // ustalanie zadanego przyspieszenia - //(ActualProximityDist) - odległość do miejsca zmniejszenia prędkości - //(AccPreferred) - wynika z psychyki oraz uwzglęnia już ewentualne zderzenie z pojazdem z przodu, ujemne gdy należy hamować - //(AccDesired) - uwzględnia sygnały na drodze ruchu, ujemne gdy należy hamować - //(fAccGravity) - chwilowe przspieszenie grawitacyjne, ujemne działa przeciwnie do zadanego kierunku jazdy - //(AbsAccS) - chwilowe przyspieszenie pojazu (uwzględnia grawitację), ujemne działa przeciwnie do zadanego kierunku jazdy - //(AccDesired) porównujemy z (fAccGravity) albo (AbsAccS) - if( ( VelNext >= 0.0 ) - && ( ActualProximityDist <= routescanrange ) - && ( vel >= VelNext ) ) { - // gdy zbliża się i jest za szybki do nowej prędkości, albo stoi na zatrzymaniu - if (vel > 0.0) { - // jeśli jedzie - if( ( vel < VelNext ) - && ( ActualProximityDist > fMaxProximityDist * ( 1.0 + 0.1 * vel ) ) ) { - // jeśli jedzie wolniej niż można i jest wystarczająco daleko, to można przyspieszyć - if( AccPreferred > 0.0 ) { - // jeśli nie ma zawalidrogi dojedz do semafora/przeszkody - AccDesired = AccPreferred; - } - } - else if (ActualProximityDist > fMinProximityDist) { - // jedzie szybciej, niż trzeba na końcu ActualProximityDist, ale jeszcze jest daleko - if (ActualProximityDist < fMaxProximityDist) { - // jak minął już maksymalny dystans po prostu hamuj (niski stopień) - // ma stanąć, a jest w drodze hamowania albo ma jechać -/* - VelDesired = min_speed( VelDesired, VelNext ); -*/ - if( VelNext == 0.0 ) { - if( mvOccupied->CategoryFlag & 1 ) { - // trains - if( ( OrderCurrentGet() & ( Shunt | Connect ) ) - && ( Obstacle.distance < 50 ) ) { - // crude detection of edge case, if approaching another vehicle coast slowly until min distance - // this should allow to bunch up trainsets more on sidings - VelDesired = min_speed( 5.0, VelDesired ); - } - else { - // hamowanie tak, aby stanąć - VelDesired = VelNext; - AccDesired = ( VelNext * VelNext - vel * vel ) / ( 25.92 * ( ActualProximityDist + 0.1 - 0.5*fMinProximityDist ) ); - AccDesired = std::min( AccDesired, fAccThreshold ); - } - } - else { - // for cars (and others) coast at low speed until we hit min proximity range - VelDesired = min_speed( VelDesired, 5.0 ); - } - } - } - else { - // outside of max safe range - AccDesired = AccPreferred; - if( vel > min_speed( (ActualProximityDist > 10.0 ? 10.0 : 5.0 ), VelDesired ) ) { - // allow to coast at reasonably low speed - auto const brakingdistance { fBrakeDist * braking_distance_multiplier( VelNext ) }; - auto const slowdowndistance { ( - mvOccupied->CategoryFlag == 2 ? // cars can stop on a dime, for bigger vehicles we enforce some minimal braking distance - brakingdistance : - std::max( - ( ( OrderCurrentGet() & Connect ) == 0 ? 100.0 : 25.0 ), - brakingdistance ) ) }; - if( ( brakingdistance + std::max( slowdowndistance, fMaxProximityDist ) ) >= ( ActualProximityDist - fMaxProximityDist ) ) { - // don't slow down prematurely; as long as we have room to come to a full stop at a safe distance, we're good - // ensure some minimal coasting speed, otherwise a vehicle entering this zone at very low speed will be crawling forever - auto const brakingpointoffset = VelNext * braking_distance_multiplier( VelNext ); - AccDesired = std::min( - AccDesired, - ( VelNext * VelNext - vel * vel ) - / ( 25.92 - * std::max( - ActualProximityDist - brakingpointoffset, - std::min( - ActualProximityDist, - brakingpointoffset ) ) - + 0.1 ) ); // najpierw hamuje mocniej, potem zluzuje - } - } - } - AccDesired = std::min( AccDesired, AccPreferred ); - } - else { - // jest bliżej niż fMinProximityDist - // utrzymuj predkosc bo juz blisko -/* - VelDesired = min_speed( VelDesired, VelNext ); -*/ - if( VelNext == 0.0 ) { - VelDesired = VelNext; - } - else { - if( vel <= VelNext + fVelPlus ) { - // jeśli niewielkie przekroczenie, ale ma jechać - AccDesired = std::max( 0.0, AccPreferred ); // to olej (zacznij luzować) - } - } - ReactionTime = 0.1; // i orientuj się szybciej - } - } - else { - // zatrzymany (vel==0.0) - if( VelNext > 0.0 ) { - // można jechać - AccDesired = AccPreferred; - } - else { - // jeśli daleko jechać nie można - if( ActualProximityDist > ( - ( mvOccupied->CategoryFlag & 2 ) ? - fMinProximityDist : // cars - fMaxProximityDist ) ) { // trains and others - // ale ma kawałek do sygnalizatora - if( AccPreferred > 0.0 ) { - // dociagnij do semafora; - AccDesired = AccPreferred; - } - else { - //stoj - VelDesired = 0.0; - } - } - else { - // VelNext=0 i stoi bliżej niż fMaxProximityDist - VelDesired = 0.0; - } - } - } - } - else { - // gdy jedzie wolniej niż potrzeba, albo nie ma przeszkód na drodze - // normalna jazda - AccDesired = ( - VelDesired != 0.0 ? - AccPreferred : - -0.01 ); - } - // koniec predkosci nastepnej - - // decisions based on current speed - if( mvOccupied->CategoryFlag == 1 ) { - - // on flats or uphill we can be less careful - if( vel > VelDesired ) { - // jesli jedzie za szybko do AKTUALNEGO - if( VelDesired == 0.0 ) { - // jesli stoj, to hamuj, ale i tak juz za pozno :) - AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie - } - else { - // slow down, not full stop - if( vel > ( VelDesired + fVelPlus ) ) { - // hamuj tak średnio - AccDesired = std::min( AccDesired, -0.25 ); - } - else { - // o 5 km/h to olej (zacznij luzować) - AccDesired = std::min( - AccDesired, // but don't override decceleration for VelNext - std::max( 0.0, AccPreferred ) ); - } - } - } - - if( fAccGravity > 0.025 ) { - // going sharply downhill we may need to start braking sooner than usual - // try to estimate increase of current velocity before engaged brakes start working - auto const speedestimate = vel + ( 1.0 - fBrake_a0[ 0 ] ) * 30.0 * AbsAccS; - if( speedestimate > VelDesired ) { - // jesli jedzie za szybko do AKTUALNEGO - if( VelDesired == 0.0 ) { - // jesli stoj, to hamuj, ale i tak juz za pozno :) - AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie - } - else { - // if it looks like we'll exceed maximum speed start thinking about slight slowing down - AccDesired = std::min( AccDesired, -0.25 ); - // HACK: for cargo trains with high braking threshold ensure we cross that threshold - if( ( true == IsCargoTrain ) - && ( fBrake_a0[ 0 ] > 0.2 ) ) { - AccDesired -= clamp( fBrake_a0[ 0 ] - 0.2, 0.0, 0.15 ); - } - } - } - else { - // stop accelerating when close enough to target speed - AccDesired = std::min( - AccDesired, // but don't override decceleration for VelNext - interpolate( // ease off as you close to the target velocity - -0.06, AccPreferred, - clamp( VelDesired - speedestimate, 0.0, fVelMinus ) / fVelMinus ) ); - } - // final tweaks - if( vel > 0.1 ) { - // going downhill also take into account impact of gravity - AccDesired -= fAccGravity; - // HACK: if the max allowed speed was exceeded something went wrong; brake harder - AccDesired -= 0.15 * clamp( vel - VelDesired, 0.0, 5.0 ); -/* - if( ( vel > VelDesired ) - && ( ( mvOccupied->BrakeDelayFlag & bdelay_G ) != 0 ) - && ( fBrake_a0[ 0 ] > 0.2 ) ) { - AccDesired = clamp( - AccDesired - clamp( fBrake_a0[ 0 ] - 0.2, 0.0, 0.15 ), - -0.9, 0.9 ); - } -*/ - } - } - // HACK: limit acceleration for cargo trains, to reduce probability of breaking couplers on sudden jolts - // TBD: expand this behaviour to all trains with car(s) exceeding certain weight? - if( ( IsPassengerTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) { - AccDesired = std::min( AccDesired, ( iVehicles - ControlledEnginesCount > 8 ? HeavyPassengetTrainAcceleration : PassengetTrainAcceleration ) ); - } - if( ( IsCargoTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) { - AccDesired = std::min( AccDesired, CargoTrainAcceleration ); - } - if( ( IsHeavyCargoTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) { - AccDesired = std::min( AccDesired, HeavyCargoTrainAcceleration ); - } - } - else { - // for cars the older version works better - if( vel > VelDesired ) { - // jesli jedzie za szybko do AKTUALNEGO - if( VelDesired == 0.0 ) { - // jesli stoj, to hamuj, ale i tak juz za pozno :) - AccDesired = std::min( AccDesired, -0.9 ); // hamuj solidnie - } - else { - // slow down, not full stop - if( vel > ( VelDesired + fVelPlus ) ) { - // hamuj tak średnio - AccDesired = std::min( AccDesired, -fBrake_a0[ 0 ] * 0.5 ); - } - else { - // o 5 km/h to olej (zacznij luzować) - AccDesired = std::min( - AccDesired, // but don't override decceleration for VelNext - std::max( 0.0, AccPreferred ) ); - } - } - } - } - // koniec predkosci aktualnej - - // last step sanity check, until the whole calculation is straightened out - AccDesired = std::min( AccDesired, AccPreferred ); - AccDesired = clamp( - AccDesired, - ( mvControlling->CategoryFlag == 2 ? -2.0 : -0.9 ), - ( mvControlling->CategoryFlag == 2 ? 2.0 : 0.9 ) ); - - - if ((-AccDesired > fBrake_a0[0] + 8 * fBrake_a1[0]) && (HelperState == 0)) - { - HelperState = 1; - } - if ((-AccDesired > fBrake_a0[0] + 12 * fBrake_a1[0]) && (HelperState == 1)) - { - HelperState = 2; - } - if ((-AccDesired > 0) && (HelperState == 2) && (-ActualProximityDist > 5)) - { - HelperState = 3; - } - if ((-AccDesired < fBrake_a0[0] + 2 * fBrake_a1[0]) && (HelperState > 0) && (vel>1)) - { - HelperState = 0; - } - - if (AIControllFlag) { - // część wykonawcza tylko dla AI, dla człowieka jedynie napisy - - // zapobieganie poslizgowi u nas - if (mvControlling->SlippingWheels && mvControlling->EngineType != TEngineType::ElectricInductionMotor) { - - if( false == mvControlling->DecScndCtrl( 2 ) ) { - // bocznik na zero - mvControlling->DecMainCtrl( 1 ); - } - DecBrake(); // cofnij hamulec - mvControlling->AntiSlippingButton(); - ++iDriverFailCount; - } - if (iDriverFailCount > maxdriverfails) { - - Psyche = Easyman; - if (iDriverFailCount > maxdriverfails * 2) - SetDriverPsyche(); - } - - if( ( true == mvOccupied->RadioStopFlag ) // radio-stop - && ( mvOccupied->Vel > 0.0 ) ) { // and still moving - // if the radio-stop was issued don't waste effort trying to fight it - ZeroSpeed(); // just throttle down... - return; // ...and don't touch any other controls - } - - if( ( IsAnyConverterOverloadRelayOpen ) // wywalił bezpiecznik nadmiarowy przetwornicy - || ( IsAnyLineBreakerOpen ) ) { // WS może wywalić z powodu błędu w drutach - // próba ponownego załączenia - PrepareEngine(); - } - // włączanie bezpiecznika - if( ( mvControlling->EngineType == TEngineType::ElectricSeriesMotor ) - || ( mvControlling->EngineType == TEngineType::DieselElectric ) - || ( mvControlling->TrainType == dt_EZT ) ) { - if( Need_TryAgain ) { - // true, jeśli druga pozycja w elektryku nie załapała - ZeroSpeed(); - Need_TryAgain = false; - } - if( IsAnyMotorOverloadRelayOpen || IsAnyGroundRelayOpen ) { - ZeroSpeed(); - mvOccupied->FuseOn(); - mvControlling->MainSwitch( true ); // Ra: dodałem, bo EN57 stawały po wywaleniu - ++iDriverFailCount; - if( iDriverFailCount > maxdriverfails ) - Psyche = Easyman; - if( iDriverFailCount > maxdriverfails * 2 ) - SetDriverPsyche(); - } - } - // NOTE: as a stop-gap measure the routine is limited to trains only while car calculations seem off - if( mvControlling->CategoryFlag == 1 ) { - if( vel < VelDesired ) { - // don't adjust acceleration when going above current goal speed - if( -AccDesired * BrakeAccFactor() < ( - ( ( fReady > ( IsHeavyCargoTrain ? 0.4 : ( mvOccupied->Vel > 5.0 ) ? 0.45 : 0.4 ) ) - || ( VelNext > vel - 40.0 ) ) ? - fBrake_a0[ 0 ] * 0.8 : - -fAccThreshold ) - / ( 1.2 * braking_distance_multiplier( VelNext ) ) ) { - AccDesired = std::max( -0.06, AccDesired ); - } - } - if( AccDesired < -0.1 ) { - // i orientuj się szybciej, jeśli hamujesz - ReactionTime = 0.25; - } - } - if (mvOccupied->BrakeSystem == TBrakeSystem::Pneumatic) { - // napełnianie uderzeniowe - if( ( mvOccupied->BrakeHandle == TBrakeHandle::FV4a ) - || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_6P ) - || (mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K5P) - || (mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P) - || ( mvOccupied->BrakeHandle == TBrakeHandle::M394 ) ) { - - if( /*GBH mvOccupied->BrakeCtrlPos*/BrakeCtrlPosition == -2 ) { - /*mvOccupied->*/BrakeLevelSet( gbh_RP ); - } - - // TODO: combine all releaser handling in single decision tree instead of having bits all over the place - if( ( AccDesired > -0.03 ) - && ( false == mvOccupied->Hamulec->Releaser() ) ) { - if( mvOccupied->PipePress < 3.0 ) { - ZeroSpeed(); // some vehicles may require master controller in neutral position - // some vehicles require brake handle to be moved to specific position - if( mvOccupied->HandleUnlock != -3 ) { - while( ( BrakeCtrlPosition >= mvOccupied->HandleUnlock ) - && ( BrakeLevelAdd( -1 ) ) ) { - // all work is done in the header - ; - } - } - mvOccupied->BrakeReleaser( 1 ); - } - if( ( mvOccupied->BrakePress > 0.4 ) - && ( mvOccupied->Hamulec->GetCRP() > 4.9 ) ) { - // wyluzuj lokomotywę, to szybciej ruszymy - mvOccupied->BrakeReleaser( 1 ); - } - } - if( ( mvOccupied->PipePress > 3.0 ) - && ( mvOccupied->Hamulec->Releaser() ) ) { - // don't overcharge train brake pipe - mvOccupied->BrakeReleaser( 0 ); - } - - if( ( /*GBH mvOccupied->BrakeCtrlPos*/BrakeCtrlPosition == 0 ) - && ( AbsAccS < 0.03 ) - && ( AccDesired > -0.03 ) - && ( VelDesired - mvOccupied->Vel > 2.0 ) ) { - - if( ( mvOccupied->EqvtPipePress < 4.5 ) - && ( fReady > 0.35 ) - && ( mvOccupied->Compressor >= 7.5 ) // don't charge without sufficient pressure in the tank - && ( BrakeChargingCooldown >= 0.0 ) - && ( ( ActualProximityDist > 100.0 ) // don't charge if we're about to be braking soon - || ( min_speed( mvOccupied->Vel, VelNext ) == mvOccupied->Vel ) ) ) { - - if( ( iDrivigFlags & moveOerlikons ) - || ( true == IsCargoTrain ) ) { - // napełnianie w Oerlikonie - /* mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_FS ) ); GBH */ - BrakeLevelSet( gbh_FS ); - // don't charge the brakes too often, or we risk overcharging - BrakeChargingCooldown = -1 * clamp( iVehicleCount * 3, 30, 90 ); - } - } -/* -// NOTE: disabled, duplicate of release activation in #5732 - else if( Need_BrakeRelease ) { - Need_BrakeRelease = false; - mvOccupied->BrakeReleaser( 1 ); - } -*/ - } - - if( ( mvOccupied->Compressor < 5.0 ) - || ( ( /*GBH mvOccupied->BrakeCtrlPos*/BrakeCtrlPosition < 0 ) - && ( mvOccupied->EqvtPipePress > ( fReady < 0.25 ? 5.1 : 5.2 ) ) ) ) { - /* GBH mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_RP ) ); */ - BrakeLevelSet( gbh_RP ); - } - } - } - // unlocking main pipe - if ((AccDesired > -0.03) - && (true == mvOccupied->LockPipe)) { - UniversalBrakeButtons |= TUniversalBrake::ub_UnlockPipe; - } - else if (false == mvOccupied->LockPipe) { - UniversalBrakeButtons &= ~TUniversalBrake::ub_UnlockPipe; - } -#if LOGVELOCITY - WriteLog("Dist=" + FloatToStrF(ActualProximityDist, ffFixed, 7, 1) + - ", VelDesired=" + FloatToStrF(VelDesired, ffFixed, 7, 1) + - ", AccDesired=" + FloatToStrF(AccDesired, ffFixed, 7, 3) + - ", VelSignal=" + AnsiString(VelSignal) + ", VelNext=" + - AnsiString(VelNext)); -#endif -/* - if( ( vel < 10.0 ) - && ( AccDesired > 0.1 ) ) { - // Ra 2F1H: jeśli prędkość jest mała, a można przyspieszać, - // to nie ograniczać przyspieszenia do 0.5m/ss - // przy małych prędkościach może być trudno utrzymać - AccDesired = std::max( 0.9, AccDesired ); - } -*/ - // małe przyspieszenie - // Ra 2F1I: wyłączyć kiedyś to uśrednianie i przeanalizować skanowanie, czemu migocze - if (AccDesired > -0.05) // hamowania lepeiej nie uśredniać - AccDesired = fAccDesiredAv = - 0.2 * AccDesired + - 0.8 * fAccDesiredAv; // uśrednione, żeby ograniczyć migotanie - if( VelDesired == 0.0 ) { - // Ra 2F1J: jeszcze jedna prowizoryczna łatka - if( AccDesired >= -0.01 ) { - AccDesired = -0.01; - } - } - if( AccDesired >= 0.0 ) { - if( true == TestFlag( iDrivigFlags, movePress ) ) { - // wyluzuj lokomotywę - może być więcej! - mvOccupied->BrakeReleaser( 1 ); - } - else if( OrderCurrentGet() != Disconnect ) { - // przy odłączaniu nie zwalniamy tu hamulca - if( ( fAccGravity * fAccGravity < 0.001 ? - true : - AccDesired > 0.0 ) ) { - // on slopes disengage the brakes only if you actually intend to accelerate - while( true == DecBrake() ) { ; } // jeśli przyspieszamy, to nie hamujemy - } - } - } - // yB: usunięte różne dziwne warunki, oddzielamy część zadającą od wykonawczej zwiekszanie predkosci - // Ra 2F1H: jest konflikt histerezy pomiędzy nastawioną pozycją a uzyskiwanym - // przyspieszeniem - utrzymanie pozycji powoduje przekroczenie przyspieszenia - if( ( AccDesired > -0.06 ) // don't add power if not asked for actual speed-up - && ( AccDesired - AbsAccS > 0.05 ) ) { - // jeśli przyspieszenie pojazdu jest mniejsze niż żądane oraz... - if( vel < ( - VelDesired == 1.0 ? // work around for trains getting stuck on tracks with speed limit = 1 - VelDesired : - VelDesired - fVelMinus ) ) { - // ...jeśli prędkość w kierunku czoła jest mniejsza od dozwolonej o margines - if( ( ActualProximityDist > ( - ( mvOccupied->CategoryFlag & 2 ) ? - fMinProximityDist : // cars are allowed to move within min proximity distance - fMaxProximityDist ) ? // other vehicle types keep wider margin - true : - ( vel + 1.0 ) < VelNext ) ) { - // to można przyspieszyć - IncSpeed(); - } - } - } - // yB: usunięte różne dziwne warunki, oddzielamy część zadającą od wykonawczej - // zmniejszanie predkosci - // margines dla prędkości jest doliczany tylko jeśli oczekiwana prędkość jest większa od 5km/h - if( false == TestFlag( iDrivigFlags, movePress ) ) { - double SpeedCtrlMargin = (mvControlling->SpeedCtrlUnit.IsActive && VelDesired > 5) ? 3 : 0; - // jeśli nie dociskanie - if( AccDesired < -0.05 ) { - ZeroSpeed(); - } - else if( ( vel > VelDesired + SpeedCtrlMargin) - || ( fAccGravity < -0.01 ? - AccDesired < 0.0 : - (AbsAccS > AccDesired + 0.05) ) - || ( IsAnyCouplerStretched ) ) { - // jak za bardzo przyspiesza albo prędkość przekroczona - // dodany wyjatek na "pelna w przod" - DecSpeed(); - } - } - if( mvOccupied->TrainType == dt_EZT ) { - // właściwie, to warunek powinien być na działający EP - // Ra: to dobrze hamuje EP w EZT - // HACK: when going downhill be more responsive to desired deceleration - auto const accthreshold { ( - fAccGravity < 0.025 ? - fAccThreshold : - std::max( -0.2, fAccThreshold ) ) }; - if( ( AccDesired <= accthreshold ) // jeśli hamować - u góry ustawia się hamowanie na fAccThreshold - && ( ( AbsAccS > AccDesired ) - || ( /*GBH mvOccupied->BrakeCtrlPos*/BrakeCtrlPosition < 0 ) ) ) { - // hamować bardziej, gdy aktualne opóźnienie hamowania mniejsze niż (AccDesired) - IncBrake(); - } - else if( OrderCurrentGet() != Disconnect ) { - // przy odłączaniu nie zwalniamy tu hamulca - if( AbsAccS < AccDesired - 0.05 ) { - // jeśli opóźnienie większe od wymaganego (z histerezą) luzowanie, gdy za dużo - // TBD: check if the condition isn't redundant with the DecBrake() code - if( /*GBH mvOccupied->BrakeCtrlPos*/BrakeCtrlPosition >= 0 ) { - if( VelDesired > 0.0 ) { - // sanity check to prevent unintended brake release on sharp slopes - DecBrake(); // tutaj zmniejszało o 1 przy odczepianiu - } - } - } - else if( mvOccupied->Handle->TimeEP ) { - if( mvOccupied->Handle->GetPos( bh_EPR ) - - mvOccupied->Handle->GetPos( bh_EPN ) < - 0.1 ) { - mvOccupied->SwitchEPBrake( 0 ); - } - else { - mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_EPN ) ); - } - } - } // order != disconnect - } // type & dt_ezt - else { - // a stara wersja w miarę dobrze działa na składy wagonowe - if( ( ( fAccGravity < -0.05 ) && ( vel < -0.1 ) ) // brake if uphill and slipping back - || ( ( AccDesired < fAccGravity - 0.1 ) && ( AbsAccS > AccDesired + fBrake_a1[ 0 ] ) ) ) { - // u góry ustawia się hamowanie na fAccThreshold - if( ( fBrakeTime < 0.0 ) - || ( AccDesired < fAccGravity - 0.5 ) - || ( /*GBH mvOccupied->BrakeCtrlPos*/BrakeCtrlPosition <= 0 ) ) { - // jeśli upłynął czas reakcji hamulca, chyba że nagłe albo luzował - if( true == IncBrake() ) { - fBrakeTime = - 3.0 - + 0.5 * ( ( - mvOccupied->BrakeDelayFlag > bdelay_G ? - mvOccupied->BrakeDelay[ 1 ] : - mvOccupied->BrakeDelay[ 3 ] ) - - 3.0 ); - // Ra: ten czas należy zmniejszyć, jeśli czas dojazdu do zatrzymania jest mniejszy - fBrakeTime *= 0.5; // Ra: tymczasowo, bo przeżyna S1 - } - } - } - if ( ( AccDesired < fAccGravity - 0.05 ) - && ( ( AccDesired - fBrake_a1[0]*0.51 ) ) - AbsAccS > 0.05 ) { - // jak hamuje, to nie tykaj kranu za często - // yB: luzuje hamulec dopiero przy różnicy opóźnień rzędu 0.2 - if( OrderCurrentGet() != Disconnect ) { - // przy odłączaniu nie zwalniamy tu hamulca - if( VelDesired > 0.0 ) { - // sanity check to prevent unintended brake release on sharp slopes - DecBrake(); // tutaj zmniejszało o 1 przy odczepianiu - } - } - fBrakeTime = ( - mvOccupied->BrakeDelayFlag > bdelay_G ? - mvOccupied->BrakeDelay[ 0 ] : - mvOccupied->BrakeDelay[ 2 ] ) - / 3.0; - fBrakeTime *= 0.5; // Ra: tymczasowo, bo przeżyna S1 - } - // stop-gap measure to ensure cars actually brake to stop even when above calculactions go awry - // instead of releasing the brakes and creeping into obstacle at 1-2 km/h - if( mvControlling->CategoryFlag == 2 ) { - if( ( VelDesired == 0.0 ) - && ( vel > VelDesired ) - && ( ActualProximityDist <= fMinProximityDist ) - && ( mvOccupied->LocalBrakePosA < 0.01 ) ) { - IncBrake(); - } - } - } - // Mietek-end1 - SpeedSet(); // ciągla regulacja prędkości -#if LOGVELOCITY - WriteLog("BrakePos=" + AnsiString(mvOccupied->BrakeCtrlPos) + ", MainCtrl=" + - AnsiString(mvControlling->MainCtrlPos)); -#endif - } // if (AIControllFlag) - } // kierunek różny od zera - else - { // tutaj, gdy pojazd jest wyłączony - if (!AIControllFlag) // jeśli sterowanie jest w gestii użytkownika - if (mvOccupied->Power24vIsAvailable) // czy użytkownik załączył baterię? - if (mvOccupied->DirActive) // czy ustawił kierunek - { // jeśli tak, to uruchomienie skanowania - CheckVehicles(); // sprawdzić skład - TableClear(); // resetowanie tabelki skanowania - PrepareEngine(); // uruchomienie - } - } - if (AIControllFlag) - { // odhamowywanie składu po zatrzymaniu i zabezpieczanie lokomotywy - if( ( ( OrderCurrentGet() & ( Disconnect | Connect ) ) == 0 ) - && ( std::abs( fAccGravity ) < 0.01 ) ) { - // przy (p)odłączaniu nie zwalniamy tu hamulca - // only do this on flats, on slopes keep applied the train brake - if( ( mvOccupied->Vel < 0.01 ) - && ( ( VelDesired == 0.0 ) - || ( AccDesired == 0.0 ) ) ) { - if( mvOccupied->LocalBrake != TLocalBrake::ManualBrake ) { - // do it only if the vehicle actually has the independent brake - if( mvOccupied->BrakeCtrlPos == mvOccupied->Handle->GetPos( bh_RP ) ) { - if( mvOccupied->LocalBrakePosA < 1.0 ) { - // dodatkowy na pozycję 1 - mvOccupied->IncLocalBrakeLevel( LocalBrakePosNo ); - if (mvOccupied->EIMCtrlEmergency) - { - mvOccupied->DecLocalBrakeLevel(1); - } - } - } - else { - mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_RP ) ); //GBH - BrakeLevelSet( gbh_RP ); - } - } - } - } - } - break; // rzeczy robione przy jezdzie - } // switch (OrderList[OrderPos]) - if (AIControllFlag) SetTimeControllers(); + // external command actions + UpdateCommand(); + // mode specific actions + handle_engine(); + handle_orders(); + // situational velocity and acceleration adjustments + pick_optimal_speed( awarenessrange ); + control_tractive_and_braking_force(); + SetTimeControllers(); + // if the route ahead is blocked we might need to head the other way + check_route_behind( 1000 ); // NOTE: legacy scan range value } // configures vehicle heating given current situation; returns: true if vehicle can be operated normally, false otherwise bool -TController::UpdateHeating() { +TController::PrepareHeating() { switch( mvControlling->EngineType ) { @@ -6885,33 +4800,73 @@ TController::UpdateHeating() { if( true == heateron ) { // make sure the water pump is running before enabling the heater if( false == mvControlling->WaterPump.is_active ) { - mvControlling->WaterPumpBreakerSwitch( true ); - mvControlling->WaterPumpSwitch( true ); + cue_action( locale::string::driver_hint_waterpumpbreakeron ); + cue_action( locale::string::driver_hint_waterpumpon ); } if( true == mvControlling->WaterPump.is_active ) { - mvControlling->WaterHeaterBreakerSwitch( true ); - mvControlling->WaterHeaterSwitch( true ); - mvControlling->WaterCircuitsLinkSwitch( true ); + cue_action( locale::string::driver_hint_waterheaterbreakeron ); + cue_action( locale::string::driver_hint_waterheateron ); + cue_action( locale::string::driver_hint_watercircuitslinkon ); } } else { // no need to heat anything up, switch the heater off - mvControlling->WaterCircuitsLinkSwitch( false ); - mvControlling->WaterHeaterSwitch( false ); - mvControlling->WaterHeaterBreakerSwitch( false ); + cue_action( locale::string::driver_hint_watercircuitslinkoff ); + cue_action( locale::string::driver_hint_waterheateroff ); + cue_action( locale::string::driver_hint_waterheaterbreakeroff ); // optionally turn off the water pump as well if( mvControlling->WaterPump.start_type != start_t::battery ) { - mvControlling->WaterPumpSwitch( false ); - mvControlling->WaterPumpBreakerSwitch( false ); + cue_action( locale::string::driver_hint_waterpumpoff ); + cue_action( locale::string::driver_hint_waterpumpbreakeroff ); } } - return ( false == lowtemperature ); + IsHeatingTemperatureTooLow = lowtemperature; + break; } default: { - return true; + IsHeatingTemperatureTooLow = false; + break; } } + // TBD, TODO: potentially pay attention to too high temperature as well? + IsHeatingTemperatureOK = ( false == IsHeatingTemperatureTooLow ); + + return IsHeatingTemperatureOK; +} + +void +TController::PrepareDirection() { + + if( iDirection == 0 ) { + // jeśli nie ma ustalonego kierunku + if( mvOccupied->Vel < 0.01 ) { // ustalenie kierunku, gdy stoi + iDirection = mvOccupied->CabActive; // wg wybranej kabiny + if( iDirection == 0 ) { + // jeśli nie ma ustalonego kierunku + if( mvOccupied->Couplers[ end::rear ].Connected == nullptr ) { + // jeśli z tyłu nie ma nic + iDirection = -1; // jazda w kierunku sprzęgu 1 + } + if( mvOccupied->Couplers[ end::front ].Connected == nullptr ) { + // jeśli z przodu nie ma nic + iDirection = 1; // jazda w kierunku sprzęgu 0 + } + } + } + else { + // ustalenie kierunku, gdy jedzie + iDirection = ( + mvOccupied->V >= 0 ? + 1 : // jazda w kierunku sprzęgu 0 + -1 ); // jazda w kierunku sprzęgu 1 + } + } + cue_action( locale::string::driver_hint_mastercontrollersetreverserunlock ); + cue_action( + iDirection > 0 ? + locale::string::driver_hint_directionforward : + locale::string::driver_hint_directionbackward ); } void TController::JumpToNextOrder() @@ -7031,9 +4986,9 @@ void TController::OrdersInit(double fVel) // ustawienie kolejności komend, niezależnie kto prowadzi OrdersClear(); // usunięcie poprzedniej tabeli OrderPush(Prepare_engine); // najpierw odpalenie silnika - if (TrainParams.TrainName == "none") + if (TrainParams.StationCount == 0) { // brak rozkładu to jazda manewrowa - if (fVel > 0.05) // typowo 0.1 oznacza gotowość do jazdy, 0.01 tylko załączenie silnika +// if (fVel > 0.05) // typowo 0.1 oznacza gotowość do jazdy, 0.01 tylko załączenie silnika OrderPush(Shunt); // jeśli nie ma rozkładu, to manewruje } else @@ -7129,13 +5084,24 @@ std::string TController::StopReasonText() const //- rozpoznają tylko zerową prędkość (jako koniec toru i brak podstaw do dalszego skanowania) //---------------------------------------------------------------------------------------------------------------------- -bool TController::IsOccupiedByAnotherConsist(TTrack *Track) +bool TController::IsOccupiedByAnotherConsist( TTrack *Track, double const Distance = 0 ) { // najpierw sprawdzamy, czy na danym torze są pojazdy z innego składu if( false == Track->Dynamics.empty() ) { for( auto dynamic : Track->Dynamics ) { if( dynamic->ctOwner != this ) { // jeśli jest jakiś cudzy to tor jest zajęty i skanowanie nie obowiązuje - return true; + if( Distance == 0 ) { + return true; + } + else { + // based on provided position of scanning vehicle and scan direction, filter out vehicles on irrelevant end + auto const scandirection { Distance > 0 ? 1 : -1 }; // positive value means scan towards point2 end of the track + auto const obstaclelocation { scandirection > 0 ? Track->Length() - dynamic->RaTranslationGet() : dynamic->RaTranslationGet() }; + // if detected vehicle is closer to the end of the track in scanned direction than we are, it means it's blocking our way + if( obstaclelocation < std::abs( Distance ) ) { + return true; + } + } } } } @@ -7178,8 +5144,8 @@ TTrack * TController::BackwardTraceRoute(double &fDistance, double &fDirection, fDistance = s; // to na tym torze stoimy return nullptr; // stop, skanowanie nie dało sensownych rezultatów } - if (Untiloccupied && IsOccupiedByAnotherConsist(Track)) - { // jak tor zajęty innym składem, to nie ma po co skanować + // jak tor zajęty innym składem, to nie ma po co skanować + if( Untiloccupied && IsOccupiedByAnotherConsist( Track, fCurrentDistance * fDirection ) ) { fDistance = s; // to na tym torze stoimy return nullptr; // stop, skanowanie nie dało sensownych rezultatów } @@ -7260,7 +5226,7 @@ void TController::SetProximityVelocity( double dist, double vel, glm::dvec3 cons */ } -TCommandType TController::BackwardScan() +TCommandType TController::BackwardScan( double const Range ) { // sprawdzanie zdarzeń semaforów z tyłu pojazdu, zwraca komendę // dzięki temu będzie można stawać za wskazanym sygnalizatorem, a zwłaszcza jeśli będzie jazda na kozioł // ograniczenia prędkości nie są wtedy istotne, również koniec toru jest do niczego nie przydatny @@ -7280,8 +5246,7 @@ TCommandType TController::BackwardScan() if (scandir != 0.0) { // skanowanie toru w poszukiwaniu eventów GetValues (PutValues nie są przydatne) // Ra: przy wstecznym skanowaniu prędkość nie ma znaczenia - double scanmax = 1000; // 1000m do tyłu, żeby widział przeciwny koniec stacji - double scandist = scanmax; // zmodyfikuje na rzeczywiście przeskanowane + double scandist = Range; // zmodyfikuje na rzeczywiście przeskanowane basic_event *e = NULL; // event potencjalnie od semafora // opcjonalnie może być skanowanie od "wskaźnika" z przodu, np. W5, Tm=Ms1, koniec toru wg drugiej osi w kierunku ruchu TTrack *scantrack = BackwardTraceRoute(scandist, scandir, pVehicles[end::front], e); @@ -7628,7 +5593,7 @@ bool TController::IsStop() const // returns most recently calculated distance to potential obstacle ahead double -TController::TrackBlock() const { +TController::TrackObstacle() const { return Obstacle.distance; } @@ -7739,3 +5704,2184 @@ std::string TController::OwnerName() const { return ( pVehicle ? pVehicle->MoverParameters->Name : "none" ); }; + +void +TController::update_timers( double dt ) { + + ElapsedTime += dt; + WaitingTime += dt; + fBrakeTime -= dt; // wpisana wartość jest zmniejszana do 0, gdy ujemna należy zmienić nastawę hamulca + if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_FS ) ) { + // brake charging timeout starts after charging ends + BrakeChargingCooldown += dt; + } + fStopTime += dt; // zliczanie czasu postoju, nie ruszy dopóki ujemne + fActionTime += dt; // czas używany przy regulacji prędkości i zamykaniu drzwi + LastReactionTime += dt; + if( ( mvOccupied->Vel < 0.05 ) + && ( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) != 0 ) ) { + IdleTime += dt; + } + else { + IdleTime = 0.0; + } + fWarningDuration -= dt; // horn activation while the value is positive +} + +void +TController::update_logs( double const dt ) { + // log vehicle data + LastUpdatedTime += dt; + if( ( WriteLogFlag ) + && ( LastUpdatedTime > deltalog ) ) { + // zapis do pliku DAT + PhysicsLog(); + LastUpdatedTime -= deltalog; + } +} + +void +TController::determine_consist_state() { + // ABu-160305 testowanie gotowości do jazdy + // Ra: przeniesione z DynObj, skład użytkownika też jest testowany, żeby mu przekazać, że ma odhamować + int index = double(BrakeAccTableSize) * (mvOccupied->Vel / mvOccupied->Vmax); + index = std::min(BrakeAccTableSize, std::max(1, index)); + fBrake_a0[0] = fBrake_a0[index]; + fBrake_a1[0] = fBrake_a1[index]; + + if ((mvOccupied->TrainType == dt_EZT) || (mvOccupied->TrainType == dt_DMU)) { + auto Coeff = clamp( mvOccupied->Vel*0.015 , 0.5 , 1.0); + fAccThreshold = fNominalAccThreshold * Coeff - fBrake_a0[BrakeAccTableSize] * (1.0 - Coeff); + } + + Ready = true; // wstępnie gotowy + fReady = 0.0; // założenie, że odhamowany + IsConsistBraked = ( + mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ? + mvOccupied->BrakePress > 2.0 : + mvOccupied->PipePress < std::max( 3.9, mvOccupied->BrakePressureActual.PipePressureVal ) + 0.1 ); + fAccGravity = 0.0; // przyspieszenie wynikające z pochylenia + IsAnyCouplerStretched = false; + IsAnyDoorOpen[ side::right ] = IsAnyDoorOpen[ side::left ] = false; + IsAnyDoorPermitActive[ side::right ] = IsAnyDoorPermitActive[ side::left ] = false; + ConsistShade = 0.0; + auto *p { pVehicles[ end::front ] }; // pojazd na czole składu + double dy; // składowa styczna grawitacji, w przedziale <0,1> + while (p) + { // sprawdzenie odhamowania wszystkich połączonych pojazdów + auto *vehicle { p->MoverParameters }; + auto const bp { std::max( 0.0, vehicle->BrakePress - ( vehicle->SpeedCtrlUnit.Parking ? vehicle->MaxBrakePress[ 0 ] * vehicle->StopBrakeDecc : 0.0 ) ) }; + if (Ready) { + // bo jak coś nie odhamowane, to dalej nie ma co sprawdzać + if( ( TestFlag( vehicle->Hamulec->GetBrakeStatus(), b_hld ) ) + || ( ( vehicle->Vel < 1.0 ) && ( bp > 0.4 ) ) ) { // ensure the brakes are sufficiently released when starting to move + Ready = false; + } + // Ra: odluźnianie przeładowanych lokomotyw, ciągniętych na zimno - prowizorka... + if( bp >= 0.4 ) { // wg UIC określone sztywno na 0.04 + if( AIControllFlag || Global.AITrainman ) { + if( ( BrakeCtrlPosition == gbh_RP ) // jest pozycja jazdy + && ( false == TestFlag( vehicle->Hamulec->GetBrakeStatus(), b_dmg ) ) // brake isn't broken + && ( vehicle->PipePress - 5.0 > -0.1 ) // jeśli ciśnienie jak dla jazdy + && ( vehicle->Hamulec->GetCRP() > vehicle->PipePress + 0.12 ) ) { // za dużo w zbiorniku + // indywidualne luzowanko + vehicle->BrakeReleaser( 1 ); + } + } + } + } + fReady = std::max( bp, fReady ); // szukanie najbardziej zahamowanego + if( ( dy = p->VectorFront().y ) != 0.0 ) { + // istotne tylko dla pojazdów na pochyleniu + // ciężar razy składowa styczna grawitacji + fAccGravity -= vehicle->TotalMassxg * dy * ( p->DirectionGet() == iDirection ? 1 : -1 ); + } + // check coupler state + IsAnyCouplerStretched = + IsAnyCouplerStretched + || ( vehicle->Couplers[ end::front ].stretch_duration > 0.0 ) + || ( vehicle->Couplers[ end::rear ].stretch_duration > 0.0 ); + // check door state + { + auto const switchsides { p->DirectionGet() != iDirection }; + auto const &rightdoor { vehicle->Doors.instances[ ( switchsides ? side::left : side::right ) ] }; + auto const &leftdoor { vehicle->Doors.instances[ ( switchsides ? side::right : side::left ) ] }; + if( vehicle->Doors.close_control != control_t::autonomous ) { + IsAnyDoorOpen[ side::right ] |= ( false == rightdoor.is_closed ); + IsAnyDoorOpen[ side::left ] |= ( false == leftdoor.is_closed ); + } + if( vehicle->Doors.permit_needed ) { + IsAnyDoorPermitActive[ side::right ] |= rightdoor.open_permit; + IsAnyDoorPermitActive[ side::left ] |= leftdoor.open_permit; + } + } + // measure lighting level + // TBD: apply weight (multiplier) to partially lit vehicles? + ConsistShade += ( p->fShade > 0.0 ? p->fShade : 1.0 ); + p = p->Next(); // pojazd podłączony z tyłu (patrząc od czoła) + } + // calculate average amount of received sunlight + ConsistShade /= iVehicles; + + // test state of main devices in all vehicles under control + IsAnyConverterOverloadRelayOpen = false; + IsAnyMotorOverloadRelayOpen = false; + IsAnyGroundRelayOpen = false; + IsAnyLineBreakerOpen = false; + // HACK: enable a make-believe compressor in all cars + // TBD, TODO: replace with a more flexible vehicle readiness check in PrepareEngine() + IsAnyCompressorEnabled = is_car(); + IsAnyCompressorExplicitlyEnabled = false; + IsAnyConverterEnabled = false; + IsAnyConverterExplicitlyEnabled = false; + + pVehicle->for_each( + coupling::control, + [this]( TDynamicObject * Vehicle ) { + auto const *vehicle { Vehicle->MoverParameters }; + IsAnyConverterOverloadRelayOpen |= vehicle->ConvOvldFlag; + IsAnyMotorOverloadRelayOpen |= vehicle->FuseFlag; + IsAnyGroundRelayOpen |= !( vehicle->GroundRelay ); + IsAnyCompressorEnabled |= ( vehicle->CompressorSpeed > 0.0 ? ( vehicle->CompressorAllow || vehicle->CompressorStart == start_t::automatic ) && ( vehicle->CompressorAllowLocal ) : false ); + IsAnyCompressorExplicitlyEnabled |= ( vehicle->CompressorSpeed > 0.0 ? ( vehicle->CompressorAllow && vehicle->CompressorAllowLocal ) : false ); + IsAnyConverterEnabled |= ( vehicle->ConverterAllow || vehicle->ConverterStart == start_t::automatic ) && ( vehicle->ConverterAllowLocal ); + IsAnyConverterExplicitlyEnabled |= ( vehicle->ConverterAllow && vehicle->ConverterAllowLocal ); + if( vehicle->Power > 0.01 ) { + IsAnyLineBreakerOpen |= !( vehicle->Mains ); } } ); + + // siłę generują pojazdy na pochyleniu ale działa ona całość składu, więc a=F/m + fAccGravity *= ( iDirection >= 0 ? 1 : -1 ); + fAccGravity /= fMass; + { + auto absaccs { fAccGravity }; // Ra 2014-03: jesli skład stoi, to działa na niego składowa styczna grawitacji + if( mvOccupied->Vel > 0.01 ) { + absaccs = 0; + auto *d = pVehicles[ end::front ]; // pojazd na czele składu + while( d ) { + absaccs += d->MoverParameters->TotalMass * d->MoverParameters->AccS * ( d->DirectionGet() == iDirection ? 1 : -1 ); + d = d->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła) + } + absaccs *= ( iDirection >= 0 ? 1 : -1 ); + absaccs /= fMass; + } + AbsAccS = absaccs; + } +#if LOGVELOCITY + WriteLog( "Vel=" + to_string( DirectionalVel() ) + ", AbsAccS=" + to_string( AbsAccS ) + ", AccGrav=" + to_string( fAccGravity ) ); +#endif + + if( ( !Ready ) // v367: jeśli wg powyższych warunków skład nie jest odhamowany + && ( fAccGravity < -0.05 ) // jeśli ma pod górę na tyle, by się stoczyć + && ( fReady < 0.8 ) ) { // delikatniejszy warunek, obejmuje wszystkie wagony + Ready = true; //żeby uznać za odhamowany + } + // second pass, for diesel engines verify the (live) engines are fully started + // TODO: cache presence of diesel engines in the consist, to skip this test if there isn't any + p = pVehicles[ end::front ]; // pojazd na czole składu + while( ( true == Ready ) + && ( p != nullptr ) ) { + + auto const *vehicle { p->MoverParameters }; + + if( has_diesel_engine() ) { + + Ready = ( + ( vehicle->Vel > 0.5 ) // already moving + || ( false == vehicle->Mains ) // deadweight vehicle + || ( vehicle->enrot > 0.8 * ( + vehicle->EngineType == TEngineType::DieselEngine ? + vehicle->dizel_nmin : + vehicle->DElist[ 0 ].RPM / 60.0 ) ) ); + } + p = p->Next(); // pojazd podłączony z tyłu (patrząc od czoła) + } +} + +void +TController::control_wheelslip() { + // wheel slip + if( mvControlling->SlippingWheels ) { + cue_action( locale::string::driver_hint_sandingon ); // piasku! + cue_action( locale::string::driver_hint_tractiveforcedecrease ); + cue_action( locale::string::driver_hint_brakingforcedecrease ); + cue_action( locale::string::driver_hint_antislip ); + ++iDriverFailCount; + } + else { + // deactivate sandbox if we aren't slipping + if( mvControlling->SandDose ) { + cue_action( locale::string::driver_hint_sandingoff ); + } + } +} + +void +TController::control_pantographs() { + + if( mvPantographUnit->EnginePowerSource.SourceType != TPowerSource::CurrentCollector ) { return; } + + if( ( false == mvPantographUnit->PantAutoValve ) + && ( mvOccupied->ScndPipePress > 4.3 ) ) { + // gdy główna sprężarka bezpiecznie nabije ciśnienie to można przestawić kurek na zasilanie pantografów z głównej pneumatyki + cue_action( locale::string::driver_hint_pantographairsourcesetmain ); + } + + // uśrednione napięcie sieci: przy spadku poniżej wartości minimalnej opóźnić rozruch o losowy czas + fVoltage = 0.5 * (fVoltage + std::max( mvControlling->GetTrainsetHighVoltage(), mvControlling->PantographVoltage ) ); + if( fVoltage < mvControlling->EnginePowerSource.CollectorParameters.MinV ) { + // gdy rozłączenie WS z powodu niskiego napięcia + if( fActionTime >= PrepareTime ) { + // jeśli czas oczekiwania nie został ustawiony, losowy czas oczekiwania przed ponownym załączeniem jazdy + fActionTime = -2.0 - Random( 10 ); + } + } + + // raise/lower pantographs as needed + auto const useregularpantographlayout { + ( pVehicle->NextC( coupling::control ) == nullptr ) // standalone + || ( mvControlling->TrainType == dt_EZT ) // special case + || ( mvControlling->TrainType == dt_ET41 ) }; // special case + + if( mvOccupied->Vel > 0.05 ) { + + if( ( fOverhead2 >= 0.0 ) || iOverheadZero ) { + // jeśli jazda bezprądowa albo z opuszczonym pantografem + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + } + + if( ( fOverhead2 > 0.0 ) || iOverheadDown ) { + // jazda z opuszczonymi pantografami + if( mvPantographUnit->Pantographs[ end::front ].is_active ) { + cue_action( locale::string::driver_hint_frontpantographvalveoff ); + } + if( mvPantographUnit->Pantographs[ end::rear ].is_active ) { + cue_action( locale::string::driver_hint_rearpantographvalveoff ); + } + } + else { + // jeśli nie trzeba opuszczać pantografów + // TODO: check if faction alone reduces frequency enough + if( fActionTime > 0.0 ) { + if( mvOccupied->AIHintPantstate == 0 ) { + // jazda na tylnym + if( ( iDirection >= 0 ) && ( useregularpantographlayout ) ) { + // jak jedzie w kierunku sprzęgu 0 + if( ( mvPantographUnit->PantRearVolt == 0.0 ) + // filter out cases with single _other_ working pantograph so we don't try to raise something we can't + && ( ( mvPantographUnit->PantographVoltage == 0.0 ) + || ( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) ) ) { + cue_action( locale::string::driver_hint_rearpantographvalveon ); + } + } + else { + // jak jedzie w kierunku sprzęgu 0 + if( ( mvPantographUnit->PantFrontVolt == 0.0 ) + // filter out cases with single _other_ working pantograph so we don't try to raise something we can't + && ( ( mvPantographUnit->PantographVoltage == 0.0 ) + || ( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) ) ) { + cue_action( locale::string::driver_hint_frontpantographvalveon ); + } + } + + if( mvOccupied->Vel > 5 ) { + // opuszczenie przedniego po rozpędzeniu się o ile jest więcej niż jeden + if( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) { + if( ( iDirection >= 0 ) && ( useregularpantographlayout ) ) // jak jedzie w kierunku sprzęgu 0 + { // poczekać na podniesienie tylnego + if( ( mvPantographUnit->PantFrontVolt != 0.0 ) + && ( mvPantographUnit->PantRearVolt != 0.0 ) ) { // czy jest napięcie zasilające na tylnym? + cue_action( locale::string::driver_hint_frontpantographvalveoff ); // opuszcza od sprzęgu 0 + } + } + else { // poczekać na podniesienie przedniego + if( ( mvPantographUnit->PantRearVolt != 0.0 ) + && ( mvPantographUnit->PantFrontVolt != 0.0 ) ) { // czy jest napięcie zasilające na przednim? + cue_action( locale::string::driver_hint_rearpantographvalveoff ); // opuszcza od sprzęgu 1 + } + } + } + } + + } + else { + // use suggested pantograph setup + if( mvOccupied->Vel > 5 ) { + auto const pantographsetup{ mvOccupied->AIHintPantstate }; + cue_action( + ( pantographsetup & ( 1 << 0 ) ? + locale::string::driver_hint_frontpantographvalveon : + locale::string::driver_hint_frontpantographvalveoff ) ); + cue_action( + ( pantographsetup & ( 1 << 1 ) ? + locale::string::driver_hint_rearpantographvalveon : + locale::string::driver_hint_rearpantographvalveoff ) ); + } + } + } + } + } + else { + if( ( mvOccupied->AIHintPantUpIfIdle ) + && ( IdleTime > 45.0 ) + // NOTE: abs(stoptime) covers either at least 15 sec remaining for a scheduled stop, or 15+ secs spent at a basic stop + && ( std::abs( fStopTime ) > 15.0 ) ) { + // spending a longer at a stop, raise also front pantograph + if( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) { + if( ( iDirection >= 0 ) && ( useregularpantographlayout ) ) { + // jak jedzie w kierunku sprzęgu 0 + if( mvPantographUnit->PantFrontVolt == 0.0 ) { + cue_action( locale::string::driver_hint_frontpantographvalveon, 5 ); // discard the hint if speed exceeds 5 km/h + } + } + else { + if( mvPantographUnit->PantRearVolt == 0.0 ) { + cue_action( locale::string::driver_hint_rearpantographvalveon, 5 ); // discard the hint if speed exceeds 5 km/h + } + } + } + } + } +} + +void +TController::control_horns( double const Timedelta ) { + // horn control + if( fWarningDuration > 0.0 ) { + // jeśli pozostało coś do wytrąbienia trąbienie trwa nadal + if( mvOccupied->WarningSignal == 0 ) { + cue_action( locale::string::driver_hint_hornon, 1 ); // low tone horn by default + } + } + else { + if( mvOccupied->WarningSignal != 0 ) { + cue_action( locale::string::driver_hint_hornoff ); // a tu się kończy + } + } + if( mvOccupied->Vel >= 5.0 ) { + // jesli jedzie, można odblokować trąbienie, bo się wtedy nie włączy + iDrivigFlags &= ~moveStartHornDone; // zatrąbi dopiero jak następnym razem stanie + iDrivigFlags |= moveStartHorn; // i trąbić przed następnym ruszeniem + } + + if( ( true == TestFlag( iDrivigFlags, moveStartHornNow ) ) + && ( true == Ready ) + && ( true == iEngineActive ) + && ( mvControlling->MainCtrlPowerPos() > 0 ) ) { + // uruchomienie trąbienia przed ruszeniem + fWarningDuration = 0.3; // czas trąbienia + cue_action( locale::string::driver_hint_hornon, pVehicle->iHornWarning ); // wysokość tonu (2=wysoki) + iDrivigFlags |= moveStartHornDone; // nie trąbić aż do ruszenia + iDrivigFlags &= ~moveStartHornNow; // trąbienie zostało zorganizowane + } +} + +void +TController::control_security_system( double const Timedelta ) { + + if( mvOccupied->SecuritySystem.Status >= s_aware ) { + // jak zadziałało CA/SHP + cue_action( locale::string::driver_hint_securitysystemreset ); // to skasuj + if( ( BrakeCtrlPosition == 0 ) // TODO: verify whether it's 0 in all vehicle types + && ( AccDesired > 0.0 ) + && ( ( TestFlag( mvOccupied->SecuritySystem.Status, s_SHPebrake ) ) + || ( TestFlag( mvOccupied->SecuritySystem.Status, s_CAebrake ) ) ) ) { + cue_action( locale::string::driver_hint_trainbrakerelease ); + } + } + // basic emergency stop handling, while at it + if( ( true == mvOccupied->RadioStopFlag ) // radio-stop + && ( mvOccupied->Vel < 0.01 ) // and actual stop + && ( true == mvOccupied->Radio ) ) { // and we didn't touch the radio yet + // turning off the radio should reset the flag, during security system check + if( m_radiocontroltime > 5.0 ) { // arbitrary delay between stop and disabling the radio + cue_action( locale::string::driver_hint_radiooff ); + } + else { + m_radiocontroltime += Timedelta; + } + } + if( ( false == mvOccupied->Radio ) + && ( false == mvOccupied->RadioStopFlag ) ) { + // otherwise if it's safe to do so, turn the radio back on + // arbitrary 5 sec delay before switching radio back on + m_radiocontroltime = std::min( m_radiocontroltime, 5.0 ); + if( m_radiocontroltime < 0.0 ) { + cue_action( locale::string::driver_hint_radioon ); + } + else { + m_radiocontroltime -= Timedelta; + } + } +} + +void +TController::control_handles() { + + switch( mvControlling->EngineType ) { + case TEngineType::ElectricSeriesMotor: { + // styczniki liniowe rozłączone yBARC + if( ( false == mvControlling->StLinFlag ) + && ( false == mvControlling->DelayCtrlFlag ) + && ( mvControlling->MainCtrlPowerPos() > 1 ) ) { + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + } + // if the power station is heavily burdened drop down to series mode to reduce the load + auto const useseriesmodevoltage { + interpolate( + mvControlling->EnginePowerSource.CollectorParameters.MinV, + mvControlling->EnginePowerSource.CollectorParameters.MaxV, + ( IsHeavyCargoTrain ? 0.35 : 0.40 ) ) }; + + if( fVoltage <= useseriesmodevoltage ) { + cue_action( locale::string::driver_hint_mastercontrollersetseriesmode ); + } + // sanity check + if( ( false == Ready ) + && ( mvControlling->MainCtrlPowerPos() > 1 ) ) { + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + } + break; + } + case TEngineType::DieselElectric: { + // styczniki liniowe rozłączone yBARC + if( ( false == mvControlling->StLinFlag ) + && ( false == mvControlling->DelayCtrlFlag ) + && ( mvControlling->MainCtrlPowerPos() > 1 ) ) { + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + } + // sanity check + if( ( false == Ready ) + && ( mvControlling->MainCtrlPowerPos() > 1 ) ) { + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + } + break; + } + default: { + break; + } } // enginetype +} + +// activate consist lamp codes +void +TController::control_lights() { + + if( is_train() ) { + // jeśli jazda pociągowa + if( true == TestFlag( OrderCurrentGet(), Obey_train ) ) { + // head lights + if( m_lighthints[ end::front ] == -1 ) { + cue_action( locale::string::driver_hint_headcodepc1 ); + } + else if( m_lighthints[ end::front ] == ( light::redmarker_left | light::headlight_right | light::headlight_upper ) ) { + cue_action( locale::string::driver_hint_headcodepc2 ); + } + else { + // custom light pattern + if( AIControllFlag ) { + pVehicles[ end::front ]->RaLightsSet( m_lighthints[ end::front ], -1 ); + } + } + // tail lights + if( m_lighthints[ end::rear ] == -1 ) { + cue_action( locale::string::driver_hint_headcodepc5 ); + } + else { + if( AIControllFlag ) { + pVehicles[ end::rear ]->RaLightsSet( -1, m_lighthints[ end::rear ] ); + } + } + } + // for shunting mode + else if( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect ) ) { + cue_action( locale::string::driver_hint_headcodetb1 ); + } + } + else if( is_car() ) { + Lights( + light::headlight_left | light::headlight_right, + light::redmarker_left | light::redmarker_right ); + } +} + +void +TController::control_compartment_lights() { + // compartment lights + if( mvOccupied->CompartmentLights.start_type != start_t::manual ) { return; } + + auto const currentlightstate { mvOccupied->CompartmentLights.is_enabled }; + auto const lightlevel { Global.fLuminance * ConsistShade }; + auto const desiredlightstate{ ( + currentlightstate ? + lightlevel < 0.40 : // turn off if lighting level goes above 0.4 + lightlevel < 0.35 ) }; // turn on if lighting level goes below 0.35 + if( desiredlightstate != currentlightstate ) { + cue_action( ( + desiredlightstate ? + locale::string::driver_hint_consistlightson : + locale::string::driver_hint_consistlightsoff ) ); + } +} + +void +TController::control_doors() { + + if( false == doors_open() ) { return; } + // jeżeli jedzie + // nie zamykać drzwi przy drganiach, bo zatrzymanie na W4 akceptuje niewielkie prędkości + if( mvOccupied->Vel > 1.0 ) { + Doors( false ); + return; + } + // HACK: crude way to deal with automatic door opening on W4 preventing further ride + // for human-controlled vehicles with no door control and dynamic brake auto-activating with door open + // TODO: check if this situation still happens and the hack is still needed + if( false == AIControllFlag ) { + // for diesel engines react when engine is put past idle revolutions + // for others straightforward master controller check + if( ( mvControlling->EngineType == TEngineType::DieselEngine ? + mvControlling->RList[ mvControlling->MainCtrlPos ].Mn > 0 : + mvControlling->MainCtrlPowerPos() > 0 ) ) { + Doors( false ); + return; + } + } +} + +void +TController::UpdateCommand() { + // TBD, TODO: rework recognizecommand() to use hint system, remove driver type condition? + if (AIControllFlag) { + if (mvOccupied->CommandIn.Command != "") + if( !mvOccupied->RunInternalCommand() ) { + // rozpoznaj komende bo lokomotywa jej nie rozpoznaje + RecognizeCommand(); // samo czyta komendę wstawioną do pojazdu? + } + } +} + +void +TController::UpdateNextStop() { + + if( ( fLastStopExpDist > 0.0 ) + && ( mvOccupied->DistCounter > fLastStopExpDist ) ) { + // zaktualizować wyświetlanie rozkładu + iStationStart = TrainParams.StationIndex; + fLastStopExpDist = -1.0; // usunąć licznik + if( true == m_makenextstopannouncement ) { + announce( announcement_t::next ); + m_makenextstopannouncement = false; // keep next stop announcements suppressed until another scheduled stop + } + } +} + +void +TController::determine_braking_distance() { + // przybliżona droga hamowania + // NOTE: we're ensuring some minimal braking distance to reduce ai flipping states between starting and braking + auto const velceil { std::max( 2.0, std::ceil( mvOccupied->Vel ) ) }; + fBrakeDist = fDriverBraking * velceil * ( 40.0 + velceil ); + if( fMass > 1000000.0 ) { + // korekta dla ciężkich, bo przeżynają - da to coś? + fBrakeDist *= 2.0; + } + if( ( -fAccThreshold > 0.05 ) + && ( mvOccupied->CategoryFlag == 1 ) ) { + fBrakeDist = velceil * velceil / 25.92 / -fAccThreshold; + } + if( mvOccupied->BrakeDelayFlag == bdelay_G ) { + // dla nastawienia G koniecznie należy wydłużyć drogę na czas reakcji + fBrakeDist += 2 * velceil; + } + if( ( mvOccupied->Vel > 15.0 ) + && ( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) + && ( ( mvControlling->TrainType & dt_EZT ) != 0 ) ) { + // HACK: make the induction motor powered EMUs start braking slightly earlier + fBrakeDist += 10.0; + } +/* + // take into account effect of gravity (but to stay on safe side of calculations, only downhill) + if( fAccGravity > 0.025 ) { + fBrakeDist *= ( 1.0 + fAccGravity ); + // TBD: use version which shortens route going uphill, too + //fBrakeDist = std::max( fBrakeDist, fBrakeDist * ( 1.0 + fAccGravity ) ); + } +*/ +} + +void +TController::scan_route( double const Range ) { + // Ra 2015-01: przy dłuższej drodze skanowania AI jeździ spokojniej + // 2. Sprawdzić, czy tabelka pokrywa założony odcinek (nie musi, jeśli jest STOP). + // 3. Sprawdzić, czy trajektoria ruchu przechodzi przez zwrotnice - jeśli tak, to sprawdzić, czy stan się nie zmienił. + // 4. Ewentualnie uzupełnić tabelkę informacjami o sygnałach i ograniczeniach, jeśli się "zużyła". + TableCheck( Range ); // wypełnianie tabelki i aktualizacja odległości + // 5. Sprawdzić stany sygnalizacji zapisanej w tabelce, wyznaczyć prędkości. + // 6. Z tabelki wyznaczyć krytyczną odległość i prędkość (najmniejsze przyspieszenie). + // 7. Jeśli jest inny pojazd z przodu, ewentualnie skorygować odległość i prędkość. + // 8. Ustalić częstotliwość świadomości AI (zatrzymanie precyzyjne - częściej, brak atrakcji - rzadziej). +} + +// check for potential collisions +void +TController::scan_obstacles( double const Range ) { + // HACK: vehicle order in the consist is based on intended travel direction + // if our actual travel direction doesn't match that, we should be scanning from the other end of the consist + // we cast to int to avoid getting confused by microstutters + auto *frontvehicle { pVehicles[ ( static_cast( mvOccupied->V ) * iDirection >= 0 ? end::front : end::rear ) ] }; + + int routescandirection; + // for moving vehicle determine heading from velocity; for standing fall back on the set direction + if( ( std::abs( frontvehicle->MoverParameters->V ) > 0.5 ? // ignore potential micro-stutters in oposite direction during "almost stop" + frontvehicle->MoverParameters->V > 0.0 : + ( pVehicle->DirectionGet() == frontvehicle->DirectionGet() ? + iDirection >= 0 : + iDirection <= 0 ) ) ) { + // towards coupler 0 + routescandirection = end::front; + } + else { + // towards coupler 1 + routescandirection = end::rear; + } +/* + if( pVehicle->MoverParameters->CabOccupied < 0 ) { + // flip the scan direction in the rear cab + routescandirection ^= routescandirection; + } +*/ + Obstacle = neighbour_data(); + auto const obstaclescanrange { std::max( ( is_car() ? 250.0 : 1000.0 ), Range ) }; + auto const lookup { frontvehicle->find_vehicle( routescandirection, obstaclescanrange ) }; + + if( std::get( lookup ) == true ) { + + Obstacle.vehicle = std::get( lookup ); + Obstacle.vehicle_end = std::get( lookup ); + Obstacle.distance = std::get( lookup ); + + if( Obstacle.distance < ( is_car() ? 50 : 100 ) ) { + // at short distances (re)calculate range between couplers directly + Obstacle.distance = TMoverParameters::CouplerDist( frontvehicle->MoverParameters, Obstacle.vehicle->MoverParameters ); + } + } +} + +void +TController::determine_proximity_ranges() { + // ustalenie dystansów w pozostałych przypadkach + switch (OrderCurrentGet()) + { + case Connect: { + // podłączanie do składu + if (iDrivigFlags & moveConnect) { + // jeśli stanął już blisko, unikając zderzenia i można próbować podłączyć + fMinProximityDist = -1.0; + fMaxProximityDist = 0.0; //[m] dojechać maksymalnie + fVelPlus = 1.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania + fVelMinus = 0.5; // margines prędkości powodujący załączenie napędu + } + else { + // jak daleko, to jazda jak dla Shunt na kolizję + fMinProximityDist = 2.0; + fMaxProximityDist = 5.0; //[m] w takim przedziale odległości powinien stanąć + fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania + fVelMinus = 1.0; // margines prędkości powodujący załączenie napędu + } + break; + } + case Disconnect: { + // 20.07.03 - manewrowanie wagonami + fMinProximityDist = 1.0; + fMaxProximityDist = 10.0; //[m] + fVelPlus = 1.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania + fVelMinus = 0.5; // margines prędkości powodujący załączenie napędu + break; + } + case Shunt: { + // na jaką odleglość i z jaką predkością ma podjechać + // TODO: test if we can use the distances calculation from obey_train + fMinProximityDist = std::min( 5 + iVehicles, 25 ); + fMaxProximityDist = std::min( 10 + iVehicles, 50 ); + // HACK: modern vehicles might brake slower at low speeds, increase safety margin as crude counter + if( mvControlling->EIMCtrlType > 0 ) { + fMinProximityDist += 5.0; + fMaxProximityDist += 5.0; + } + // take into account weather conditions + if( ( Global.FrictionWeatherFactor < 1.f ) + && ( iVehicles > 1 ) ) { + fMinProximityDist += 5.0; + fMaxProximityDist += 5.0; + } + fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania + // margines prędkości powodujący załączenie napędu + // były problemy z jazdą np. 3km/h podczas ładowania wagonów + fVelMinus = std::min( 0.1 * fShuntVelocity, 3.0 ); + break; + } + case Loose_shunt: { + fMinProximityDist = -1.0; + fMaxProximityDist = 0.0; //[m] dojechać maksymalnie + fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania + fVelMinus = 0.5; // margines prędkości powodujący załączenie napędu + break; + } + case Obey_train: { + // na jaka odleglosc i z jaka predkoscia ma podjechac do przeszkody + // jeśli pociąg + if( is_train() ) { + fMinProximityDist = clamp( 5 + iVehicles, 10, 15 ); + fMaxProximityDist = clamp( 10 + iVehicles, 15, 40 ); + + if( IsCargoTrain ) { + // increase distances for cargo trains to take into account slower reaction to brakes + fMinProximityDist += 10.0; + fMaxProximityDist += 10.0; +/* + if( IsHeavyCargoTrain ) { + // cargo trains with high braking threshold may require even larger safety margin + fMaxProximityDist += 20.0; + } +*/ + } + + if( ( Global.FrictionWeatherFactor < 1.f ) + && ( iVehicles > 1 ) ) { + // take into account weather conditions + fMinProximityDist += 5.0; + fMaxProximityDist += 5.0; + } + + if( mvOccupied->Vel < 0.1 ) { + // jak stanie za daleko, to niech nie dociąga paru metrów + fMaxProximityDist = 50.0; + } + + if( iDrivigFlags & moveLate ) { + // jeśli spóźniony, to gna + fVelMinus = 1.0; + // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania + fVelPlus = 5.0; + } + else { + // gdy nie musi się sprężać + // margines prędkości powodujący załączenie napędu; min 1.0 żeby nie ruszał przy 0.1 + fVelMinus = clamp( std::round( 0.05 * VelDesired ), 1.0, 5.0 ); + // normalnie dopuszczalne przekroczenie to 5% prędkości ale nie więcej niż 5km/h + // bottom margin raised to 2 km/h to give the AI more leeway at low speed limits + fVelPlus = clamp( std::ceil( 0.05 * VelDesired ), 2.0, 5.0 ); + } + } + // samochod (sokista też) + else { + fMinProximityDist = std::max( 3.5, mvOccupied->Vel * 0.2 ); + fMaxProximityDist = std::max( 9.5, mvOccupied->Vel * 0.375 ); //[m] + if( Global.FrictionWeatherFactor < 1.f ) { + // take into account weather conditions + fMinProximityDist += 0.75; + fMaxProximityDist += 0.75; + } + // margines prędkości powodujący załączenie napędu + fVelMinus = 2.0; + // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania + fVelPlus = std::min( 10.0, mvOccupied->Vel * 0.1 ); + } + break; + } + case Bank: { + // TODO: implement + break; + } + default: { + fMinProximityDist = 5.0; + fMaxProximityDist = 10.0; //[m] + fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania + fVelMinus = 5.0; // margines prędkości powodujący załączenie napędu + } + } // switch OrderList[OrderPos] +} + +void +TController::check_departure() { + + if(( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank | Connect ) ) == 0 ) { return; } + // odjechać sam może tylko jeśli jest w trybie jazdy + // automatyczne ruszanie po odstaniu albo spod SBL + if( ( VelSignal == 0.0 ) + && ( WaitingTime > 0.0 ) + && ( mvOccupied->RunningTrack.Velmax != 0.0 ) ) { + // jeśli stoi, a upłynął czas oczekiwania i tor ma niezerową prędkość + if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) + && ( iDrivigFlags & moveStopHere ) ) { + // zakaz ruszania z miejsca bez otrzymania wolnej drogi + WaitingTime = -WaitingExpireTime; + } + else { + if( mvOccupied->CategoryFlag & 1 ) { // jeśli pociąg + PrepareDirection(); // zmieni ustawiony kierunek + SetVelocity( 20, 20 ); // jak się nastał, to niech jedzie 20km/h + WaitingTime = 0.0; + fWarningDuration = 1.5; // a zatrąbić trochę + } + else { // samochód ma stać, aż dostanie odjazd, chyba że stoi przez kolizję + if( ( eStopReason == stopBlock ) + && ( Obstacle.distance > fDriverDist ) ) { + PrepareDirection(); // zmieni ustawiony kierunek + SetVelocity( -1, -1 ); // jak się nastał, to niech jedzie + WaitingTime = 0.0; + } + } + } + } + else if( ( VelSignal == 0.0 ) && ( VelNext > 0.0 ) && ( mvOccupied->Vel < 1.0 ) ) { + if( ( iCoupler > 0 ) || ( ( iDrivigFlags & moveStopHere ) == 0 ) ) { + // Ra: tu jest coś nie tak, bo bez tego warunku ruszało w manewrowym !!!! + SetVelocity( VelNext, VelNext, stopSem ); // omijanie SBL + } + } +} + +void +TController::UpdateChangeDirection() { + // TODO: rework into driver mode independent routine + if( ( true == AIControllFlag) + && ( true == TestFlag( OrderCurrentGet(), Change_direction ) ) ) { + // sprobuj zmienic kierunek (może być zmieszane z jeszcze jakąś komendą) + if( mvOccupied->Vel < 0.1 ) { + // jeśli się zatrzymał, to zmieniamy kierunek jazdy, a nawet kabinę/człon + Activation(); // ustawienie zadanego wcześniej kierunku i ewentualne przemieszczenie AI + PrepareEngine(); + JumpToNextOrder(); // następnie robimy, co jest do zrobienia (Shunt albo Obey_train) + if( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect ) ) { + // jeśli dalej mamy manewry + if( false == TestFlag( iDrivigFlags, moveStopHere ) ) { + // o ile nie ma stać w miejscu, + // jechać od razu w przeciwną stronę i nie trąbić z tego tytułu: + iDrivigFlags &= ~moveStartHorn; + SetVelocity( fShuntVelocity, fShuntVelocity ); + } + } + } + } // Change_direction (tylko dla AI) +} + +void +TController::UpdateLooseShunt() { + // when loose shunting try to detect situations where engaged brakes in a consist to be pushed prevent movement + // TODO: run also for potential settings-based virtual assistant + auto const autobrakerelease { ( true == AIControllFlag ) }; + + if( ( autobrakerelease ) + && ( Obstacle.distance < 1.0 ) + && ( AccDesired > 0.1 ) + && ( mvOccupied->Vel < 1.0 ) ) { + + auto *vehicle { Obstacle.vehicle }; + auto const direction { ( vehicle->Prev() != nullptr ? end::front : end::rear ) }; + while( vehicle != nullptr ) { + if( vehicle->MoverParameters->BrakePress > 0.2 ) { + vehicle->MoverParameters->BrakeLevelSet( 0 ); // hamulec na zero, aby nie hamował + vehicle->MoverParameters->BrakeReleaser( 1 ); // wyluzuj pojazd, aby dało się dopychać + } + // NOTE: we trust the consist to be arranged in a valid chain + // TBD, TODO: traversal direction validation? + vehicle = ( direction == end::front ? vehicle->Prev() : vehicle->Next() ); + } + } +} + +void +TController::UpdateObeyTrain() { + + UpdateNextStop(); + + if( ( ( iDrivigFlags & moveGuardSignal ) != 0 ) + && ( VelDesired > 0.0 ) ) { + // komunikat od kierownika tu, bo musi być wolna droga i odczekany czas stania + iDrivigFlags &= ~moveGuardSignal; // tylko raz nadać + if( false == tsGuardSignal.empty() ) { + tsGuardSignal.stop(); + // w zasadzie to powinien mieć flagę, czy jest dźwiękiem radiowym, czy bezpośrednim + // albo trzeba zrobić dwa dźwięki, jeden bezpośredni, słyszalny w + // pobliżu, a drugi radiowy, słyszalny w innych lokomotywach + // na razie zakładam, że to nie jest dźwięk radiowy, bo trzeba by zrobić + // obsługę kanałów radiowych itd. + if( iGuardRadio == 0 ) { + // jeśli nie przez radio + tsGuardSignal.owner( pVehicle ); + // place virtual conductor some distance away + tsGuardSignal.offset( { pVehicle->MoverParameters->Dim.W * -0.75f, 1.7f, std::min( -20.0, -0.2 * fLength ) } ); + tsGuardSignal.play( sound_flags::exclusive ); + // NOTE: we can't rely on is_playing() check as sound playback is based on distance from local camera + fActionTime = -5.0; // niech trochę potrzyma + } + else { + // radio message + tsGuardSignal.owner( pVehicle ); +/* + tsGuardSignal.offset( { 0.f, 2.f, pVehicle->MoverParameters->Dim.L * 0.4f * ( pVehicle->MoverParameters->CabOccupied < 0 ? -1 : 1 ) } ); + tsGuardSignal.play( sound_flags::exclusive ); +*/ + simulation::radio_message( &tsGuardSignal, iGuardRadio ); + // NOTE: we can't rely on is_playing() check as sound playback is based on distance from local camera + fActionTime = -5.0; // niech trochę potrzyma + } + } + } +} + +void +TController::UpdateConnect() { + // podłączanie do składu + if (iDrivigFlags & moveConnect) { + // sprzęgi sprawdzamy w pierwszej kolejności, bo jak połączony, to koniec + auto *vehicle { pVehicles[ end::front ] }; + auto *vehicleparameters { vehicle->MoverParameters }; + auto const couplingend { ( vehicle->DirectionGet() > 0 ? end::front : end::rear ) }; + if( vehicleparameters->Couplers[ couplingend ].CouplingFlag != iCoupler ) { + auto const &neighbour { vehicleparameters->Neighbours[ couplingend ] }; + if( neighbour.vehicle != nullptr ) { + if( neighbour.distance < 10 ) { + // check whether we need to attach coupler adapter + auto coupleriscompatible { true }; + auto const &coupler { vehicleparameters->Couplers[ couplingend ] }; + if( coupler.type() != TCouplerType::Automatic ) { + auto const &othercoupler = neighbour.vehicle->MoverParameters->Couplers[ ( neighbour.vehicle_end != 2 ? neighbour.vehicle_end : coupler.ConnectedNr ) ]; + if( othercoupler.type() == TCouplerType::Automatic ) { + coupleriscompatible = false; + cue_action( locale::string::driver_hint_couplingadapterattach, couplingend ); + } + } + // próba podczepienia + if( AIControllFlag || Global.AITrainman ) { + if( ( coupleriscompatible ) && ( neighbour.distance < 2 ) ) { + vehicleparameters->Attach( + couplingend, neighbour.vehicle_end, + neighbour.vehicle->MoverParameters, + iCoupler ); + } + } + } + } + } + // NOTE: no else as the preceeding block can potentially establish expected connection + if( vehicleparameters->Couplers[ couplingend ].CouplingFlag == iCoupler ) { + // jeżeli został podłączony + CheckVehicles( Connect ); // sprawdzić światła nowego składu + + iCoupler = 0; // dalsza jazda manewrowa już bez łączenia + iDrivigFlags &= ~moveConnect; // zdjęcie flagi doczepiania + JumpToNextOrder(); // wykonanie następnej komendy + } + } // moveConnect + else { + if( Obstacle.distance <= 20.0 ) { + // początek podczepiania, z wyłączeniem sprawdzania fTrackBlock + iDrivigFlags |= moveConnect; + } + } +} + +void +TController::UpdateDisconnect() { + + if( iVehicleCount >= 0 ) { + // 3rd stage: change direction, compress buffers and uncouple + if( iDirection != iDirectionOrder ) { + cue_action( locale::string::driver_hint_mastercontrollersetreverserunlock ); + cue_action( locale::string::driver_hint_directionother ); + } + if( ( ( iDrivigFlags & movePress ) != 0 ) && ( iDirection == iDirectionOrder ) ) { + if( iVehicleCount >= 0 ) { + // zmieni się po odczepieniu + WriteLog( "Uncoupling [" + mvOccupied->Name + "]: actuating and compressing buffers..." ); + cue_action( locale::string::driver_hint_independentbrakerelease ); + cue_action( locale::string::driver_hint_bufferscompress ); + } + + WriteLog( "Uncoupling [" + mvOccupied->Name + "]: from " + ( mvOccupied->DirAbsolute > 0 ? "front" : "rear" ) ); + auto *decoupledvehicle { pVehicle }; // pojazd do odczepienia, w (pVehicle) siedzi AI + int decoupledend; // numer sprzęgu, który sprawdzamy albo odczepiamy + auto vehiclecount { iVehicleCount }; // ile wagonów ma zostać + // szukanie pojazdu do odczepienia + do { + decoupledend = decoupledvehicle->DirectionGet() > 0 ? // numer sprzęgu od strony czoła składu + end::front : + end::rear; + // jeżeli sprzęg zablokowany to liczymy człony jako jeden + if( decoupledvehicle->MoverParameters->Couplers[ decoupledend ].CouplingFlag & coupling::permanent ) { + ++vehiclecount; + } + if( decoupledvehicle != pVehicle ) { + decoupledvehicle->MoverParameters->BrakeReleaser( 1 ); // wyluzuj pojazd, aby dało się dopychać + } + if( vehiclecount ) { // jeśli jeszcze nie koniec + decoupledvehicle = decoupledvehicle->Prev(); // kolejny w stronę czoła składu (licząc od tyłu), bo dociskamy + if( decoupledvehicle == nullptr ) { + vehiclecount = 0; // nie ma co dalej sprawdzać, odczepianie zakończone + } + } + } while( vehiclecount-- ); + if( decoupledvehicle == nullptr ) { + // no target, or already just virtual coupling + WriteLog( "Uncoupling [" + mvOccupied->Name + "]: didn't find anything to disconnect" ); + iVehicleCount = -2; // odczepiono, co było do odczepienia + } + else { + if( AIControllFlag || Global.AITrainman ) { + decoupledvehicle->Dettach( decoupledend ); + } + // tylko jeśli odepnie + if( decoupledvehicle->MoverParameters->Couplers[ decoupledend ].CouplingFlag == coupling::faux ) { + WriteLog( "Uncoupling [" + mvOccupied->Name + "]: uncoupled" ); + iVehicleCount = -2; + // update trainset state + CheckVehicles( Disconnect ); + // potentially remove coupler adapter +/* + if( decoupledvehicle->MoverParameters->Couplers[ decoupledend ].has_adapter() ) { + decoupledvehicle->remove_coupler_adapter( decoupledend ); + } +*/ + if( pVehicles[ end::front ]->MoverParameters->Couplers[ decoupledend ].has_adapter() ) { + cue_action( locale::string::driver_hint_couplingadapterremove, decoupledend ); + } + } + } // a jak nie, to dociskać dalej + } + // 2nd stage: apply consist brakes and change direction + if( ( iDrivigFlags & movePress ) == 0 ) { + if( false == IsConsistBraked ) { + WriteLog( "Uncoupling [" + mvOccupied->Name + "]: applying consist brakes..." ); + cue_action( locale::string::driver_hint_trainbrakeapply ); + } + // jeśli w miarę został zahamowany (ciśnienie mniejsze niż podane na pozycji 3, zwyle 0.37) + else { + WriteLog( "Uncoupling [" + mvOccupied->Name + "]: direction change" ); +/* // TODO: test if this block is needed + if( mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ) { + // wyłączenie EP, gdy wystarczy (może nie być potrzebne, bo na początku jest) + mvOccupied->BrakeLevelSet( 0 ); + } +*/ + iDirectionOrder = -iDirection; // zmiana kierunku jazdy na przeciwny (dociskanie) + iDrivigFlags |= movePress; // następnie będzie dociskanie + } + } + } // odczepianie + if( iVehicleCount < 0 ) { + // 4th stage: restore initial direction + if( ( iDrivigFlags & movePress ) != 0 ) { + if( eStopReason == stopNone ) { // HACK: use current speed limit to discern whether we're entering this stage for the first time + WriteLog( "Uncoupling [" + mvOccupied->Name + "]: second direction change" ); + iDirectionOrder = -iDirection; + cue_action( locale::string::driver_hint_mastercontrollersetreverserunlock ); + cue_action( locale::string::driver_hint_directionother ); // zmiana kierunku jazdy na właściwy + } + if( iDirection == iDirectionOrder ) { + iDrivigFlags &= ~movePress; // koniec dociskania + JumpToNextOrder(); // zmieni światła + TableClear(); // skanowanie od nowa + iDrivigFlags &= ~moveStartHorn; // bez trąbienia przed ruszeniem + } + } + } +} + +void +TController::handle_engine() { + // HACK: activate route scanning if an idling vehicle is activated by a human user + if( ( OrderCurrentGet() == Wait_for_orders ) + && ( false == iEngineActive ) +// && ( false == AIControllFlag ) + && ( true == mvOccupied->Power24vIsAvailable ) ) { + OrderNext( Prepare_engine ); + } + // basic engine preparation + if( OrderCurrentGet() == Prepare_engine ) { + if( PrepareEngine() ) { // gotowy do drogi? + JumpToNextOrder(); + } + } + // engine state can potentially deteriorate in one of usual driving modes + if( ( OrderCurrentGet() & ( Change_direction | Connect | Disconnect | Shunt | Loose_shunt | Obey_train | Bank ) ) + && ( ( false == iEngineActive ) + || ( IsAnyConverterOverloadRelayOpen ) // wywalił bezpiecznik nadmiarowy przetwornicy + || ( IsAnyLineBreakerOpen ) ) ) { // WS może wywalić z powodu błędu w drutach + // jeśli coś ma robić to niech odpala do skutku + PrepareEngine(); + } +} + +void +TController::handle_orders() { + + switch (OrderCurrentGet()) + { + case Release_engine: { + if( ReleaseEngine() ) { // zdana maszyna? + JumpToNextOrder(); + } + break; + } + case Obey_train: { + UpdateObeyTrain(); + break; + } + case Loose_shunt: { + UpdateLooseShunt(); + break; + } + case Connect: { + UpdateConnect(); + break; + } + case Disconnect: { + UpdateDisconnect(); + break; + } + case Jump_to_first_order: { + if( OrderPos > 1 ) { + OrderPos = 1; // w zerowym zawsze jest czekanie + } + else { + ++OrderPos; + } +#if LOGORDERS + OrdersDump("Jump_to_first_order"); +#endif + break; + } + default: { + break; + } + } // switch OrderList[OrderPos] + UpdateChangeDirection(); +} + +void +TController::pick_optimal_speed( double const Range ) { + +// if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank | Connect | Disconnect | Change_direction ) ) == 0 ) { return; } + + // set initial velocity and acceleration values + VelDesired = fVelMax; // wstępnie prędkość maksymalna dla pojazdu(-ów), będzie następnie ograniczana + VelNext = VelDesired; // maksymalna prędkość wynikająca z innych czynników niż trajektoria ruchu + SetDriverPsyche(); // ustawia AccPreferred (potrzebne tu?) + AccDesired = AccPreferred; // AccPreferred wynika z osobowości mechanika + ActualProximityDist = Range; // funkcja Update() może pozostawić wartości bez zmian + VelLimitLastDist = { VelDesired, -1 }; + SwitchClearDist = -1; + + // if we're idling bail out early + if( false == TestFlag( iDrivigFlags, moveActive ) ) { + VelDesired = 0.0; + AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION ); + return; + } + + // basic velocity and acceleration adjustments + + // jeśli manewry, to ograniczamy prędkość + if( ( OrderCurrentGet() & ( Obey_train | Bank ) ) == 0 ) { // spokojne manewry + SetVelocity( fShuntVelocity, fShuntVelocity ); + } + + // uncoupling mode changes velocity/acceleration between stages + if( ( OrderCurrentGet() & Disconnect ) != 0 ) { + if( iVehicleCount >= 0 ) { + // 3rd stage: compress buffers and uncouple + if( ( ( iDrivigFlags & movePress ) != 0 ) && ( iDirection == iDirectionOrder ) ) { + SetVelocity( 2, 0 ); // jazda w ustawionym kierunku z prędkością 2 + } + // 1st stage: bring it to stop + // 2nd stage: apply consist brakes and change direction + else { + SetVelocity( 0, 0, stopJoin ); + } + } + else { + // 4th stage: restore initial direction + if( ( iDrivigFlags & movePress ) != 0 ) { + SetVelocity( 0, 0, stopJoin ); + } + } + } + // Ra: odczyt (ActualProximityDist), (VelNext) i (AccPreferred) z tabelki prędkosci + check_route_ahead( Range ); + + check_departure(); + + // if ordered to turn off the vehicle, try to stop + if( true == TestFlag( OrderCurrentGet(), Release_engine ) ) { + SetVelocity( 0, 0, stopSleep ); + } + // if ordered to change direction, try to stop + if( true == TestFlag( OrderCurrentGet(), Change_direction ) ) { + SetVelocity( 0, 0, stopDir ); + } + + adjust_desired_speed_for_obstacles(); + adjust_desired_speed_for_limits(); + adjust_desired_speed_for_target_speed( Range ); + adjust_desired_speed_for_current_speed(); + + // Ra 2F1I: wyłączyć kiedyś to uśrednianie i przeanalizować skanowanie, czemu migocze + if( AccDesired > EU07_AI_NOACCELERATION ) { // hamowania lepeiej nie uśredniać + AccDesired = fAccDesiredAv = + 0.2 * AccDesired + + 0.8 * fAccDesiredAv; // uśrednione, żeby ograniczyć migotanie + } + if( VelDesired == 0.0 ) { + // Ra 2F1J: jeszcze jedna prowizoryczna łatka + AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION ); + } + + // last step sanity check, until the whole calculation is straightened out + AccDesired = std::min( AccDesired, AccPreferred ); + AccDesired = clamp( + AccDesired, + ( is_car() ? -2.0 : -0.9 ), + ( is_car() ? 2.0 : 0.9 ) ); +} + +void +TController::adjust_desired_speed_for_obstacles() { + // prędkość w kierunku jazdy, ujemna gdy jedzie w przeciwną stronę niż powinien + auto const vel { DirectionalVel() }; + + if (VelDesired < 0.0) + VelDesired = fVelMax; // bo VelDesired<0 oznacza prędkość maksymalną + + // Ra: jazda na widoczność + if( Obstacle.distance < 5000 ) { + // mamy coś z przodu + // prędkość pojazdu z przodu (zakładając, że jedzie w tę samą stronę!!!) + auto const k { Obstacle.vehicle->MoverParameters->Vel }; + + if( k - vel < 5 ) { + // porównanie modułów prędkości [km/h] + // zatroszczyć się trzeba, jeśli tamten nie jedzie znacząco szybciej + ActualProximityDist = std::min( + ActualProximityDist, + Obstacle.distance ); + + if( Obstacle.distance <= ( + ( mvOccupied->CategoryFlag & 2 ) ? + 100.0 : // cars + 250.0 ) ) { // others + // regardless of driving mode at close distance take precaution measures: + // match the other vehicle's speed or slow down if the other vehicle is stopped + VelDesired = + min_speed( + VelDesired, + std::max( + k, + ( mvOccupied->CategoryFlag & 2 ) ? + 40.0 : // cars + 20.0 ) ); // others + if( vel > VelDesired + fVelPlus ) { + // if going too fast force some prompt braking + AccPreferred = std::min( + ( ( mvOccupied->CategoryFlag & 2 ) ? + -0.65 : // cars + -0.30 ), // others + AccPreferred ); + } + } + + double const distance = Obstacle.distance - fMaxProximityDist - ( fBrakeDist * 1.15 ); // odległość bezpieczna zależy od prędkości + if( distance < 0.0 ) { + // jeśli odległość jest zbyt mała + if( k < 10.0 ) // k - prędkość tego z przodu + { // jeśli tamten porusza się z niewielką prędkością albo stoi + // keep speed difference within a safer margin + VelDesired = std::floor( + min_speed( + VelDesired, + ( Obstacle.distance > 100 ? + k + 20.0: + std::min( 8.0, k + 4.0 ) ) ) ); + + if( ( OrderCurrentGet() & ( Connect | Loose_shunt ) ) != 0 ) { + // jeśli spinanie, to jechać dalej + AccPreferred = std::min( 0.35, AccPreferred ); // nie hamuj + VelNext = std::floor( std::min( 8.0, k + 2.0 ) ); // i pakuj się na tamtego + } + else { + // a normalnie to hamować + VelNext = 0.0; + if( Obstacle.distance <= fMinProximityDist ) { + VelDesired = 0.0; + } + + if( ( mvOccupied->CategoryFlag & 1 ) + && ( OrderCurrentGet() & Obey_train ) ) { + // trains which move normally should try to stop at safe margin + ActualProximityDist -= fDriverDist; + } + } + } + else { + // jeśli oba jadą, to przyhamuj lekko i ogranicz prędkość + if( Obstacle.distance < ( + ( mvOccupied->CategoryFlag & 2 ) ? + fMaxProximityDist + 0.5 * vel : // cars + 2.0 * fMaxProximityDist + 2.0 * vel ) ) { //others + // jak tamten jedzie wolniej a jest w drodze hamowania + AccPreferred = std::min( -0.9, AccPreferred ); + VelNext = min_speed( std::round( k ) - 5.0, VelDesired ); + if( Obstacle.distance <= ( + ( mvOccupied->CategoryFlag & 2 ) ? + fMaxProximityDist : // cars + 2.0 * fMaxProximityDist ) ) { //others + // try to force speed change if obstacle is really close + VelDesired = VelNext; + } + } + } + ReactionTime = ( + mvOccupied->Vel > 0.01 ? + 0.1 : // orientuj się, bo jest goraco + 2.0 ); // we're already standing still, so take it easy + } + else { + if( OrderCurrentGet() & Connect ) { + // if there's something nearby in the connect mode don't speed up too much + VelDesired = + min_speed( + VelDesired, + ( Obstacle.distance > 100 ? + 20.0 : + 4.0 ) ); + } + } + } + } +} + +void +TController::adjust_desired_speed_for_limits() { + // speed caps checks + // sprawdzamy możliwe ograniczenia prędkości + if( VelSignal >= 0 ) { + // jeśli skład był zatrzymany na początku i teraz już może jechać + VelDesired = + min_speed( + VelDesired, + VelSignal ); + } + if( ( ( OrderCurrentGet() & Obey_train ) != 0 ) + && ( TrainParams.TTVmax > 0.0 ) ) { + // jesli nie spozniony to nie przekraczać rozkladowej + VelDesired = + min_speed( + VelDesired, + TrainParams.TTVmax ); + } + if( mvOccupied->RunningTrack.Velmax >= 0 ) { + // ograniczenie prędkości z trajektorii ruchu + VelDesired = + min_speed( + VelDesired, + mvOccupied->RunningTrack.Velmax ); // uwaga na ograniczenia szlakowej! + } + if( VelforDriver >= 0 ) { + // tu jest zero przy zmianie kierunku jazdy + // Ra: tu może być 40, jeśli mechanik nie ma znajomości szlaaku, albo kierowca jeździ 70 + VelDesired = + min_speed( + VelDesired, + VelforDriver ); + } + + // recalculate potential load exchange duration + DoesAnyDoorNeedOpening = false; + ExchangeTime = 0.f; + if( fStopTime < 0 ) { + // czas postoju przed dalszą jazdą (np. na przystanku) + // verify progress of load exchange + auto *vehicle { pVehicles[ end::front ] }; + while( vehicle != nullptr ) { + auto const vehicleexchangetime { vehicle->LoadExchangeTime() }; + DoesAnyDoorNeedOpening |= ( ( vehicleexchangetime > 0 ) && ( vehicle->LoadExchangeSpeed() == 0 ) ); + ExchangeTime = std::max( ExchangeTime, vehicleexchangetime ); + vehicle = vehicle->Next(); + } + if( ( ExchangeTime > 0 ) + || ( mvOccupied->Vel > 2.0 ) ) { // HACK: force timer reset if the load exchange is cancelled due to departure + WaitingSet( ExchangeTime ); + } + VelDesired = 0.0; // jak ma czekać, to nie ma jazdy + cue_action( locale::string::driver_hint_waitloadexchange ); + return; // speed limit can't get any lower + } + + if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) != 0 ) { + // w Connect nie, bo moveStopHere odnosi się do stanu po połączeniu + if( ( ( iDrivigFlags & moveStopHere ) != 0 ) + && ( mvOccupied->Vel < 0.01 ) + && ( VelSignalNext == 0.0 ) ) { + // jeśli ma czekać na wolną drogę, stoi a wyjazdu nie ma, to ma stać + VelDesired = 0.0; + return; // speed limit can't get any lower + } + } + + if( OrderCurrentGet() == Wait_for_orders ) { + // wait means sit and wait + VelDesired = 0.0; + return; // speed limit can't get any lower + } +} + +void +TController::adjust_desired_speed_for_target_speed( double const Range ) { + // ustalanie zadanego przyspieszenia + //(ActualProximityDist) - odległość do miejsca zmniejszenia prędkości + //(AccPreferred) - wynika z psychyki oraz uwzglęnia już ewentualne zderzenie z pojazdem z przodu, ujemne gdy należy hamować + //(AccDesired) - uwzględnia sygnały na drodze ruchu, ujemne gdy należy hamować + //(fAccGravity) - chwilowe przspieszenie grawitacyjne, ujemne działa przeciwnie do zadanego kierunku jazdy + //(AbsAccS) - chwilowe przyspieszenie pojazu (uwzględnia grawitację), ujemne działa przeciwnie do zadanego kierunku jazdy + //(AccDesired) porównujemy z (fAccGravity) albo (AbsAccS) + + // gdy jedzie wolniej niż potrzeba, albo nie ma przeszkód na drodze + // normalna jazda + AccDesired = ( + VelDesired != 0.0 ? + AccPreferred : + -0.01 ); + + auto const vel { DirectionalVel() }; + + if( ( VelNext >= 0.0 ) + && ( ActualProximityDist <= Range ) + && ( vel >= VelNext ) ) { + // gdy zbliża się i jest za szybki do nowej prędkości, albo stoi na zatrzymaniu + if (vel > 0.0) { + // jeśli jedzie + if( ( vel < VelNext ) + && ( ActualProximityDist > fMaxProximityDist * ( 1.0 + 0.1 * vel ) ) ) { + // jeśli jedzie wolniej niż można i jest wystarczająco daleko, to można przyspieszyć + if( AccPreferred > 0.0 ) { + // jeśli nie ma zawalidrogi dojedz do semafora/przeszkody + AccDesired = AccPreferred; + } + } + else if (ActualProximityDist > fMinProximityDist) { + // jedzie szybciej, niż trzeba na końcu ActualProximityDist, ale jeszcze jest daleko + if (ActualProximityDist < fMaxProximityDist) { + // jak minął już maksymalny dystans po prostu hamuj (niski stopień) + // ma stanąć, a jest w drodze hamowania albo ma jechać +/* + VelDesired = min_speed( VelDesired, VelNext ); +*/ + if( VelNext == 0.0 ) { + if( mvOccupied->CategoryFlag & 1 ) { + // trains + if( ( OrderCurrentGet() & ( Shunt | Connect ) ) + && ( Obstacle.distance < 50 ) ) { + // crude detection of edge case, if approaching another vehicle coast slowly until min distance + // this should allow to bunch up trainsets more on sidings + VelDesired = min_speed( 5.0, VelDesired ); + } + else { + // hamowanie tak, aby stanąć + VelDesired = VelNext; + AccDesired = ( VelNext * VelNext - vel * vel ) / ( 25.92 * ( ActualProximityDist + 0.1 - 0.5*fMinProximityDist ) ); + AccDesired = std::min( AccDesired, fAccThreshold ); + } + } + else { + // for cars (and others) coast at low speed until we hit min proximity range + VelDesired = min_speed( VelDesired, 5.0 ); + } + } + } + else { + // outside of max safe range + AccDesired = AccPreferred; + if( vel > min_speed( (ActualProximityDist > 10.0 ? 10.0 : 5.0 ), VelDesired ) ) { + // allow to coast at reasonably low speed + auto const brakingdistance { fBrakeDist * braking_distance_multiplier( VelNext ) }; + auto const slowdowndistance { ( + mvOccupied->CategoryFlag == 2 ? // cars can stop on a dime, for bigger vehicles we enforce some minimal braking distance + brakingdistance : + std::max( + ( ( OrderCurrentGet() & Connect ) == 0 ? 100.0 : 25.0 ), + brakingdistance ) ) }; + if( ( brakingdistance + std::max( slowdowndistance, fMaxProximityDist ) ) >= ( ActualProximityDist - fMaxProximityDist ) ) { + // don't slow down prematurely; as long as we have room to come to a full stop at a safe distance, we're good + // ensure some minimal coasting speed, otherwise a vehicle entering this zone at very low speed will be crawling forever + auto const brakingpointoffset = VelNext * braking_distance_multiplier( VelNext ); + AccDesired = std::min( + AccDesired, + ( VelNext * VelNext - vel * vel ) + / ( 25.92 + * std::max( + ActualProximityDist - brakingpointoffset, + std::min( + ActualProximityDist, + brakingpointoffset ) ) + + 0.1 ) ); // najpierw hamuje mocniej, potem zluzuje + } + } + } + AccDesired = std::min( AccDesired, AccPreferred ); + } + else { + // jest bliżej niż fMinProximityDist + // utrzymuj predkosc bo juz blisko +/* + VelDesired = min_speed( VelDesired, VelNext ); +*/ + if( VelNext == 0.0 ) { + VelDesired = VelNext; + } + else { + if( vel <= VelNext + fVelPlus ) { + // jeśli niewielkie przekroczenie, ale ma jechać + AccDesired = std::max( 0.0, AccPreferred ); // to olej (zacznij luzować) + } + } + ReactionTime = 0.1; // i orientuj się szybciej + } + } + else { + // zatrzymany (vel==0.0) + if( VelNext > 0.0 ) { + // można jechać + AccDesired = AccPreferred; + } + else { + // jeśli daleko jechać nie można + if( ActualProximityDist > ( + ( mvOccupied->CategoryFlag & 2 ) ? + fMinProximityDist : // cars + fMaxProximityDist ) ) { // trains and others + // ale ma kawałek do sygnalizatora + if( AccPreferred > 0.0 ) { + // dociagnij do semafora; + AccDesired = AccPreferred; + } + else { + //stoj + VelDesired = 0.0; + } + } + else { + // VelNext=0 i stoi bliżej niż fMaxProximityDist + VelDesired = 0.0; + } + } + } + } +} + +void +TController::adjust_desired_speed_for_current_speed() { + // decisions based on current speed + auto const vel { DirectionalVel() }; + + if( is_train() ) { + // on flats or uphill we can be less careful + if( vel > VelDesired ) { + // jesli jedzie za szybko do AKTUALNEGO + if( VelDesired == 0.0 ) { + // jesli stoj, to hamuj, ale i tak juz za pozno :) + AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie + } + else { + // slow down, not full stop + if( vel > ( VelDesired + fVelPlus ) ) { + // hamuj tak średnio + AccDesired = std::min( AccDesired, -0.05 + fAccThreshold ); + } + else { + // o 5 km/h to olej (zacznij luzować) + AccDesired = std::min( + AccDesired, // but don't override decceleration for VelNext + std::max( 0.0, AccPreferred ) ); + } + } + } + + if( fAccGravity > 0.025 ) { + // going sharply downhill we may need to start braking sooner than usual + // try to estimate increase of current velocity before engaged brakes start working + auto const speedestimate = vel + ( 1.0 - fBrake_a0[ 0 ] ) * 30.0 * AbsAccS; + if( speedestimate > VelDesired ) { + // jesli jedzie za szybko do AKTUALNEGO + if( VelDesired == 0.0 ) { + // jesli stoj, to hamuj, ale i tak juz za pozno :) + AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie + } + else { + // if it looks like we'll exceed maximum speed start thinking about slight slowing down + AccDesired = std::min( AccDesired, -0.05 + fAccThreshold ); + // HACK: for cargo trains with high braking threshold ensure we cross that threshold + if( ( true == IsCargoTrain ) + && ( fBrake_a0[ 0 ] > 0.2 ) ) { + AccDesired -= clamp( fBrake_a0[ 0 ] - 0.2, 0.0, 0.15 ); + } + } + } + else { + // stop accelerating when close enough to target speed + AccDesired = std::min( + AccDesired, // but don't override decceleration for VelNext + interpolate( // ease off as you close to the target velocity + EU07_AI_NOACCELERATION, AccPreferred, + clamp( VelDesired - speedestimate, 0.0, fVelMinus ) / fVelMinus ) ); + } + // final tweaks + if( vel > 0.1 ) { + // going downhill also take into account impact of gravity + AccDesired -= fAccGravity; + // HACK: if the max allowed speed was exceeded something went wrong; brake harder + AccDesired -= 0.15 * clamp( vel - VelDesired, 0.0, 5.0 ); + } + } + // HACK: limit acceleration for cargo trains, to reduce probability of breaking couplers on sudden jolts + // TBD: expand this behaviour to all trains with car(s) exceeding certain weight? + if( ( IsPassengerTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) { + AccDesired = std::min( AccDesired, ( iVehicles - ControlledEnginesCount > 8 ? HeavyPassengetTrainAcceleration : PassengetTrainAcceleration ) ); + } + if( ( IsCargoTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) { + AccDesired = std::min( AccDesired, CargoTrainAcceleration ); + } + if( ( IsHeavyCargoTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) { + AccDesired = std::min( AccDesired, HeavyCargoTrainAcceleration ); + } + } + else { + // for cars the older version works better + if( vel > VelDesired ) { + // jesli jedzie za szybko do AKTUALNEGO + if( VelDesired == 0.0 ) { + // jesli stoj, to hamuj, ale i tak juz za pozno :) + AccDesired = std::min( AccDesired, -0.9 ); // hamuj solidnie + } + else { + // slow down, not full stop + if( vel > ( VelDesired + fVelPlus ) ) { + // hamuj tak średnio + AccDesired = std::min( AccDesired, -fBrake_a0[ 0 ] * 0.5 ); + } + else { + // o 5 km/h to olej (zacznij luzować) + AccDesired = std::min( + AccDesired, // but don't override decceleration for VelNext + std::max( 0.0, AccPreferred ) ); + } + } + } + } + + // NOTE: as a stop-gap measure the routine is limited to trains only while car calculations seem off + if( is_train() ) { + if( vel < VelDesired ) { + // don't adjust acceleration when going above current target speed + if( -AccDesired * BrakeAccFactor() < ( + ( ( fReady > ( IsHeavyCargoTrain ? 0.4 : ( mvOccupied->Vel > 5.0 ) ? 0.45 : 0.4 ) ) + || ( VelNext > vel - 40.0 ) ) ? + fBrake_a0[ 0 ] * 0.8 : + -fAccThreshold ) + / ( 1.2 * braking_distance_multiplier( VelNext ) ) ) { + AccDesired = std::max( EU07_AI_NOACCELERATION, AccDesired ); + } + } + } +} + +void +TController::control_tractive_and_braking_force() { + +#if LOGVELOCITY + WriteLog("Dist=" + FloatToStrF(ActualProximityDist, ffFixed, 7, 1) + + ", VelDesired=" + FloatToStrF(VelDesired, ffFixed, 7, 1) + + ", AccDesired=" + FloatToStrF(AccDesired, ffFixed, 7, 3) + + ", VelSignal=" + AnsiString(VelSignal) + ", VelNext=" + + AnsiString(VelNext)); +#endif + + if (iDriverFailCount > maxdriverfails) { + Psyche = Easyman; + if (iDriverFailCount > maxdriverfails * 2) + SetDriverPsyche(); + } + + control_relays(); + control_motor_connectors(); + + // if the radio-stop was issued don't waste effort trying to fight it + if( ( true == mvOccupied->RadioStopFlag ) // radio-stop + && ( mvOccupied->Vel > 0.0 ) ) { // and still moving + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); // just throttle down... + return; // ...and don't touch any other controls + } + // if we're slipping leave the controls alone, there's a separate logic section trying to deal with it + if( true == mvControlling->SlippingWheels ) { + return; + } + + // ensure train brake isn't locked + if( BrakeCtrlPosition == gbh_NP ) { + cue_action( locale::string::driver_hint_trainbrakerelease ); + } + control_releaser(); + control_main_pipe(); + + control_tractive_force(); + control_braking_force(); + +#if LOGVELOCITY + WriteLog("BrakePos=" + AnsiString(mvOccupied->BrakeCtrlPos) + ", MainCtrl=" + + AnsiString(mvControlling->MainCtrlPos)); +#endif +} + +// ensure relays are active +void TController::control_relays() { + + if( fActionTime < 0.0 ) { return; } + + if( IsAnyMotorOverloadRelayOpen ) { + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + cue_action( locale::string::driver_hint_tractionnmotoroverloadreset ); + ++iDriverFailCount; + } + if( IsAnyGroundRelayOpen ) { + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + cue_action( locale::string::driver_hint_maincircuitgroundreset ); + ++iDriverFailCount; + } +} + +void TController::control_motor_connectors() { + // ensure motor connectors are active + if( ( mvControlling->EngineType == TEngineType::ElectricSeriesMotor ) + || ( mvControlling->EngineType == TEngineType::DieselElectric ) + || ( mvControlling->TrainType == dt_EZT ) ) { + if( Need_TryAgain ) { + // true, jeśli druga pozycja w elektryku nie załapała + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + Need_TryAgain = false; + } + } +} + +void TController::control_tractive_force() { + + auto const velocity { DirectionalVel() }; + // jeśli przyspieszenie pojazdu jest mniejsze niż żądane oraz... + if( ( AccDesired > EU07_AI_NOACCELERATION ) // don't add power if not asked for actual speed-up + && ( AccDesired - AbsAccS > 0.05 ) + && ( false == TestFlag( iDrivigFlags, movePress ) ) ) { + // ...jeśli prędkość w kierunku czoła jest mniejsza od dozwolonej o margines... + if( velocity < ( + VelDesired == 1.0 ? // work around for trains getting stuck on tracks with speed limit = 1 + VelDesired : + VelDesired - fVelMinus ) ) { + // within proximity margin don't exceed next scheduled speed, outside of the margin accelerate as you like + if( ( ActualProximityDist > ( + is_car() ? + fMinProximityDist : // cars are allowed to move within min proximity distance + fMaxProximityDist ) ? // other vehicle types keep wider margin + true : + ( velocity + 1.0 ) < VelNext ) ) { + // ...to można przyspieszyć + increase_tractive_force(); + } + } + } + // zmniejszanie predkosci + // margines dla prędkości jest doliczany tylko jeśli oczekiwana prędkość jest większa od 5km/h + if( false == TestFlag( iDrivigFlags, movePress ) ) { + double SpeedCtrlMargin = (mvControlling->SpeedCtrlUnit.IsActive && VelDesired > 5) ? 3 : 0; + // jeśli nie dociskanie + if( AccDesired <= EU07_AI_NOACCELERATION ) { + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + } + else if( ( velocity > VelDesired + SpeedCtrlMargin) + || ( fAccGravity < -0.01 ? + AccDesired < 0.0 : + (AbsAccS > AccDesired + 0.05) ) + || ( IsAnyCouplerStretched ) ) { + // jak za bardzo przyspiesza albo prędkość przekroczona + // dodany wyjatek na "pelna w przod" + cue_action( locale::string::driver_hint_tractiveforcedecrease ); + } + } + + control_handles(); + SpeedSet(); // ciągla regulacja prędkości +} + +void TController::increase_tractive_force() { + + if( fActionTime < 0.0 ) { return; } // gdy jest nakaz poczekać z jazdą, to nie ruszać + if( IsAnyCouplerStretched ) { return; } // train is already stretched past its limits, don't pull even harder + + if( mvOccupied->SpringBrake.Activate ) { + cue_action( locale::string::driver_hint_springbrakeoff ); + } + if( ( VelDesired > 0.0 ) // to prevent door shuffle on stop + && ( doors_open() || doors_permit_active() ) ) { + // zamykanie drzwi - tutaj wykonuje tylko AI (zmienia fActionTime) + Doors( false ); + // Doors() call can potentially adjust fActionTime + if( fActionTime < 0.0 ) { return; } + } + if( true == mvOccupied->DepartureSignal ) { + cue_action( locale::string::driver_hint_departuresignaloff ); + } + + cue_action( locale::string::driver_hint_tractiveforceincrease ); +} + +void TController::control_braking_force() { + + // don't touch the brakes when compressing buffers during coupling/uncoupling + if( TestFlag( iDrivigFlags, movePress ) ) { return; } + + auto const velocity { DirectionalVel() }; + + // jeśli przyspieszamy, to nie hamujemy + if( AccDesired > 0.0 ) { + if( ( OrderCurrentGet() != Disconnect ) // przy odłączaniu nie zwalniamy tu hamulca + && ( false == TestFlag( iDrivigFlags, movePress ) ) ) { + cue_action( locale::string::driver_hint_brakingforcesetzero ); + } + } + + if( mvOccupied->TrainType == dt_EZT ) { + // właściwie, to warunek powinien być na działający EP + // Ra: to dobrze hamuje EP w EZT + auto const accthreshold { ( + fAccGravity < 0.025 ? // HACK: when going downhill be more responsive to desired deceleration + fAccThreshold : + std::max( -0.2, fAccThreshold ) ) }; + if( ( AccDesired < accthreshold ) // jeśli hamować - u góry ustawia się hamowanie na fAccThreshold + && ( ( AbsAccS > AccDesired ) + || ( BrakeCtrlPosition < 0 ) ) ) { + // hamować bardziej, gdy aktualne opóźnienie hamowania mniejsze niż (AccDesired) + cue_action( locale::string::driver_hint_brakingforceincrease ); + } + else if( OrderCurrentGet() != Disconnect ) { // przy odłączaniu nie zwalniamy tu hamulca + if( AbsAccS < AccDesired - 0.05 ) { + // jeśli opóźnienie większe od wymaganego (z histerezą) luzowanie, gdy za dużo + // TBD: check if the condition isn't redundant with the DecBrake() code + if( BrakeCtrlPosition >= 0 ) { + if( VelDesired > 0.0 ) { // sanity check to prevent unintended brake release on sharp slopes + cue_action( locale::string::driver_hint_brakingforcedecrease ); + } + } + } + else { + cue_action( locale::string::driver_hint_brakingforcelap ); + } + } + } // type & dt_ezt + else { + // a stara wersja w miarę dobrze działa na składy wagonowe + if( ( ( AccDesired < fAccGravity - 0.1 ) && ( AbsAccS > AccDesired + fBrake_a1[ 0 ] ) ) // regular braking + || ( ( fAccGravity < -0.05 ) && ( velocity < -0.1 ) ) ) { // also brake if uphill and slipping back + // u góry ustawia się hamowanie na fAccThreshold + if( ( fBrakeTime < 0.0 ) + || ( AccDesired < fAccGravity - 0.5 ) + || ( BrakeCtrlPosition <= 0 ) ) { + // jeśli upłynął czas reakcji hamulca, chyba że nagłe albo luzował + // TODO: check whether brake delay variable still has any purpose + cue_action( + locale::string::driver_hint_brakingforceincrease, + // Ra: ten czas należy zmniejszyć, jeśli czas dojazdu do zatrzymania jest mniejszy + ( 3.0 + + 0.5 * ( ( + mvOccupied->BrakeDelayFlag > bdelay_G ? + mvOccupied->BrakeDelay[ 1 ] : + mvOccupied->BrakeDelay[ 3 ] ) + - 3.0 ) ) + * 0.5 ); // Ra: tymczasowo, bo przeżyna S1 + } + } + if ( ( AccDesired < fAccGravity - 0.05 ) + && ( ( AccDesired - fBrake_a1[ 0 ] * 0.51 ) ) - AbsAccS > 0.05 ) { + // jak hamuje, to nie tykaj kranu za często + // yB: luzuje hamulec dopiero przy różnicy opóźnień rzędu 0.2 + if( OrderCurrentGet() != Disconnect ) { // przy odłączaniu nie zwalniamy tu hamulca + if( VelDesired > 0.0 ) { // sanity check to prevent unintended brake release on sharp slopes + // TODO: check whether brake delay variable still has any purpose + cue_action( + locale::string::driver_hint_brakingforcedecrease, + ( ( mvOccupied->BrakeDelayFlag > bdelay_G ? + mvOccupied->BrakeDelay[ 0 ] : + mvOccupied->BrakeDelay[ 2 ] ) + / 3.0 ) + * 0.5 ); // Ra: tymczasowo, bo przeżyna S1 + } + } + } + // stop-gap measure to ensure cars actually brake to stop even when above calculactions go awry + // instead of releasing the brakes and creeping into obstacle at 1-2 km/h + if( is_car() ) { + if( ( VelDesired == 0.0 ) + && ( velocity > VelDesired ) + && ( ActualProximityDist <= fMinProximityDist ) + && ( mvOccupied->LocalBrakePosA < 0.01 ) ) { + cue_action( locale::string::driver_hint_brakingforceincrease ); + } + } + } + + // odhamowywanie składu po zatrzymaniu i zabezpieczanie lokomotywy + if( ( mvOccupied->Vel < 0.01 ) + && ( ( VelDesired == 0.0 ) + || ( AccDesired <= EU07_AI_NOACCELERATION ) ) ) { + + if( ( ( OrderCurrentGet() & ( Disconnect | Connect ) ) == 0 ) // przy (p)odłączaniu nie zwalniamy tu hamulca + && ( std::abs( fAccGravity ) < 0.01 ) ) { // only do this on flats, on slopes keep applied the train brake + // do it only if the vehicle actually has the independent brake + apply_independent_brake_only(); + } + } +} + +void TController::apply_independent_brake_only() { + + if( mvOccupied->LocalBrake != TLocalBrake::ManualBrake ) { + if( ( false == mvOccupied->ShuntMode ) // no need for independent brake if autobrake is active + && ( is_equal( mvOccupied->fBrakeCtrlPos, mvOccupied->Handle->GetPos( bh_RP ), 0.2 ) ) ) { + cue_action( locale::string::driver_hint_independentbrakeapply ); + } + else { + cue_action( locale::string::driver_hint_trainbrakerelease ); + } + } +} + +void TController::control_releaser() { + // HACK: don't immediately touch releaser in inert vehicle, in case the simulation is about to replace us with human driver + if( ( mvOccupied->Vel < 1.0 ) && ( fActionTime < 0.0 ) ) { return; } + // TODO: combine all releaser handling in single decision tree instead of having bits all over the place + // TODO: replace handle and system conditions with flag indicating releaser presence + if( mvOccupied->BrakeSystem != TBrakeSystem::Pneumatic ) { return; } + + auto const hasreleaser { + ( mvOccupied->BrakeHandle == TBrakeHandle::FV4a ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_6P ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K5P ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::M394 ) }; + + if( false == hasreleaser ) { return; } + + auto actuate { false }; + auto isbrakehandleinrightposition { true }; + + if( AccDesired > EU07_AI_NOACCELERATION ) { + // fill up brake pipe if needed + if( mvOccupied->PipePress < 3.0 ) { + actuate = true; + // some vehicles require brake handle to be moved to specific position + if( mvOccupied->HandleUnlock != -3 ) { + cue_action( locale::string::driver_hint_trainbrakesetpipeunlock ); + isbrakehandleinrightposition &= ( BrakeCtrlPosition == mvOccupied->HandleUnlock ); + } + } + // wyluzuj lokomotywę, to szybciej ruszymy + if( ( mvOccupied->BrakePress > 0.4 ) + && ( mvOccupied->Hamulec->GetCRP() > 4.9 ) ) { + actuate = true; + } + // keep engine brakes released during coupling/uncoupling + if( ( true == TestFlag( iDrivigFlags, movePress ) ) + && ( mvOccupied->BrakePress > 0.1 ) ) { + actuate = true; + } + } + // don't overcharge train brake pipe + if( mvOccupied->PipePress > 5.2 ) { + actuate = false; + } + + if( actuate ) { + // some vehicles may require master controller in neutral position + cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); + if( ( false == mvOccupied->Hamulec->Releaser() ) + && ( isbrakehandleinrightposition ) ) { + cue_action( locale::string::driver_hint_releaseron ); + } + } + else { + if( true == mvOccupied->Hamulec->Releaser() ) { + cue_action( locale::string::driver_hint_releaseroff ); + } + } +} + +void TController::control_main_pipe() { + + // unlocking main pipe + if( ( AccDesired > -0.03 ) + && ( true == mvOccupied->LockPipe ) ) { + UniversalBrakeButtons |= TUniversalBrake::ub_UnlockPipe; + } + else if (false == mvOccupied->LockPipe ) { + UniversalBrakeButtons &= ~TUniversalBrake::ub_UnlockPipe; + } + + // napełnianie uderzeniowe + if( mvOccupied->BrakeSystem != TBrakeSystem::Pneumatic ) { return; } + + if( ( mvOccupied->BrakeHandle == TBrakeHandle::FV4a ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_6P ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K5P ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::M394 ) ) { + + if( ( iDrivigFlags & moveOerlikons ) + || ( true == IsCargoTrain ) ) { + + if( ( BrakeCtrlPosition == gbh_RP ) + && ( AbsAccS < 0.03 ) + && ( AccDesired > -0.03 ) + && ( VelDesired - mvOccupied->Vel > 2.0 ) ) { + + if( ( fReady > 0.35 ) + && ( mvOccupied->EqvtPipePress < 4.5 ) // don't charge with a risk of overcharging the main pipe + && ( mvOccupied->Compressor >= 7.5 ) // don't charge without sufficient pressure in the tank + && ( BrakeChargingCooldown >= 0.0 ) // don't charge while cooldown is active + && ( ( ActualProximityDist > 100.0 ) // don't charge if we're about to be braking soon + || ( min_speed( mvOccupied->Vel, VelNext ) >= mvOccupied->Vel ) ) ) { + + BrakeLevelSet( gbh_FS ); + // don't charge the brakes too often, or we risk overcharging + BrakeChargingCooldown = -1 * clamp( iVehicles * 3, 30, 90 ); + } + } + } + + if( ( mvOccupied->Compressor < 5.0 ) + || ( ( BrakeCtrlPosition < gbh_RP ) + && ( mvOccupied->EqvtPipePress > ( fReady < 0.25 ? 5.1 : 5.2 ) ) ) ) { + cue_action( locale::string::driver_hint_trainbrakerelease ); + } + } +} + +void +TController::check_route_ahead( double const Range ) { + + auto const comm { TableUpdate( VelDesired, ActualProximityDist, VelNext, AccDesired ) }; + + switch (comm) { + // ustawienie VelSignal - trochę proteza = do przemyślenia + case TCommandType::cm_Ready: { // W4 zezwolił na jazdę + // ewentualne doskanowanie trasy za W4, który zezwolił na jazdę + TableCheck( Range ); + TableUpdate( VelDesired, ActualProximityDist, VelNext, AccDesired ); // aktualizacja po skanowaniu + if( VelNext == 0.0 ) { + break; // ale jak coś z przodu zamyka, to ma stać + } + if( iDrivigFlags & moveStopCloser ) { + VelSignal = -1.0; // ma czekać na sygnał z sygnalizatora! + } + break; + } + case TCommandType::cm_SetVelocity: { // od wersji 357 semafor nie budzi wyłączonej lokomotywy + if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) { // jedzie w dowolnym trybie albo Wait_for_orders + if( std::fabs( VelSignal ) >= 1.0 ) { // 0.1 nie wysyła się do samochodow, bo potem nie ruszą + PutCommand( "SetVelocity", VelSignal, VelNext, nullptr ); // komenda robi dodatkowe operacje + } + } + break; + } + case TCommandType::cm_ShuntVelocity: { // od wersji 357 Tm nie budzi wyłączonej lokomotywy + if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) { // jedzie w dowolnym trybie albo Wait_for_orders + PutCommand( "ShuntVelocity", VelSignal, VelNext, nullptr ); + } + else if( iCoupler ) { // jeśli jedzie w celu połączenia + SetVelocity( VelSignal, VelNext ); + } + break; + } + case TCommandType::cm_Command: { // komenda z komórki + if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) { + // jedzie w dowolnym trybie albo Wait_for_orders + if( mvOccupied->Vel < 0.1 ) { + // dopiero jak stanie +/* + PutCommand( eSignNext->text(), eSignNext->value( 1 ), eSignNext->value( 2 ), nullptr ); + eSignNext->StopCommandSent(); // się wykonało już +*/ // replacement of the above + eSignNext->send_command( *this ); + } + } + break; + } + default: { + break; + } + } +} + +void +TController::check_route_behind( double const Range ) { + + if( VelNext != 0.0 ) { return; } + + if( !( OrderCurrentGet() & ~( Shunt | Loose_shunt | Connect ) ) ) { + // jedzie w Shunt albo Connect, albo Wait_for_orders + // w trybie Connect skanować do tyłu tylko jeśli przed kolejnym sygnałem nie ma taboru do podłączenia + if( ( ( OrderCurrentGet() & Connect ) == 0 ) + || ( Obstacle.distance > std::min( 2000.0, FirstSemaphorDist ) ) ) { + auto const comm { BackwardScan( Range ) }; + if( comm != TCommandType::cm_Unknown ) { + // jeśli w drugą można jechać + // należy sprawdzać odległość od znalezionego sygnalizatora, + // aby w przypadku prędkości 0.1 wyciągnąć najpierw skład za sygnalizator + // i dopiero wtedy zmienić kierunek jazdy, oczekując podania prędkości >0.5 + if( comm == TCommandType::cm_Command ) { + // jeśli komenda Shunt to ją odbierz bez przemieszczania się (np. odczep wagony po dopchnięciu do końca toru) + iDrivigFlags |= moveStopHere; + } + iDirectionOrder = -iDirection; // zmiana kierunku jazdy + // zmiana kierunku bez psucia kolejnych komend + OrderList[ OrderPos ] = TOrders( OrderCurrentGet() | Change_direction ); + } + } + } +} + +void +TController::UpdateBrakingHelper() { + + if (( HelperState > 0 ) && (-AccDesired < fBrake_a0[0] + 2 * fBrake_a1[0]) && (mvOccupied->Vel > 1)) { + HelperState = 0; + } + + switch( HelperState ) { + case 0: { + if( ( -AccDesired > fBrake_a0[0] + 8 * fBrake_a1[0] ) ) { + HelperState = 1; + } + break; + } + case 1: { + if( ( -AccDesired > fBrake_a0[0] + 12 * fBrake_a1[0] ) ) { + HelperState = 2; + } + break; + } + case 2: { + if( ( -AccDesired > 0 ) && ( -ActualProximityDist > 5 ) ) { + HelperState = 3; + } + break; + } + default: { + break; + } + } +} diff --git a/Driver.h b/Driver.h index 72ed1f51..b45ab861 100644 --- a/Driver.h +++ b/Driver.h @@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/. #include "sound.h" #include "DynObj.h" #include "mtable.h" +#include "translation.h" enum TOrders { // rozkazy dla AI @@ -183,6 +184,7 @@ class TController { friend class TTrain; friend class drivingaid_panel; friend class timetable_panel; + friend class scenario_panel; friend class debug_panel; friend class whois_event; @@ -191,41 +193,46 @@ public: ~TController(); // ai operations logic +// types + using hintpredicate = std::function; // returns true if action hint can be removed + // methods public: - void UpdateSituation( double dt ); // uruchamiac przynajmniej raz na sekundę + void Update( double dt ); // uruchamiac przynajmniej raz na sekundę void MoveTo( TDynamicObject *to ); void TakeControl( bool const Aidriver, bool const Forcevehiclecheck = false ); inline - bool primary( bool const Primary ) { + bool primary( bool const Primary ) { SetFlag( iDrivigFlags, ( Primary ? movePrimary : -movePrimary ) ); - return primary(); - } + return primary(); } inline - bool primary() const { - return ( ( iDrivigFlags & movePrimary ) != 0 ); - }; + bool primary() const { + return ( ( iDrivigFlags & movePrimary ) != 0 ); }; inline - TMoverParameters const *Controlling() const { - return mvControlling; - } + TMoverParameters const *Controlling() const { + return mvControlling; } inline - TMoverParameters const *Occupied() const { - return mvOccupied; - } + TMoverParameters const *Occupied() const { + return mvOccupied; } void DirectionInitial(); void DirectionChange(); inline - int Direction() const { - return iDirection; - } + int Direction() const { + return iDirection; } inline - TAction & action() { - return eAction; - } + TAction & action() { + return eAction; } inline - TAction const & action() const { - return eAction; + TAction const & action() const { + return eAction; } + inline + bool is_train() const { + return TestFlag( mvOccupied->CategoryFlag, 1 ); } + bool is_car() const { + return TestFlag( mvOccupied->CategoryFlag, 2 ); } + bool has_diesel_engine() const { + return ( ( mvControlling->EngineType == TEngineType::DieselElectric ) + || ( mvControlling->EngineType == TEngineType::DieselEngine ) ); } private: void Activation(); // umieszczenie obsady w odpowiednim członie @@ -233,6 +240,7 @@ private: void SetDriverPsyche(); bool IncBrake(); bool DecBrake(); + void LapBrake(); void ZeroLocalBrake(); bool IncSpeed(); bool DecSpeed( bool force = false ); @@ -248,7 +256,8 @@ private: void SetTimeControllers(); /*setting state of time controllers depending of desired action*/ void CheckTimeControllers(); /*checking state of time controllers to reset them to stable position*/ double ESMVelocity( bool Main ); - bool UpdateHeating(); + void PrepareDirection(); + bool PrepareHeating(); // uaktualnia informacje o prędkości void SetVelocity( double NewVel, double NewVelNext, TStopReason r = stopNone ); int CheckDirection(); @@ -265,8 +274,60 @@ private: braking_distance_multiplier( float const Targetvelocity ) const; inline int DrivigFlags() const { - return iDrivigFlags; - }; + return iDrivigFlags; }; + inline + double DirectionalVel() const { + return mvOccupied->Vel * sign( iDirection * mvOccupied->V ); } + + void update_timers( double const dt ); + void update_logs( double const dt ); + void determine_consist_state(); + void determine_braking_distance(); + void determine_proximity_ranges(); + void scan_route( double const Range ); + void scan_obstacles( double const Range ); + void control_wheelslip(); + void control_pantographs(); + void control_horns( double const Timedelta ); + void control_security_system( double const Timedelta ); + void control_handles(); + void control_lights(); + void control_compartment_lights(); + void control_doors(); + void UpdateCommand(); + void handle_engine(); + void handle_orders(); + void UpdateNextStop(); + void check_departure(); + void UpdateConnect(); + void UpdateDisconnect(); + void UpdateChangeDirection(); + void UpdateLooseShunt(); + void UpdateObeyTrain(); + void pick_optimal_speed( double const Range ); + void adjust_desired_speed_for_obstacles(); + void adjust_desired_speed_for_limits(); + void adjust_desired_speed_for_target_speed( double const Range ); + void adjust_desired_speed_for_current_speed(); + void control_tractive_and_braking_force(); + void control_releaser(); + void control_main_pipe(); + void control_relays(); + void control_motor_connectors(); + void control_tractive_force(); + void increase_tractive_force(); + void control_braking_force(); + void apply_independent_brake_only(); + void check_route_ahead( double const Range ); + void check_route_behind( double const Range ); + void UpdateBrakingHelper(); + void hint( locale::string const Value, hintpredicate const Predicate, float const Predicateparameter = 0.f ); + void update_hints(); + void remove_hint( locale::string const Value ); + void remove_train_brake_hints(); + void remove_master_controller_hints(); + void remove_reverser_hints(); + void cue_action( locale::string const Action, float const Actionparameter = 0.f ); // members public: bool AIControllFlag = false; // rzeczywisty/wirtualny maszynista @@ -286,7 +347,7 @@ private: std::string VehicleName; std::array m_lighthints = { -1, -1 }; // suggested light patterns double AccPreferred = 0.0; // preferowane przyspieszenie (wg psychiki kierującego, zmniejszana przy wykryciu kolizji) - double AccDesired = AccPreferred; // przyspieszenie, jakie ma utrzymywać (<0:nie przyspieszaj,<-0.1:hamuj) + double AccDesired = 0.0; // przyspieszenie, jakie ma utrzymywać (<0:nie przyspieszaj,<-0.1:hamuj) double VelDesired = 0.0; // predkość, z jaką ma jechać, wynikająca z analizy tableki; <=VelSignal double fAccDesiredAv = 0.0; // uśrednione przyspieszenie z kolejnych przebłysków świadomości, żeby ograniczyć migotanie double VelforDriver = -1.0; // prędkość, używana przy zmianie kierunku (ograniczenie przy nieznajmości szlaku?) @@ -309,7 +370,10 @@ private: int iDriverFailCount = 0; // licznik błędów AI bool Need_TryAgain = false; // true, jeśli druga pozycja w elektryku nie załapała // bool Need_BrakeRelease = true; + bool IsHeatingTemperatureOK{ true }; + bool IsHeatingTemperatureTooLow{ false }; bool IsAtPassengerStop{ false }; // true if the consist is within acceptable range of w4 post + bool IsScheduledPassengerStopVisible{ false }; double fMinProximityDist = 30.0; // stawanie między 30 a 60 m przed przeszkodą // minimalna oległość do przeszkody, jaką należy zachować double fOverhead1 = 3000.0; // informacja o napięciu w sieci trakcyjnej (0=brak drutu, zatrzymaj!) double fOverhead2 = -1.0; // informacja o sposobie jazdy (-1=normalnie, 0=bez prądu, >0=z opuszczonym i ograniczeniem prędkości) @@ -324,7 +388,7 @@ private: double IdleTime{}; // keeps track of time spent at a stop double fStopTime = 0.0; // czas postoju przed dalszą jazdą (np. na przystanku) float ExchangeTime{ 0.0 }; // time needed to finish current load exchange - double fShuntVelocity = 40.0; // maksymalna prędkość manewrowania, zależy m.in. od składu // domyślna prędkość manewrowa + double fShuntVelocity = 25.0; // prędkość manewrowania, zależy m.in. od składu int iDrivigFlags = // flagi bitowe ruchu moveStopPoint | // podjedź do W4 możliwie blisko moveStopHere | // nie podjeżdżaj do semafora, jeśli droga nie jest wolna @@ -336,7 +400,7 @@ private: double fBrakeReaction = 1.0; //opóźnienie zadziałania hamulca - czas w s / (km/h) double fNominalAccThreshold = 0.0; // nominalny próg opóźnienia dla zadziałania hamulca double fAccThreshold = 0.0; // aktualny próg opóźnienia dla zadziałania hamulca - double AbsAccS_pub = 0.0; // próg opóźnienia dla zadziałania hamulca + double AbsAccS = 0.0; // próg opóźnienia dla zadziałania hamulca // dla fBrake_aX: // indeks [0] - wartości odpowiednie dla aktualnej prędkości // a potem jest 20 wartości dla różnych prędkości zmieniających się co 5 % Vmax pojazdu obsadzonego @@ -351,6 +415,7 @@ private: double fActionTime = 0.0; // czas używany przy regulacji prędkości i zamykaniu drzwi double m_radiocontroltime{ 0.0 }; // timer used to control speed of radio operations TAction eAction{ TAction::actUnknown }; // aktualny stan + std::list< std::tuple > m_hints; // queued ai operations // orders // methods @@ -396,8 +461,10 @@ private: void TableTraceRoute( double fDistance, TDynamicObject *pVehicle ); void TableCheck( double fDistance ); TCommandType TableUpdate( double &fVelDes, double &fDist, double &fNext, double &fAcc ); + bool TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, double const Signaldistance ); + bool TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPos &Point, double &Signaldistance, int const Pointindex ); // returns most recently calculated distance to potential obstacle ahead - double TrackBlock() const; + double TrackObstacle() const; void TablePurger(); void TableSort(); inline double MoveDistanceGet() const { @@ -410,11 +477,11 @@ private: void TableClear(); int TableDirection() { return iTableDirection; } // Ra: stare funkcje skanujące, używane do szukania sygnalizatora z tyłu - bool IsOccupiedByAnotherConsist( TTrack *Track ); + bool IsOccupiedByAnotherConsist( TTrack *Track, double const Distance ); basic_event *CheckTrackEventBackward( double fDirection, TTrack *Track, TDynamicObject *Vehicle, int const Eventdirection = 1, end const End = end::rear ); TTrack *BackwardTraceRoute( double &fDistance, double &fDirection, TDynamicObject *Vehicle, basic_event *&Event, int const Eventdirection = 1, end const End = end::rear, bool const Untiloccupied = true ); void SetProximityVelocity( double dist, double vel, glm::dvec3 const *pos ); - TCommandType BackwardScan(); + TCommandType BackwardScan( double const Range ); std::string TableText( std::size_t const Index ) const; /* void RouteSwitch(int d); @@ -471,7 +538,6 @@ private: bool doors_open() const; bool doors_permit_active() const; void AutoRewident(); // ustawia hamulce w składzie - void UpdatePantographs(); void announce( announcement_t const Announcement ); // members double fLength = 0.0; // długość składu (do wyciągania z ograniczeń) @@ -485,6 +551,7 @@ private: bool IsHeavyCargoTrain{ false }; double fReady = 0.0; // poziom odhamowania wagonów bool Ready = false; // ABu: stan gotowosci do odjazdu - sprawdzenie odhamowania wagonow + bool IsConsistBraked { false }; double ConsistShade{ 1.0 }; // averaged amount of sunlight received by the consist TDynamicObject *pVehicles[ 2 ]; // skrajne pojazdy w składzie (niekoniecznie bezpośrednio sterowane) bool DoesAnyDoorNeedOpening{ false }; @@ -495,6 +562,9 @@ private: bool IsAnyMotorOverloadRelayOpen{ false }; // state of motor overload relays in all vehicles under control bool IsAnyGroundRelayOpen{ false }; bool IsAnyCompressorEnabled{ false }; + bool IsAnyCompressorExplicitlyEnabled{ false }; // only takes into account manually controlled devices + bool IsAnyConverterEnabled{ false }; + bool IsAnyConverterExplicitlyEnabled{ false }; // only takes into account manually controlled devices bool IsAnyCouplerStretched{ false }; // whether there's a coupler in the consist stretched above limit // logs diff --git a/DynObj.cpp b/DynObj.cpp index 99d07a17..ad606f0b 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -410,12 +410,12 @@ int TDynamicObject::GetPneumatic(bool front, bool red) y = btPneumatic2r.GetStatus(); } z = 0; // brak węży? - if ((x == 1) && (y == 1)) - z = 3; // dwa proste - if ((x == 2) && (y == 0)) - z = 1; // lewy skośny, brak prawego - if ((x == 0) && (y == 2)) - z = 2; // brak lewego, prawy skośny + if ((x > 0) && (y > 0)) + z = 3; // dwa + if ((x > 0) && (y == 0)) + z = 1; // lewy, brak prawego + if ((x == 0) && (y > 0)) + z = 2; // brak lewego, prawy return z; } @@ -975,64 +975,46 @@ void TDynamicObject::ABuLittleUpdate(double ObjSqrDist) // else btCPass2.TurnOff(); if (MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable) { // sygnaly konca pociagu - if (m_endsignals1.Active()) - { - if (TestFlag(MoverParameters->iLights[0], 2) || TestFlag(MoverParameters->iLights[0], 32)) - { + if (m_endsignals1.Active()) { + if (TestFlag(MoverParameters->iLights[end::front], ( light::redmarker_left | light::redmarker_right ) ) ) { m_endsignals1.Turn( true ); btnOn = true; } - // else btEndSignals1.TurnOff(); } - else - { - if (TestFlag(MoverParameters->iLights[0], 2)) - { + else { + if (TestFlag(MoverParameters->iLights[end::front], light::redmarker_left)) { m_endsignal13.Turn( true ); btnOn = true; } - // else btEndSignals11.TurnOff(); - if (TestFlag(MoverParameters->iLights[0], 32)) - { + if (TestFlag(MoverParameters->iLights[end::front], light::redmarker_right)) { m_endsignal12.Turn( true ); btnOn = true; } - // else btEndSignals13.TurnOff(); } - if (m_endsignals2.Active()) - { - if (TestFlag(MoverParameters->iLights[1], 2) || TestFlag(MoverParameters->iLights[1], 32)) - { + if (m_endsignals2.Active()) { + if (TestFlag(MoverParameters->iLights[end::rear], ( light::redmarker_left | light::redmarker_right ) ) ) { m_endsignals2.Turn( true ); btnOn = true; } - // else btEndSignals2.TurnOff(); } - else - { - if (TestFlag(MoverParameters->iLights[1], 2)) - { + else { + if (TestFlag(MoverParameters->iLights[end::rear], light::redmarker_left)) { m_endsignal23.Turn( true ); btnOn = true; } - // else btEndSignals21.TurnOff(); - if (TestFlag(MoverParameters->iLights[1], 32)) - { + if (TestFlag(MoverParameters->iLights[end::rear], light::redmarker_right)) { m_endsignal22.Turn( true ); btnOn = true; } - // else btEndSignals23.TurnOff(); } } // tablice blaszane: - if (TestFlag(MoverParameters->iLights[end::front], light::rearendsignals)) - { + if (TestFlag(MoverParameters->iLights[end::front], light::rearendsignals)) { m_endtab1.Turn( true ); btnOn = true; } // else btEndSignalsTab1.TurnOff(); - if (TestFlag(MoverParameters->iLights[end::rear], light::rearendsignals)) - { + if (TestFlag(MoverParameters->iLights[end::rear], light::rearendsignals)) { m_endtab2.Turn( true ); btnOn = true; } @@ -1851,7 +1833,7 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" if (!MoverParameters->LoadFIZ(asBaseDir)) { // jak wczytanie CHK się nie uda, to błąd if (ConversionError == 666) - ErrorLog( "Bad vehicle: failed do locate definition file \"" + BaseDir + "/" + Type_Name + ".fiz" + "\"" ); + ErrorLog( "Bad vehicle: failed to locate definition file \"" + BaseDir + "/" + Type_Name + ".fiz" + "\"" ); else { ErrorLog( "Bad vehicle: failed to load definition from file \"" + BaseDir + "/" + Type_Name + ".fiz\" (error " + to_string( ConversionError ) + ")" ); } @@ -2980,64 +2962,41 @@ bool TDynamicObject::Update(double dt, double dt1) else { MoverParameters->InsideConsist = false; } - // HACK: we're using sound event to detect whether vehicle was connected to another if( TestFlag( MoverParameters->AIFlag, sound::attachcoupler ) ) { auto *driver{ ctOwner ? ctOwner : Mechanik }; if( driver != nullptr ) { driver->CheckVehicles( Connect ); } - SetFlag( MoverParameters->AIFlag, -sound::attachcoupler ); + ClearFlag( MoverParameters->AIFlag, sound::attachcoupler ); } // napiecie sieci trakcyjnej - // Ra 15-01: przeliczenie poboru prądu powinno być robione wcześniej, żeby na - // tym etapie były - // znane napięcia - // TTractionParam tmpTraction; - // tmpTraction.TractionVoltage=0; - if (MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) - { // dla EZT tylko silnikowy - // if (Global.bLiveTraction) - { // Ra 2013-12: to niżej jest chyba trochę bez sensu - tmpTraction.TractionVoltage = std::max( std::abs( MoverParameters->PantRearVolt ), std::abs( MoverParameters->PantFrontVolt ) ); - /* - if (v == 0.0) { - v = MoverParameters->PantFrontVolt; - if( v == 0.0 ) { -// if( MoverParameters->TrainType & ( dt_EZT | dt_ET40 | dt_ET41 | dt_ET42 ) ) { - // dwuczłony mogą mieć sprzęg WN - // NOTE: condition disabled, other vehicles types can have power cables as well - v = MoverParameters->GetTrainsetVoltage(); // ostatnia szansa -// } - } - } - */ - if ( tmpTraction.TractionVoltage > 0.0) - { // jeśli jest zasilanie - NoVoltTime = 0; + if (MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) { // dla EZT tylko silnikowy + + tmpTraction.TractionVoltage = std::max( std::abs( MoverParameters->PantRearVolt ), std::abs( MoverParameters->PantFrontVolt ) ); + // jeśli brak zasilania dłużej niż 0.2 sekundy (25km/h pod izolatorem daje 0.15s) + // Ra 2F1H: prowizorka, trzeba przechować napięcie, żeby nie wywalało WS pod izolatorem + if( tmpTraction.TractionVoltage > 0.0) { + NoVoltTime = 0; + } + else { + NoVoltTime += dt1; + if( NoVoltTime <= 0.2 ) { + tmpTraction.TractionVoltage = MoverParameters->PantographVoltage; } else { - NoVoltTime += dt1; - if( NoVoltTime > 0.2 ) { - // jeśli brak zasilania dłużej niż 0.2 sekundy (25km/h pod izolatorem daje 0.15s) - // Ra 2F1H: prowizorka, trzeba przechować napięcie, żeby nie wywalało WS pod izolatorem - if( MoverParameters->Vel > 0.5 ) { - // jeśli jedzie - // Ra 2014-07: doraźna blokada logowania zimnych lokomotyw - zrobić to trzeba inaczej - if( MoverParameters->Pantographs[end::front].is_active - || MoverParameters->Pantographs[end::rear].is_active ) { - - if( ( MoverParameters->Mains ) - && ( MoverParameters->GetTrainsetHighVoltage() < 0.1f ) ) { - // Ra 15-01: logować tylko, jeśli WS załączony - // yB 16-03: i nie jest to asynchron zasilany z daleka - // Ra 15-01: bezwzględne współrzędne pantografu nie są dostępne, - // więc lepiej się tego nie zaloguje - ErrorLog( - "Bad traction: " + MoverParameters->Name - + " lost power for " + to_string( NoVoltTime, 2 ) + " sec. at " - + to_string( glm::dvec3{ vPosition } ) ); - } + // jeśli jedzie + if( MoverParameters->Vel > 0.5 ) { + // Ra 2014-07: doraźna blokada logowania zimnych lokomotyw - zrobić to trzeba inaczej + if( MoverParameters->Pantographs[end::front].is_active + || MoverParameters->Pantographs[end::rear].is_active ) { + if( ( MoverParameters->Mains ) // Ra 15-01: logować tylko, jeśli WS załączony + && ( MoverParameters->GetTrainsetHighVoltage() < 0.1f ) ) { // yB 16-03: i nie jest to asynchron zasilany z daleka + // Ra 15-01: bezwzględne współrzędne pantografu nie są dostępne więc lepiej się tego nie zaloguje + ErrorLog( + "Bad traction: " + MoverParameters->Name + + " lost power for " + to_string( NoVoltTime, 2 ) + " sec. at " + + to_string( glm::dvec3{ vPosition } ) ); } } } @@ -3053,13 +3012,14 @@ bool TDynamicObject::Update(double dt, double dt1) MoverParameters->PantographVoltage = tmpTraction.TractionVoltage; // McZapkie: predkosc w torze przekazac do TrackParam - // McZapkie: Vel ma wymiar [km/h] (absolutny), V ma wymiar [m/s], taka - // przyjalem notacje + // McZapkie: Vel ma wymiar [km/h] (absolutny), V ma wymiar [m/s], taka przyjalem notacje tp.Velmax = MyTrack->VelocityGet(); if (Mechanik) { // Ra 2F3F: do Driver.cpp to przenieść? - MoverParameters->EqvtPipePress = GetEPP(); // srednie cisnienie w PG + if( Mechanik->primary() ) { + MoverParameters->EqvtPipePress = GetEPP(); // srednie cisnienie w PG + } if ((Mechanik->primary()) && ((MoverParameters->EngineType == TEngineType::DieselEngine) || (MoverParameters->EngineType == TEngineType::DieselElectric)) @@ -3395,7 +3355,7 @@ bool TDynamicObject::Update(double dt, double dt1) MED_oldFED = FzadED; } - Mechanik->UpdateSituation(dt1); // przebłyski świadomości AI + Mechanik->Update(dt1); // przebłyski świadomości AI } // fragment "z EXE Kursa" @@ -4100,19 +4060,36 @@ void TDynamicObject::RenderSounds() { if( MoverParameters->CompressorSpeed > 0.0 ) { // McZapkie! - dzwiek compressor.wav tylko gdy dziala sprezarka if( MoverParameters->CompressorFlag ) { + // for compressor coupled with the diesel engine sound pitch is driven by engine revolutions if( MoverParameters->CompressorPower == 3 ) { // presume the compressor sound is recorded for idle revolutions // increase the pitch according to increase of engine revolutions - auto const enginefactor { ( MoverParameters->EngineMaxRPM() / MoverParameters->EngineIdleRPM() ) * MoverParameters->EngineRPMRatio() }; - sCompressor.pitch( + auto const enginefactor { clamp( // try to keep the sound pitch in semi-reasonable range - enginefactor, - 0.5, 2.5 ) ); + MoverParameters->EngineMaxRPM() / MoverParameters->EngineIdleRPM() * MoverParameters->EngineRPMRatio(), + 0.5, 2.5 ) }; + sCompressor.pitch( enginefactor ); + sCompressorIdle.pitch( enginefactor ); + } + if( sCompressorIdle.empty() ) { + // legacy sound path, if there's no dedicated idle sound + sCompressor.play( sound_flags::exclusive | sound_flags::looping ); + } + else { + // enhanced sound path, with dedicated sound for idling compressor + if( MoverParameters->CompressorGovernorLock ) { + sCompressor.stop(); + sCompressorIdle.play( sound_flags::exclusive | sound_flags::looping ); + } + else { + sCompressor.play( sound_flags::exclusive | sound_flags::looping ); + sCompressorIdle.stop(); + } } - sCompressor.play( sound_flags::exclusive | sound_flags::looping ); } else { sCompressor.stop(); + sCompressorIdle.stop(); } } @@ -4254,7 +4231,7 @@ void TDynamicObject::RenderSounds() { auto brakeforceratio{ 0.0 }; if( //( false == mvOccupied->SlippingWheels ) && ( MoverParameters->UnitBrakeForce > 10.0 ) - && ( MoverParameters->Vel > 0.05 ) ) { + && ( MoverParameters->Vel > 0.05 ) ) { brakeforceratio = clamp( @@ -5499,15 +5476,19 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co m_powertrainsounds.water_heater.owner( this ); } - else if( ( token == "tractionmotor:" ) + else if( ( ( token == "tractionmotor:" ) || ( token == "tractionacmotor:" ) ) && ( MoverParameters->Power > 0 ) ) { + // dc motors are (legacy) default + m_powertrainsounds.dcmotors = ( token == "tractionmotor:" ); // plik z dzwiekiem silnika, mnozniki i ofsety amp. i czest. sound_source motortemplate { sound_placement::external }; motortemplate.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); motortemplate.owner( this ); - auto const amplitudedivisor = static_cast( MoverParameters->nmax * 60 + MoverParameters->Power * 3 ); - motortemplate.m_amplitudefactor /= amplitudedivisor; + if( m_powertrainsounds.dcmotors ) { + auto const amplitudedivisor = static_cast( MoverParameters->nmax * 60 + MoverParameters->Power * 3 ); + motortemplate.m_amplitudefactor /= amplitudedivisor; + } if( true == m_powertrainsounds.motors.empty() ) { // fallback for cases without specified motor locations, convert sound template to a single sound source @@ -5584,13 +5565,22 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co else if( token == "transmission:" ) { // plik z dzwiekiem, mnozniki i ofsety amp. i czest. // NOTE, fixed default parameters, legacy system leftover - m_powertrainsounds.transmission.m_amplitudefactor = 0.029; - m_powertrainsounds.transmission.m_amplitudeoffset = 0.1; - m_powertrainsounds.transmission.m_frequencyfactor = 0.005; - m_powertrainsounds.transmission.m_frequencyoffset = 1.0; + auto &sound { m_powertrainsounds.transmission }; + sound.m_amplitudefactor = 0.029; + sound.m_amplitudeoffset = 0.1; + sound.m_frequencyfactor = 0.005; + sound.m_frequencyoffset = 1.0; - m_powertrainsounds.transmission.deserialize( parser, sound_type::single, sound_parameters::range ); - m_powertrainsounds.transmission.owner( this ); + sound.deserialize( parser, sound_type::single, sound_parameters::range ); + sound.owner( this ); + } + + else if( token == "brakesound:" ) { + // hamowanie zwykle: + rsBrake.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency ); + rsBrake.owner( this ); + // NOTE: can't pre-calculate amplitude normalization based on max brake force, as this varies depending on vehicle speed + rsBrake.m_frequencyfactor /= ( 1 + MoverParameters->Vmax ); } else if( token == "brake:" ) { @@ -5696,6 +5686,12 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co sCompressor.owner( this ); } + else if( token == "compressoridle:" ) { + // pliki ze sprezarka + sCompressorIdle.deserialize( parser, sound_type::multipart, sound_parameters::range ); + sCompressorIdle.owner( this ); + } + else if( token == "converter:" ) { // pliki z przetwornica sConverter.deserialize( parser, sound_type::multipart, sound_parameters::range ); @@ -6066,14 +6062,6 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co dsbPneumaticRelay.deserialize( parser, sound_type::single ); dsbPneumaticRelay.owner( this ); } - // braking sounds - else if( token == "brakesound:" ) { - // hamowanie zwykle: - rsBrake.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency ); - rsBrake.owner( this ); - // NOTE: can't pre-calculate amplitude normalization based on max brake force, as this varies depending on vehicle speed - rsBrake.m_frequencyfactor /= ( 1 + MoverParameters->Vmax ); - } else if( token == "slipperysound:" ) { // sanie: rsSlippery.deserialize( parser, sound_type::single, sound_parameters::amplitude ); @@ -6415,7 +6403,7 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co // other engine compartment sounds auto const nullvector { glm::vec3() }; std::vector enginesounds = { - &sConverter, &sCompressor, &sSmallCompressor, &sHeater + &sConverter, &sCompressor, &sCompressorIdle, &sSmallCompressor, &sHeater }; for( auto sound : enginesounds ) { if( sound->offset() == nullvector ) { @@ -6641,8 +6629,7 @@ void TDynamicObject::RaLightsSet(int head, int rear) // EN57 może nie mieć końcówek od środka członu if (MoverParameters->Power > 1.0) // jeśli ma moc napędową if (!MoverParameters->DirActive) // jeśli nie ma ustawionego kierunku - { // jeśli ma zarówno światła jak i końcówki, ustalić, czy jest w stanie - // aktywnym + { // jeśli ma zarówno światła jak i końcówki, ustalić, czy jest w stanie aktywnym // np. lokomotywa na zimno będzie mieć końcówki a nie światła rear = light::rearendsignals; // tablice blaszane // trzeba to uzależnić od "załączenia baterii" w pojeździe @@ -6659,22 +6646,66 @@ void TDynamicObject::RaLightsSet(int head, int rear) rear = light::rearendsignals; // tablice blaszane } } - if (iDirection) // w zależności od kierunku pojazdu w składzie - { // jesli pojazd stoi sprzęgiem 0 w stronę czoła - if (head >= 0) - MoverParameters->iLights[0] = head; - if (rear >= 0) - MoverParameters->iLights[1] = rear; + // w zależności od kierunku pojazdu w składzie + if( head >= 0 ) { + auto const vehicleend { iDirection > 0 ? end::front : end::rear }; + MoverParameters->iLights[ vehicleend ] = ( head & iInventory[ vehicleend ] ); } - else - { // jak jest odwrócony w składzie (-1), to zapalamy odwrotnie - if (head >= 0) - MoverParameters->iLights[1] = head; - if (rear >= 0) - MoverParameters->iLights[0] = rear; + if( rear >= 0 ) { + auto const vehicleend{ iDirection > 0 ? end::rear : end::front }; + MoverParameters->iLights[ vehicleend ] = ( rear & iInventory[ vehicleend ] ); } }; +bool TDynamicObject::has_signal_pc1_on() const { + + auto const vehicleend { iDirection > 0 ? end::front : end::rear }; + auto const equippedlights { iInventory[ vehicleend ] }; + auto const pattern { equippedlights & ( light::headlight_left | light::headlight_right | light::headlight_upper ) }; + auto const patternfallback { equippedlights & ( light::auxiliary_left | light::auxiliary_right | light::headlight_upper ) }; + auto const hasauxiliarylights { ( equippedlights & ( light::auxiliary_left | light::auxiliary_right ) ) != 0 }; + auto const activelights { MoverParameters->iLights[ vehicleend ] }; + + return ( ( ( pattern != 0 ) && ( activelights == pattern ) ) + || ( ( hasauxiliarylights ) && ( activelights == patternfallback ) ) + || ( ( pattern == 0 ) && ( patternfallback == 0 ) && ( activelights == light::rearendsignals ) ) ); // pc4 +} + +bool TDynamicObject::has_signal_pc2_on() const { + + auto const vehicleend { iDirection > 0 ? end::front : end::rear }; + auto const equippedlights { iInventory[ vehicleend ] }; + auto const pattern { equippedlights & ( light::redmarker_left | light::headlight_right | light::headlight_upper ) }; + auto const patternfallback { equippedlights & ( light::redmarker_left | light::auxiliary_right | light::headlight_upper ) }; + auto const hasauxiliarylights { ( equippedlights & ( light::auxiliary_left | light::auxiliary_right ) ) != 0 }; + auto const activelights { MoverParameters->iLights[ vehicleend ] }; + + return ( ( activelights == pattern ) + || ( hasauxiliarylights && ( activelights == patternfallback ) ) ); +} + +bool TDynamicObject::has_signal_pc5_on() const { + + auto const vehicleend { iDirection > 0 ? end::rear : end::front }; + auto const equippedlights { iInventory[ vehicleend ] }; + auto const pattern { equippedlights & ( light::redmarker_left | light::redmarker_right ) }; + auto const patternfallback { equippedlights & ( light::rearendsignals ) }; + auto const activelights { MoverParameters->iLights[ vehicleend ] }; + + return ( ( ( pattern != 0 ) && ( activelights == pattern ) ) + || ( ( patternfallback != 0 ) && ( activelights == patternfallback ) ) ); +} + +bool TDynamicObject::has_signal_on( int const Side, int const Pattern ) const { +/* + auto const vehicleend { iDirection > 0 ? Side : 1 - Side }; + auto const pattern { iInventory[ vehicleend ] & Pattern }; + + return ( MoverParameters->iLights[ vehicleend ] == pattern ); +*/ + return ( MoverParameters->iLights[ Side ] == ( Pattern & iInventory[ Side ] ) ); +} + int TDynamicObject::DirectionSet(int d) { // ustawienie kierunku w składzie (wykonuje AI) auto const lastdirection { iDirection }; @@ -6723,7 +6754,7 @@ TDynamicObject * TDynamicObject::NextC(int C) const { nullptr ); // hide neighbour lacking specified connection type } - // checks whether there's unbroken connection of specified type to specified vehicle +// checks whether there's unbroken connection of specified type to specified vehicle bool TDynamicObject::is_connected( TDynamicObject const *Vehicle, coupling const Coupling ) const { @@ -6749,7 +6780,7 @@ TDynamicObject::is_connected( TDynamicObject const *Vehicle, coupling const Coup return true; } } - // no lack in either direction, give up + // no luck in either direction, give up return false; } @@ -7062,22 +7093,30 @@ material_handle TDynamicObject::DestinationFind( std::string Destination ) { if( Destination.empty() ) { return null_handle; } - Destination = Bezogonkow( Destination ); // do szukania pliku obcinamy ogonki // destination textures are kept in the vehicle's directory so we point the current texture path there auto const currenttexturepath { Global.asCurrentTexturePath }; Global.asCurrentTexturePath = asBaseDir; - // now see if we can find any version of the texture - std::vector const destinations { - Destination + '@' + MoverParameters->TypeName, - Destination }; auto destinationhandle { null_handle }; - for( auto const &destination : destinations ) { - auto material = TextureTest( ToLower( destination ) ); - if( false == material.empty() ) { - destinationhandle = GfxRenderer->Fetch_Material( material ); - break; + if( starts_with( Destination, "make:" ) ) { + // autogenerated texture + destinationhandle = GfxRenderer->Fetch_Material( Destination ); + } + else { + // regular texture + Destination = Bezogonkow( Destination ); // do szukania pliku obcinamy ogonki + // now see if we can find any version of the texture + std::vector const destinations { + Destination + '@' + MoverParameters->TypeName, + Destination }; + + for( auto const &destination : destinations ) { + auto material = TextureTest( ToLower( destination ) ); + if( false == material.empty() ) { + destinationhandle = GfxRenderer->Fetch_Material( material ); + break; + } } } // whether we got anything, restore previous texture path @@ -7549,91 +7588,113 @@ TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, doub // motor sounds volume = 0.0; if( false == motors.empty() ) { + // ac and dc motors have significantly different sounds + if( dcmotors ) { - if( std::abs( Vehicle.nrot ) > 0.01 ) { + if( std::abs( Vehicle.nrot ) > 0.01 ) { - auto const &motor { motors.front() }; - // frequency calculation - auto normalizer { 1.f }; - if( true == motor.is_combined() ) { - // for combined motor sound we calculate sound point in rpm, to make .mmd files setup easier - // NOTE: we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range - normalizer = 60.f * 0.01f; - } - auto const motorrevolutions { std::abs( Vehicle.nrot ) * Vehicle.Transmision.Ratio }; - frequency = - motor.m_frequencyoffset - + motor.m_frequencyfactor * motorrevolutions * normalizer; - - // base volume calculation - switch( Vehicle.EngineType ) { - case TEngineType::ElectricInductionMotor: { - volume = - motor.m_amplitudeoffset - + motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 2 ); - break; + auto const &motor { motors.front() }; + // frequency calculation + auto normalizer { 1.f }; + if( true == motor.is_combined() ) { + // for combined motor sound we calculate sound point in rpm, to make .mmd files setup easier + // NOTE: we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range + normalizer = 60.f * 0.01f; } - case TEngineType::ElectricSeriesMotor: { - volume = - motor.m_amplitudeoffset - + motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 60.0 ); - break; - } - default: { - volume = - motor.m_amplitudeoffset - + motor.m_amplitudefactor * motorrevolutions * 60.0; - break; - } - } + auto const motorrevolutions { std::abs( Vehicle.nrot ) * Vehicle.Transmision.Ratio }; + frequency = + motor.m_frequencyoffset + + motor.m_frequencyfactor * motorrevolutions * normalizer; - if( Vehicle.EngineType == TEngineType::ElectricSeriesMotor ) { - // volume variation - if( ( volume < 1.0 ) - && ( Vehicle.EnginePower < 100 ) ) { - - auto const volumevariation { LocalRandom( 100 ) * Vehicle.enrot / ( 1 + Vehicle.nmax ) }; - if( volumevariation < 2 ) { - volume += volumevariation / 200; + // base volume calculation + switch( Vehicle.EngineType ) { + case TEngineType::ElectricInductionMotor: { + volume = + motor.m_amplitudeoffset + + motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 2 ); + break; + } + case TEngineType::ElectricSeriesMotor: { + volume = + motor.m_amplitudeoffset + + motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 60.0 ); + break; + } + default: { + volume = + motor.m_amplitudeoffset + + motor.m_amplitudefactor * motorrevolutions * 60.0; + break; } } - if( ( Vehicle.DynamicBrakeFlag ) - && ( Vehicle.EnginePower > 0.1 ) ) { - // Szociu - 29012012 - jeżeli uruchomiony jest hamulec elektrodynamiczny, odtwarzany jest dźwięk silnika - volume += 0.8; + if( Vehicle.EngineType == TEngineType::ElectricSeriesMotor ) { + // volume variation + if( ( volume < 1.0 ) + && ( Vehicle.EnginePower < 100 ) ) { + + auto const volumevariation { LocalRandom( 100 ) * Vehicle.enrot / ( 1 + Vehicle.nmax ) }; + if( volumevariation < 2 ) { + volume += volumevariation / 200; + } + } + + if( ( Vehicle.DynamicBrakeFlag ) + && ( Vehicle.EnginePower > 0.1 ) ) { + // Szociu - 29012012 - jeżeli uruchomiony jest hamulec elektrodynamiczny, odtwarzany jest dźwięk silnika + volume += 0.8; + } } - } - // scale motor volume based on whether they're active - motor_momentum = - clamp( - motor_momentum - - 1.0 * Deltatime // smooth out decay - + std::abs( Vehicle.Mm ) / 60.0 * Deltatime, - 0.0, 1.25 ); - volume *= std::max( 0.25f, motor_momentum ); - motor_volume = interpolate( motor_volume, volume, 0.25 ); - if( motor_volume >= 0.05 ) { - // apply calculated parameters to all motor instances - for( auto &motor : motors ) { - motor - .pitch( frequency ) - .gain( motor_volume ) - .play( sound_flags::exclusive | sound_flags::looping ); + // scale motor volume based on whether they're active + motor_momentum = + clamp( + motor_momentum + - 1.0 * Deltatime // smooth out decay + + std::abs( Vehicle.Mm ) / 60.0 * Deltatime, + 0.0, 1.25 ); + volume *= std::max( 0.25f, motor_momentum ); + motor_volume = interpolate( motor_volume, volume, 0.25 ); + if( motor_volume >= 0.05 ) { + // apply calculated parameters to all motor instances + for( auto &motor : motors ) { + motor + .pitch( frequency ) + .gain( motor_volume ) + .play( sound_flags::exclusive | sound_flags::looping ); + } + } + else { + // stop all motor instances + for( auto &motor : motors ) { + motor.stop(); + } } } else { // stop all motor instances + motor_volume = 0.0; for( auto &motor : motors ) { motor.stop(); } } } else { - // stop all motor instances - motor_volume = 0.0; - for( auto &motor : motors ) { - motor.stop(); + // ac motors + if( Vehicle.EngineType == TEngineType::ElectricInductionMotor ) { + if( Vehicle.InverterFrequency > 0.001 ) { + + for( auto &motor : motors ) { + motor + .pitch( motor.m_frequencyoffset + motor.m_frequencyfactor * Vehicle.InverterFrequency ) + .gain( motor.m_amplitudeoffset + motor.m_amplitudefactor * std::sqrt( std::abs( Vehicle.eimv_pr ) ) ) + .play( sound_flags::exclusive | sound_flags::looping ); + } + } + else { + for( auto &motor : motors ) { + motor.stop(); + } + } } } } diff --git a/DynObj.h b/DynObj.h index 38be5f90..79ba289e 100644 --- a/DynObj.h +++ b/DynObj.h @@ -138,7 +138,7 @@ public: }; // void _fastcall Update(); //wskaźnik do funkcji aktualizacji animacji int iFlags{ 0 }; // flagi animacji - float fMaxDist; // do jakiej odległości wykonywana jest animacja + float fMaxDist; // do jakiej odległości wykonywana jest animacja NOTE: square of actual distance float fSpeed; // parametr szybkości animacji int iNumber; // numer kolejny obiektu @@ -374,6 +374,7 @@ private: sound_source inverter { sound_placement::engine }; std::vector motorblowers; std::vector motors; // generally traction motor(s) + bool dcmotors { true }; // traction dc motor(s) double motor_volume { 0.0 }; // MC: pomocnicze zeby gladziej silnik buczal float motor_momentum { 0.f }; // recent change in motor revolutions sound_source motor_relay { sound_placement::engine }; @@ -473,6 +474,7 @@ private: powertrain_sounds m_powertrainsounds; sound_source sConverter { sound_placement::engine }; sound_source sCompressor { sound_placement::engine }; // NBMX wrzesien 2003 + sound_source sCompressorIdle { sound_placement::engine }; sound_source sSmallCompressor { sound_placement::engine }; sound_source sHeater { sound_placement::engine }; // braking sounds @@ -710,6 +712,10 @@ private: void SetLights(); void RaLightsSet(int head, int rear); int LightList( end const Side ) const { return iInventory[ Side ]; } + bool has_signal_pc1_on() const; + bool has_signal_pc2_on() const; + bool has_signal_pc5_on() const; + bool has_signal_on( int const Side, int const Pattern ) const; void set_cab_lights( int const Cab, float const Level ); TDynamicObject * FirstFind(int &coupler_nr, int cf = 1); float GetEPP(); // wyliczanie sredniego cisnienia w PG @@ -729,6 +735,8 @@ private: auto find_vehicle( coupling const Coupling, Predicate_ const Predicate ) -> TDynamicObject *; TDynamicObject * FindPowered(); TDynamicObject * FindPantographCarrier(); + template + void for_each( coupling const Coupling, UnaryFunction_ const Function ); void ParamSet(int what, int into); // zapytanie do AI, po którym segmencie skrzyżowania jechać int RouteWish(TTrack *tr); @@ -829,4 +837,19 @@ TDynamicObject::find_vehicle( coupling const Coupling, Predicate_ const Predicat return nullptr; } +template +void +TDynamicObject::for_each( coupling const Coupling, UnaryFunction_ const Function ) { + + Function( this ); + // walk first towards the rear + auto *vehicle { this }; + while( ( vehicle = vehicle->NextC( Coupling ) ) != nullptr ) { + Function( vehicle ); } + // then towards the front + vehicle = this; + while( ( vehicle = vehicle->PrevC( Coupling ) ) != nullptr ) { + Function( vehicle ); } +} + //--------------------------------------------------------------------------- diff --git a/Event.cpp b/Event.cpp index b59c9fa3..51991a20 100644 --- a/Event.cpp +++ b/Event.cpp @@ -1050,7 +1050,7 @@ whois_event::run_() { -1.0 ) }; auto const collisiondistance { ( owner != nullptr ? - owner->TrackBlock() : + owner->TrackObstacle() : -1.0 ) }; targetcell->UpdateValues( diff --git a/Globals.cpp b/Globals.cpp index 0cd9817c..bc042f2a 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -486,6 +486,7 @@ global_settings::ConfigParse(cParser &Parser) { // tryb antyaliasingu: 0=brak,1=2px,2=4px Parser.getTokens(1, false); Parser >> iMultisampling; + iMultisampling = clamp( iMultisampling, 0, 3 ); } else if (token == "latitude") { @@ -658,6 +659,11 @@ global_settings::ConfigParse(cParser &Parser) { Parser.getTokens(1, false); Parser >> iHiddenEvents; } + else if( token == "ai.trainman" ) { + // czy łączyć eventy z torami poprzez nazwę toru + Parser.getTokens( 1, false ); + Parser >> AITrainman; + } else if (token == "pause") { // czy po wczytaniu ma być pauza? @@ -1045,6 +1051,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "scenario.time.offset", ScenarioTimeOffset ); export_as_text( Output, "scenario.time.current", ScenarioTimeCurrent ); export_as_text( Output, "scenario.weather.temperature", AirTemperature ); + export_as_text( Output, "ai.trainman", AITrainman ); export_as_text( Output, "scalespeculars", ScaleSpecularValues ); export_as_text( Output, "gfxrenderer", GfxRenderer ); export_as_text( Output, "shadows", RenderShadows ); diff --git a/Globals.h b/Globals.h index e485e882..06cdd629 100644 --- a/Globals.h +++ b/Globals.h @@ -100,6 +100,7 @@ struct global_settings { bool bRollFix{ true }; // czy wykonać przeliczanie przechyłki bool bJoinEvents{ false }; // czy grupować eventy o tych samych nazwach int iHiddenEvents{ 1 }; // czy łączyć eventy z torami poprzez nazwę toru + bool AITrainman{ true }; // virtual assistant performing consist coupling/decoupling and other maintenance tasks // ui int PythonScreenUpdateRate{ 200 }; // delay between python-based screen updates, in milliseconds int iTextMode{ 0 }; // tryb pracy wyświetlacza tekstowego @@ -116,7 +117,7 @@ struct global_settings { bool bWireFrame{ false }; bool bAdjustScreenFreq{ true }; float BaseDrawRange{ 2500.f }; - int DynamicLightCount{ 3 }; + int DynamicLightCount{ 7 }; bool ScaleSpecularValues{ true }; std::string GfxRenderer{ "default" }; bool LegacyRenderer{ false }; diff --git a/Logs.h b/Logs.h index 3ea8ccee..821639f4 100644 --- a/Logs.h +++ b/Logs.h @@ -22,6 +22,7 @@ enum class logtype : unsigned int { shader = ( 1 << 6 ), net = ( 1 << 7 ), sound = ( 1 << 8 ), + powergrid = ( 1 << 9 ), }; void WriteLog( const char *str, logtype const Type = logtype::generic ); diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index f46ea938..cf89d19f 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -120,7 +120,7 @@ static int const dtrain_engine = 32; /*dla lokomotyw*/ static int const dtrain_loaddestroyed = 32;/*dla wagonow*/ static int const dtrain_axle = 64; static int const dtrain_out = 128; /*wykolejenie*/ - +static int const dtrain_pantograph = 256; /*wagi prawdopodobienstwa dla funkcji FuzzyLogic*/ #define p_elengproblem (1e-02) #define p_elengdamage (1e-01) @@ -837,7 +837,7 @@ private: struct water_pump : public basic_device { // ld inputs // TODO: move to breaker list in the basic device once implemented - bool breaker { true }; // device is allowed to operate + bool breaker { false }; // device is allowed to operate }; // basic approximation of a solenoid valve @@ -921,7 +921,7 @@ private: float temp_max { -1 }; // highest accepted temperature } config; // ld inputs - bool breaker { true }; // device is allowed to operate + bool breaker { false }; // device is allowed to operate bool is_enabled { false }; // device is requested to operate // ld outputs bool is_active { false }; // device is working @@ -1215,6 +1215,7 @@ public: double dizel_minVelfullengage = 0.0; /*najmniejsza predkosc przy jezdzie ze sprzeglem bez poslizgu*/ double dizel_maxVelANS = 3.0; /*predkosc progowa rozlaczenia przetwornika momentu*/ double dizel_AIM = 1.0; /*moment bezwladnosci walu itp*/ + double dizel_RevolutionsDecreaseRate{ 2.0 }; double dizel_NominalFuelConsumptionRate = 250.0; /*jednostkowe zużycie paliwa przy mocy nominalnej, wczytywane z fiz, g/kWh*/ double dizel_FuelConsumption = 0.0; /*współczynnik zużycia paliwa przeliczony do jednostek maszynowych, l/obrót*/ double dizel_FuelConsumptionActual = 0.0; /*chwilowe spalanie paliwa w l/h*/ @@ -1273,7 +1274,7 @@ public: double MED_Vmax = 0; // predkosc maksymalna dla obliczen chwilowej sily hamowania EP w MED double MED_Vmin = 0; // predkosc minimalna dla obliczen chwilowej sily hamowania EP w MED double MED_Vref = 0; // predkosc referencyjna dla obliczen dostepnej sily hamowania EP w MED - double MED_amax = 9.81; // maksymalne opoznienie hamowania sluzbowego MED + double MED_amax { 9.81 }; // maksymalne opoznienie hamowania sluzbowego MED bool MED_EPVC = 0; // czy korekcja sily hamowania EP, gdy nie ma dostepnego ED double MED_EPVC_Time = 7; // czas korekcji sily hamowania EP, gdy nie ma dostepnego ED bool MED_Ncor = 0; // czy korekcja sily hamowania z uwzglednieniem nacisku @@ -1673,12 +1674,15 @@ public: bool DecMainCtrl(int CtrlSpeed); bool IsMainCtrlActualNoPowerPos() const; // whether the master controller is actually set to position which won't generate any extra power bool IsMainCtrlNoPowerPos() const; // whether the master controller is set to position which won't generate any extra power + bool IsMainCtrlMaxPowerPos() const; int MainCtrlNoPowerPos() const; // highest setting of master controller which won't cause engine to generate extra power int MainCtrlActualPowerPos() const; // current actual setting of master controller, relative to the highest setting not generating extra power int MainCtrlPowerPos() const; // current setting of master controller, relative to the highest setting not generating extra power /*! pomocniczy nastawnik:*/ bool IncScndCtrl(int CtrlSpeed); bool DecScndCtrl(int CtrlSpeed); + bool IsScndCtrlNoPowerPos() const; + bool IsScndCtrlMaxPowerPos() const; bool AddPulseForce(int Multipler);/*dla drezyny*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 11961067..2cec66eb 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -2419,6 +2419,11 @@ bool TMoverParameters::IsMainCtrlNoPowerPos() const { return MainCtrlPos <= MainCtrlNoPowerPos(); } +bool TMoverParameters::IsMainCtrlMaxPowerPos() const { + // TODO: wrap controller pieces into a class for potential specializations, similar to brake subsystems + return MainCtrlPos == MainCtrlPosNo; +} + int TMoverParameters::MainCtrlNoPowerPos() const { switch( EIMCtrlType ) { @@ -2578,6 +2583,16 @@ bool TMoverParameters::DecScndCtrl(int CtrlSpeed) return OK; } +bool TMoverParameters::IsScndCtrlNoPowerPos() const { + // TODO: refine the check on account of potential electric series vehicles with speed control + return ( ( ScndCtrlPos == 0 ) || ( true == SpeedCtrl ) ); +} + +bool TMoverParameters::IsScndCtrlMaxPowerPos() const { + // TODO: refine the check on account of potential electric series vehicles with speed control + return ( ( ScndCtrlPos == ScndCtrlPosNo ) || ( true == SpeedCtrl ) ); +} + // ************************************************************************************************* // Q: 20160710 // załączenie rozrządu @@ -2833,12 +2848,12 @@ void TMoverParameters::SecuritySystemCheck(double dt) if( ( TestFlag( SecuritySystem.SystemType, 2 ) && ( SecuritySystem.Status == s_waiting ) && ( false == SecuritySystem.PoweredUp ) - && ( Power24vIsAvailable ) ) ) { + && ( Power24vIsAvailable || Power110vIsAvailable ) ) ) { // Ra: znowu w kabinie jest coś, co być nie powinno! SetFlag( SecuritySystem.Status, s_active ); SetFlag( SecuritySystem.Status, s_SHPalarm ); } - SecuritySystem.PoweredUp = Power24vIsAvailable; + SecuritySystem.PoweredUp = ( Power24vIsAvailable || Power110vIsAvailable ); if ((SecuritySystem.SystemType > 0) && (SecuritySystem.Status != s_off) @@ -5175,6 +5190,7 @@ double TMoverParameters::CouplerForce( int const End, double dt ) { } if( Attach( End, otherend, othervehicle, couplingtype ) ) { + // HACK: we're reusing sound enum to mark whether vehicle was connected to another SetFlag( AIFlag, sound::attachcoupler ); coupler.AutomaticCouplingAllowed = false; othercoupler.AutomaticCouplingAllowed = false; @@ -5241,8 +5257,8 @@ double TMoverParameters::TractionForce( double dt ) { enrot = clamp( enrot + ( dt / dizel_AIM ) * ( enrot < tmp ? - 1.0 : - -2.0 ), // NOTE: revolutions drop faster than they rise, maybe? TBD: maybe not + 1.0 : + -1.0 * dizel_RevolutionsDecreaseRate ), // NOTE: revolutions typically drop faster than they rise 0.0, std::max( tmp, enrot ) ); if( std::abs( tmp - enrot ) < 0.001 ) { enrot = tmp; @@ -6851,8 +6867,6 @@ bool TMoverParameters::OperatePantographsValve( operation_t const State, range_t if( ( EnginePowerSource.SourceType == TPowerSource::CurrentCollector ) && ( EnginePowerSource.CollectorParameters.CollectorsNo > 0 ) ) { - auto const lowvoltagepower { PantsValve.solenoid ? ( Power24vIsAvailable || Power110vIsAvailable ) : true }; - auto &valve { PantsValve }; switch( State ) { @@ -8156,7 +8170,7 @@ bool TMoverParameters::ChangeDoorControlMode( bool const State, range_t const No OperateDoors( side::right, true ); } - return ( Doors.step_enabled != initialstate ); + return ( Doors.remote_only != initialstate ); } bool TMoverParameters::OperateDoors( side const Door, bool const State, range_t const Notify ) { @@ -8298,8 +8312,8 @@ TMoverParameters::update_doors( double const Deltatime ) { door.local_open = door.local_open && ( false == door.is_open ) && ( ( false == Doors.permit_needed ) || door.open_permit ); door.remote_open = ( door.remote_open || Doors.remote_only ) && ( false == door.is_open ) && ( ( false == Doors.permit_needed ) || door.open_permit ); - door.local_close = door.local_close && ( false == door.is_closed ); - door.remote_close = door.remote_close && ( false == door.is_closed ); + door.local_close = door.local_close && ( false == door.is_closed ) && ( ( false == remoteopencontrol ) || ( false == door.remote_open ) ); + door.remote_close = door.remote_close && ( false == door.is_closed ) && ( ( false == localopencontrol ) || ( false == door.local_open ) ); auto const autoopenrequest { ( Doors.open_control == control_t::autonomous ) @@ -10497,6 +10511,7 @@ void TMoverParameters::LoadFIZ_Engine( std::string const &Input ) { extract_value( dizel_nmax_cutoff, "nmax_cutoff", Input, "0.0" ); dizel_nmax_cutoff /= 60.0; extract_value( dizel_AIM, "AIM", Input, "1.0" ); + extract_value( dizel_RevolutionsDecreaseRate, "RPMDecRate", Input, "" ); extract_value(engageupspeed, "EUS", Input, "0.5"); extract_value(engagedownspeed, "EDS", Input, "0.9"); @@ -10561,6 +10576,7 @@ void TMoverParameters::LoadFIZ_Engine( std::string const &Input ) { } extract_value( EngineHeatingRPM, "HeatingRPM", Input, "" ); extract_value( dizel_AIM, "AIM", Input, "1.25" ); + extract_value( dizel_RevolutionsDecreaseRate, "RPMDecRate", Input, "" ); break; } case TEngineType::ElectricInductionMotor: { diff --git a/MdlMngr.cpp b/MdlMngr.cpp index 52b47acb..6f025dbf 100644 --- a/MdlMngr.cpp +++ b/MdlMngr.cpp @@ -108,7 +108,7 @@ TModelsManager::GetModel(std::string const &Name, bool const Dynamic, bool const else { // there's nothing matching in the databank nor on the disk, report failure... if( Logerrors ) { - ErrorLog( "Bad file: failed do locate 3d model file \"" + filename + "\"", logtype::file ); + ErrorLog( "Bad file: failed to locate 3d model file \"" + filename + "\"", logtype::file ); } // ...and link it with the error model slot m_modelsmap.emplace( filename, null_handle ); diff --git a/Model3d.cpp b/Model3d.cpp index a6bd8953..7a7b3837 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -1847,7 +1847,7 @@ void TModel3d::deserialize(std::istream &s, size_t size, bool dynamic) m_vertexcount += submodelgeometry.vertex_count; switch( vertextype ) { case 0: { - // legacy vnt0 format + // legacy vnt0 format for( auto &vertex : submodel.Vertices ) { vertex.deserialize( s, hastangents ); if( submodel.eType < TP_ROTATOR ) { @@ -1876,6 +1876,7 @@ void TModel3d::deserialize(std::istream &s, size_t size, bool dynamic) break; } default: { + // TBD, TODO: throw error here? break; } } diff --git a/Texture.cpp b/Texture.cpp index 410ec98d..8500e2e4 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -1203,7 +1203,7 @@ texture_manager::create( std::string Filename, bool const Loadnow, GLint Formath locator = find_on_disk( Filename ); if( true == locator.first.empty() ) { // there's nothing matching in the databank nor on the disk, report failure - ErrorLog( "Bad file: failed do locate texture file \"" + Filename + "\"", logtype::file ); + ErrorLog( "Bad file: failed to locate texture file \"" + Filename + "\"", logtype::file ); return npos; } } diff --git a/Train.cpp b/Train.cpp index 51c5387d..413b0478 100644 --- a/Train.cpp +++ b/Train.cpp @@ -463,7 +463,7 @@ TTrain::TTrain() { for ( int i = 0; i < 20; ++i ) { - for ( int j = 0; j < 3; ++j ) + for ( int j = 0; j < 4; ++j ) fPress[i][j] = 0.0; bBrakes[i][0] = bBrakes[i][1] = false; } @@ -624,7 +624,7 @@ dictionary_source *TTrain::GetTrainState() { char const *TXTT[ 10 ] = { "fd", "fdt", "fdb", "pd", "pdt", "pdb", "itothv", "1", "2", "3" }; char const *TXTC[ 10 ] = { "fr", "frt", "frb", "pr", "prt", "prb", "im", "vm", "ihv", "uhv" }; char const *TXTD[ 10 ] = { "enrot", "nrot", "fill_des", "fill_real", "clutch_des", "clutch_real", "water_temp", "oil_press", "engine_temp", "retarder_fill" }; - char const *TXTP[ 3 ] = { "bc", "bp", "sp" }; + char const *TXTP[ 4 ] = { "bc", "bp", "sp", "cp" }; char const *TXTB[ 2 ] = { "spring_active", "spring_shutoff" }; for( int j = 0; j < 10; ++j ) dict->insert( ( "eimp_t_" + std::string( TXTT[ j ] ) ), fEIMParams[ 0 ][ j ] ); @@ -648,7 +648,7 @@ dictionary_source *TTrain::GetTrainState() { } for( int i = 0; i < 20; ++i ) { - for( int j = 0; j < 3; ++j ) { + for( int j = 0; j < 4; ++j ) { dict->insert( ( "eimp_pn" + std::to_string( i + 1 ) + "_" + TXTP[ j ] ), fPress[ i ][ j ] ); } for ( int j = 0; j < 2; ++j) { @@ -6169,6 +6169,7 @@ bool TTrain::Update( double const Deltatime ) fPress[i][0] = p->MoverParameters->BrakePress; fPress[i][1] = p->MoverParameters->PipePress; fPress[i][2] = p->MoverParameters->ScndPipePress; + fPress[i][3] = p->MoverParameters->CntrlPipePress; bBrakes[i][0] = p->MoverParameters->SpringBrake.IsActive; bBrakes[i][1] = p->MoverParameters->SpringBrake.ShuttOff; bDoors[i][1] = ( p->MoverParameters->Doors.instances[ side::left ].position > 0.f ); @@ -6247,6 +6248,7 @@ bool TTrain::Update( double const Deltatime ) fPress[i][0] = fPress[i][1] = fPress[i][2] + = fPress[i][3] = 0; bDoors[i][0] = bDoors[i][1] @@ -7188,7 +7190,7 @@ bool TTrain::Update( double const Deltatime ) fScreenTimer += Deltatime; if( ( this == simulation::Train ) // no point in drawing screens for vehicles other than our own && ( Global.PythonScreenUpdateRate > 0 ) - && ( fScreenTimer > Global.PythonScreenUpdateRate * 0.001f ) + && ( fScreenTimer > std::max( fScreenUpdateRate, Global.PythonScreenUpdateRate ) * 0.001f ) && ( false == FreeFlyModeFlag ) ) { // don't bother if we're outside fScreenTimer = 0.f; for( auto const &screen : m_screens ) { @@ -7344,6 +7346,29 @@ TTrain::update_sounds( double const Deltatime ) { } } // koniec nie FV4a + // brakes + if( ( mvOccupied->UnitBrakeForce > 10.0 ) + && ( mvOccupied->Vel > 0.05 ) ) { + + auto const brakeforceratio { + clamp( + mvOccupied->UnitBrakeForce / std::max( 1.0, mvOccupied->BrakeForceR( 1.0, mvOccupied->Vel ) / ( mvOccupied->NAxles * std::max( 1, mvOccupied->NBpA ) ) ), + 0.0, 1.0 ) }; + // HACK: in external view mute the sound rather than stop it, in case there's an opening bookend it'd (re)play on sound restart after returning inside + volume = ( + FreeFlyModeFlag ? + 0.0 : + rsBrake.m_amplitudeoffset + + std::sqrt( brakeforceratio * interpolate( 0.4, 1.0, ( mvOccupied->Vel / ( 1 + mvOccupied->Vmax ) ) ) ) * rsBrake.m_amplitudefactor ); + rsBrake + .pitch( rsBrake.m_frequencyoffset + mvOccupied->Vel * rsBrake.m_frequencyfactor ) + .gain( volume ) + .play( sound_flags::exclusive | sound_flags::looping ); + } + else { + rsBrake.stop(); + } + // ambient sound // since it's typically ticking of the clock we can center it on tachometer or on middle of compartment bounding area rsFadeSound.play( sound_flags::exclusive | sound_flags::looping ); @@ -7612,26 +7637,22 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) parser.getTokens(); parser >> token; // SEKCJA DZWIEKOW - if (token == "ctrl:") - { + if (token == "ctrl:") { // nastawnik: dsbNastawnikJazdy.deserialize( parser, sound_type::single ); dsbNastawnikJazdy.owner( DynamicObject ); } - else if (token == "ctrlscnd:") - { + else if (token == "ctrlscnd:") { // hunter-081211: nastawnik bocznikowania dsbNastawnikBocz.deserialize( parser, sound_type::single ); dsbNastawnikBocz.owner( DynamicObject ); } - else if (token == "reverserkey:") - { + else if (token == "reverserkey:") { // hunter-131211: dzwiek kierunkowego dsbReverserKey.deserialize( parser, sound_type::single ); dsbReverserKey.owner( DynamicObject ); } - else if (token == "buzzer:") - { + else if (token == "buzzer:") { // bzyczek shp: dsbBuzzer.deserialize( parser, sound_type::single ); dsbBuzzer.owner( DynamicObject ); @@ -7641,38 +7662,32 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) m_radiostop.deserialize( parser, sound_type::single ); m_radiostop.owner( DynamicObject ); } - else if (token == "slipalarm:") - { + else if (token == "slipalarm:") { // Bombardier 011010: alarm przy poslizgu: dsbSlipAlarm.deserialize( parser, sound_type::single ); dsbSlipAlarm.owner( DynamicObject ); } - else if (token == "distancecounter:") - { + else if (token == "distancecounter:") { // distance meter 'good to go' sound m_distancecounterclear.deserialize( parser, sound_type::single ); m_distancecounterclear.owner( DynamicObject ); } - else if (token == "tachoclock:") - { + else if (token == "tachoclock:") { // cykanie rejestratora: dsbHasler.deserialize( parser, sound_type::single ); dsbHasler.owner( DynamicObject ); } - else if (token == "switch:") - { + else if (token == "switch:") { // przelaczniki: dsbSwitch.deserialize( parser, sound_type::single ); dsbSwitch.owner( DynamicObject ); } - else if (token == "pneumaticswitch:") - { + else if (token == "pneumaticswitch:") { // stycznik EP: dsbPneumaticSwitch.deserialize( parser, sound_type::single ); dsbPneumaticSwitch.owner( DynamicObject ); } - else if (token == "airsound:") - { + else if (token == "airsound:") { // syk: rsHiss.deserialize( parser, sound_type::single, sound_parameters::amplitude ); rsHiss.owner( DynamicObject ); @@ -7681,8 +7696,7 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) rsSBHiss = rsHiss; } } - else if (token == "airsound2:") - { + else if (token == "airsound2:") { // syk: rsHissU.deserialize( parser, sound_type::single, sound_parameters::amplitude ); rsHissU.owner( DynamicObject ); @@ -7691,26 +7705,22 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) rsSBHissU = rsHissU; } } - else if (token == "airsound3:") - { + else if (token == "airsound3:") { // syk: rsHissE.deserialize( parser, sound_type::single, sound_parameters::amplitude ); rsHissE.owner( DynamicObject ); } - else if (token == "airsound4:") - { + else if (token == "airsound4:") { // syk: rsHissX.deserialize( parser, sound_type::single, sound_parameters::amplitude ); rsHissX.owner( DynamicObject ); } - else if (token == "airsound5:") - { + else if (token == "airsound5:") { // syk: rsHissT.deserialize( parser, sound_type::single, sound_parameters::amplitude ); rsHissT.owner( DynamicObject ); } - else if (token == "localbrakesound:") - { + else if (token == "localbrakesound:") { // syk: rsSBHiss.deserialize( parser, sound_type::single, sound_parameters::amplitude ); rsSBHiss.owner( DynamicObject ); @@ -7720,8 +7730,14 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) rsSBHissU.deserialize( parser, sound_type::single, sound_parameters::amplitude ); rsSBHissU.owner( DynamicObject ); } - else if (token == "fadesound:") - { + else if( token == "brakesound:" ) { + // the sound of vehicle body vibrations etc, when brakes are engaged + rsBrake.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency ); + rsBrake.owner( DynamicObject ); + // NOTE: can't pre-calculate amplitude normalization based on max brake force, as this varies depending on vehicle speed + rsBrake.m_frequencyfactor /= ( 1 + mvOccupied->Vmax ); + } + else if (token == "fadesound:") { // ambient sound: rsFadeSound.deserialize( parser, sound_type::single ); rsFadeSound.owner( DynamicObject ); @@ -7730,16 +7746,12 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) // szum podczas jazdy: rsRunningNoise.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, mvOccupied->Vmax ); rsRunningNoise.owner( DynamicObject ); - -// rsRunningNoise.m_amplitudefactor /= ( 1 + mvOccupied->Vmax ); rsRunningNoise.m_frequencyfactor /= ( 1 + mvOccupied->Vmax ); } else if( token == "huntingnoise:" ) { // hunting oscillation sound: rsHuntingNoise.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, mvOccupied->Vmax ); rsHuntingNoise.owner( DynamicObject ); - -// rsHuntingNoise.m_amplitudefactor /= ( 1 + mvOccupied->Vmax ); rsHuntingNoise.m_frequencyfactor /= ( 1 + mvOccupied->Vmax ); } else if( token == "rainsound:" ) { @@ -8040,6 +8052,10 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) m_screens.back().viewer = std::make_unique(rt, touch_list, rendererpath); */ } + else if (token == "pyscreenupdatetime:") { + parser.getTokens(); + parser >> fScreenUpdateRate; + } // btLampkaUnknown.Init("unknown",mdKabina,false); } while ( ( token != "" ) // TODO: enable full per-cab deserialization when/if .mmd files get proper per-cab switch configuration @@ -9356,13 +9372,13 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con } else if (Label == "brakes:") { - // amperomierz calkowitego pradu + // specified pipe pressure of specified consist vehicle int i, j; Parser.getTokens(2, false); Parser >> i >> j; auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka gauge.Load(Parser, DynamicObject, 0.1); - gauge.AssignFloat(&fPress[i - 1][j]); + gauge.AssignFloat(&fPress[clamp(i, 1, 20) - 1][clamp(j, 0, 3)]); } else if ((Label == "brakepress:") || (Label == "brakepressb:")) { diff --git a/Train.h b/Train.h index dbd3fabe..e285a10c 100644 --- a/Train.h +++ b/Train.h @@ -722,6 +722,7 @@ public: // reszta może by?publiczna sound_source rsSBHissU { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // local, engage brakes float m_lastlocalbrakepressure { -1.f }; // helper, cached level of pressure in local brake cylinder float m_localbrakepressurechange { 0.f }; // recent change of pressure in local brake cylinder + sound_source rsBrake { sound_placement::internal, -1 }; sound_source rsFadeSound { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; sound_source rsRunningNoise{ sound_placement::internal, EU07_SOUND_GLOBALRANGE }; @@ -757,6 +758,7 @@ private: float fMainRelayTimer; // hunter-141211: zalaczanie WSa z opoznieniem float fCzuwakTestTimer; // hunter-091012: do testu czuwaka float fScreenTimer { 0.f }; + int fScreenUpdateRate { 0 }; // vehicle specific python screen update rate override bool CAflag { false }; // hunter-131211: dla osobnego zbijania CA i SHP @@ -800,7 +802,7 @@ private: bool m_couplingdisconnect { false }; public: - float fPress[20][3]; // cisnienia dla wszystkich czlonow + float fPress[20][4]; // cisnienia dla wszystkich czlonow bool bBrakes[20][2]; // zalaczenie i dzialanie hamulcow static std::vector const fPress_labels; float fEIMParams[9][10]; // parametry dla silnikow asynchronicznych diff --git a/application.cpp b/application.cpp index 46ab85d9..e734a3a5 100644 --- a/application.cpp +++ b/application.cpp @@ -739,15 +739,17 @@ eu07_application::init_glfw() { if (!monitor) monitor = glfwGetPrimaryMonitor(); + auto const uselegacyrenderer { Global.GfxRenderer != "default" }; glfwWindowHint( GLFW_AUTO_ICONIFY, GLFW_FALSE ); glfwWindowHint( GLFW_FLOATING, GLFW_FALSE ); - if( ( Global.iMultisampling > 0 ) && ( Global.gfx_skippipeline ) ) { + if( ( Global.iMultisampling > 0 ) + && ( Global.gfx_skippipeline || uselegacyrenderer ) ) { glfwWindowHint( GLFW_SAMPLES, 1 << Global.iMultisampling ); } glfwWindowHint(GLFW_SRGB_CAPABLE, !Global.gfx_shadergamma); - if( Global.GfxRenderer == "default" ) { + if( false == uselegacyrenderer ) { // activate core profile for opengl 3.3 renderer if( !Global.gfx_usegles ) { glfwWindowHint( GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE ); diff --git a/color.h b/color.h index 19ed43c2..bc3555d4 100644 --- a/color.h +++ b/color.h @@ -6,6 +6,10 @@ glm::vec4 const none{ 0.f, 0.f, 0.f, 1.f }; glm::vec4 const white{ 1.f, 1.f, 1.f, 1.f }; glm::vec4 const shadow{ 0.25f, 0.30f, 0.35f, 1.f }; +glm::vec4 const uitextred{ 164.0f / 255.0f, 84.0f / 255.0f, 84.0f / 255.0f, 1.f }; +glm::vec4 const uitextorange{ 164.0f / 255.0f, 132.0f / 255.0f, 84.0f / 255.0f, 1.f }; +glm::vec4 const uitextgreen{ 84.0f / 255.0f, 164.0f / 255.0f, 132.0f / 255.0f, 1.f }; + inline glm::vec3 XYZtoRGB( glm::vec3 const &XYZ ) { diff --git a/driverhints.cpp b/driverhints.cpp new file mode 100644 index 00000000..d7d121aa --- /dev/null +++ b/driverhints.cpp @@ -0,0 +1,1310 @@ +/* +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/. +*/ + +#include "stdafx.h" +#include "Driver.h" + +void +TController::hint( locale::string const Value, hintpredicate const Predicate, float const Predicateparameter ) { + + if( Predicate( Predicateparameter ) ) { return; } + // check already recorded hints, if there's a match update it instead of adding a new one + for( auto &hint : m_hints ) { + if( std::get( hint ) == Value ) { + std::get( hint ) = Predicateparameter; + return; // done, can bail out early + } + } + m_hints.emplace_back( Value, Predicate, Predicateparameter ); +} + +void +TController::update_hints() { + + m_hints.remove_if( + []( auto const &Hint ) { + return ( std::get( Hint )( std::get( Hint ) ) ); } ); +} + +void +TController::remove_hint( locale::string const Value ) { + + m_hints.remove_if( + [=]( auto const &Hint ) { + return ( std::get( Hint ) == Value ); } ); +} + +void +TController::remove_train_brake_hints() { + + remove_hint( locale::string::driver_hint_trainbrakesetpipeunlock ); + remove_hint( locale::string::driver_hint_trainbrakerelease ); + remove_hint( locale::string::driver_hint_trainbrakeapply ); + remove_hint( locale::string::driver_hint_brakingforcedecrease ); + remove_hint( locale::string::driver_hint_brakingforceincrease ); + remove_hint( locale::string::driver_hint_brakingforcesetzero ); + remove_hint( locale::string::driver_hint_brakingforcelap ); +} + +void +TController::remove_master_controller_hints() { + + remove_hint( locale::string::driver_hint_mastercontrollersetidle ); + remove_hint( locale::string::driver_hint_mastercontrollersetseriesmode ); + remove_hint( locale::string::driver_hint_mastercontrollersetzerospeed ); + remove_hint( locale::string::driver_hint_mastercontrollersetreverserunlock ); + remove_hint( locale::string::driver_hint_tractiveforcedecrease ); + remove_hint( locale::string::driver_hint_tractiveforceincrease ); + remove_hint( locale::string::driver_hint_bufferscompress ); +} + +void +TController::remove_reverser_hints() { + + remove_hint( locale::string::driver_hint_directionforward ); + remove_hint( locale::string::driver_hint_directionbackward ); + remove_hint( locale::string::driver_hint_directionother ); + remove_hint( locale::string::driver_hint_directionnone ); +} + +void +TController::cue_action( locale::string const Action, float const Actionparameter ) { + + switch( Action ) { + // battery + case locale::string::driver_hint_batteryon: { + if( AIControllFlag ) { + mvOccupied->BatterySwitch( true ); + } + remove_hint( locale::string::driver_hint_batteryoff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvOccupied->BatteryStart != start_t::manual ) || ( mvOccupied->Power24vIsAvailable == true ) ); } ); + break; + } + case locale::string::driver_hint_batteryoff: { + if( AIControllFlag ) { + mvOccupied->BatterySwitch( false ); + } + remove_hint( locale::string::driver_hint_batteryon ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvOccupied->BatteryStart != start_t::manual ) || ( mvOccupied->Power24vIsAvailable == false ) ); } ); + break; + } + // radio + case locale::string::driver_hint_radioon: { + if( AIControllFlag ) { + mvOccupied->Radio = true; + } + remove_hint( locale::string::driver_hint_radiooff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvOccupied->Radio == true ); } ); + break; + } + case locale::string::driver_hint_radiochannel: { + if( AIControllFlag ) { + iRadioChannel = static_cast( Actionparameter ); + } + hint( + Action, + [this](float const Parameter) -> bool { + return ( iRadioChannel == static_cast( Parameter ) ); }, + Actionparameter ); + break; + } + case locale::string::driver_hint_radiooff: { + if( AIControllFlag ) { + mvOccupied->Radio = false; + } + remove_hint( locale::string::driver_hint_radioon ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvOccupied->Radio == false ); } ); + break; + } + // oil pump + case locale::string::driver_hint_oilpumpon: { + if( AIControllFlag ) { + mvOccupied->OilPumpSwitch( true ); + } + remove_hint( locale::string::driver_hint_oilpumpoff ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvOccupied->OilPump }; + return ( ( device.start_type != start_t::manual ) || ( device.is_enabled == true ) || ( device.is_active == true ) || ( mvOccupied->Mains ) ); } ); + break; + } + case locale::string::driver_hint_oilpumpoff: { + if( AIControllFlag ) { + mvOccupied->OilPumpSwitch( false ); + } + remove_hint( locale::string::driver_hint_oilpumpon ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvOccupied->OilPump }; + return ( ( device.start_type != start_t::manual ) || ( ( device.is_enabled == false ) && ( device.is_active == false ) ) ); } ); + break; + } + // fuel pump + case locale::string::driver_hint_fuelpumpon: { + if( AIControllFlag ) { + mvOccupied->FuelPumpSwitch( true ); + } + remove_hint( locale::string::driver_hint_fuelpumpoff ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvOccupied->FuelPump }; + return ( ( device.start_type != start_t::manual ) || ( device.is_enabled == true ) || ( device.is_active == true ) ); } ); + break; + } + case locale::string::driver_hint_fuelpumpoff: { + if( AIControllFlag ) { + mvOccupied->FuelPumpSwitch( false ); + } + remove_hint( locale::string::driver_hint_fuelpumpon ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvOccupied->FuelPump }; + return ( ( device.start_type != start_t::manual ) || ( ( device.is_enabled == false ) && ( device.is_active == false ) ) ); } ); + break; + } + // pantographs + case locale::string::driver_hint_pantographairsourcesetmain: { + if( AIControllFlag ) { + mvPantographUnit->bPantKurek3 = true; + } + remove_hint( locale::string::driver_hint_pantographairsourcesetauxiliary ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvPantographUnit->bPantKurek3 == true ); } ); + break; + } + case locale::string::driver_hint_pantographairsourcesetauxiliary: { + if( AIControllFlag ) { + mvPantographUnit->bPantKurek3 = false; + } + remove_hint( locale::string::driver_hint_pantographcompressoron ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvPantographUnit->bPantKurek3 == false ); } ); + break; + } + case locale::string::driver_hint_pantographcompressoron: { + if( AIControllFlag ) { + mvPantographUnit->PantCompFlag = true; + } + remove_hint( locale::string::driver_hint_pantographcompressoroff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvPantographUnit->PantCompFlag == true ); } ); + break; + } + case locale::string::driver_hint_pantographcompressoroff: { + if( AIControllFlag ) { + mvPantographUnit->PantCompFlag = false; + } + remove_hint( locale::string::driver_hint_pantographcompressoron ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvPantographUnit->PantCompFlag == false ); } ); + break; + } + case locale::string::driver_hint_pantographsvalveon: { + if( AIControllFlag ) { + mvOccupied->OperatePantographsValve( operation_t::enable ); + } + remove_hint( locale::string::driver_hint_pantographsvalveoff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvPantographUnit->PantsValve.is_active == true ); } ); + break; + } + case locale::string::driver_hint_frontpantographvalveon: { + if( AIControllFlag ) { + mvOccupied->OperatePantographValve( end::front, operation_t::enable ); + } + remove_hint( locale::string::driver_hint_frontpantographvalveoff ); + hint( + Action, + [ this ]( float const Parameter ) -> bool { + return ( ( mvPantographUnit->Pantographs[ end::front ].valve.is_active == true ) || ( ( Parameter > 0 ) && ( mvOccupied->Vel > Parameter ) ) ); }, + Actionparameter ); + break; + } + case locale::string::driver_hint_frontpantographvalveoff: { + if( AIControllFlag ) { + mvOccupied->OperatePantographValve( end::front, operation_t::disable ); + } + remove_hint( locale::string::driver_hint_frontpantographvalveon ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvPantographUnit->Pantographs[ end::front ].valve.is_active == false ); } ); + break; + } + case locale::string::driver_hint_rearpantographvalveon: { + if( AIControllFlag ) { + mvOccupied->OperatePantographValve( end::rear, operation_t::enable ); + } + remove_hint( locale::string::driver_hint_rearpantographvalveoff ); + hint( + Action, + [ this ]( float const Parameter ) -> bool { + return ( ( mvPantographUnit->Pantographs[ end::rear ].valve.is_active == true ) || ( ( Parameter > 0 ) && ( mvOccupied->Vel > Parameter ) ) ); }, + Actionparameter ); + break; + } + case locale::string::driver_hint_rearpantographvalveoff: { + if( AIControllFlag ) { + mvOccupied->OperatePantographValve( end::rear, operation_t::disable ); + } + remove_hint( locale::string::driver_hint_rearpantographvalveon ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvPantographUnit->Pantographs[ end::rear ].valve.is_active == false ); } ); + break; + } + // converter + case locale::string::driver_hint_converteron: { + if( AIControllFlag ) { + mvOccupied->ConverterSwitch( true ); + } + remove_hint( locale::string::driver_hint_converteroff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyConverterEnabled == true ); } ); + break; + } + case locale::string::driver_hint_converteroff: { + if( AIControllFlag ) { + mvOccupied->ConverterSwitch( false ); + } + remove_hint( locale::string::driver_hint_converteron ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyConverterExplicitlyEnabled == false ); } ); + break; + } + // relays + case locale::string::driver_hint_primaryconverteroverloadreset: { + if( AIControllFlag ) { + mvOccupied->RelayReset( relay_t::primaryconverteroverload ); + } + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvOccupied->ConverterOverloadRelayStart != start_t::manual ) || ( mvOccupied->ConvOvldFlag == false ) ); } ); + break; + } + case locale::string::driver_hint_maincircuitgroundreset: { + if( AIControllFlag ) { + mvOccupied->RelayReset( relay_t::maincircuitground ); + } + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvOccupied->GroundRelayStart != start_t::manual ) || ( mvOccupied->GroundRelay == true ) ); } ); + break; + } + case locale::string::driver_hint_tractionnmotoroverloadreset: { + if( AIControllFlag ) { + mvOccupied->RelayReset( relay_t::tractionnmotoroverload ); + } + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvOccupied->FuseFlag == false ); } ); + break; + } + // line breaker + case locale::string::driver_hint_linebreakerclose: { + if( AIControllFlag ) { + mvOccupied->MainSwitch( true ); + } + remove_hint( locale::string::driver_hint_linebreakeropen ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyLineBreakerOpen == false ); } ); + break; + } + case locale::string::driver_hint_linebreakeropen: { + if( AIControllFlag ) { + mvOccupied->MainSwitch( false ); + } + remove_hint( locale::string::driver_hint_linebreakerclose ); + hint( + Action, + [this](float const Parameter) -> bool { + // TBD, TODO: replace with consist-wide flag set true if any line breaker is closed? + return ( mvControlling->Mains == false ); } ); + break; + } + // compressor + case locale::string::driver_hint_compressoron: { + if( AIControllFlag ) { + mvOccupied->CompressorSwitch( true ); + } + remove_hint( locale::string::driver_hint_compressoroff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyCompressorEnabled == true ); } ); + break; + } + case locale::string::driver_hint_compressoroff: { + if( AIControllFlag ) { + mvOccupied->CompressorSwitch( false ); + } + remove_hint( locale::string::driver_hint_compressoron ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyCompressorExplicitlyEnabled == false ); } ); + break; + } + case locale::string::driver_hint_frontmotorblowerson: { + if( AIControllFlag ) { + mvOccupied->MotorBlowersSwitchOff( false, end::front ); + mvOccupied->MotorBlowersSwitch( true, end::front ); + } + remove_hint( locale::string::driver_hint_frontmotorblowersoff ); + hint( + Action, + [this]( float const Parameter ) -> bool { + auto const &device { mvOccupied->MotorBlowers[ end::front ] }; + return ( ( device.start_type != start_t::manual ) || ( ( device.is_enabled == true ) && ( device.is_disabled == false ) ) ); } ); + break; + } + case locale::string::driver_hint_rearmotorblowerson: { + if( AIControllFlag ) { + mvOccupied->MotorBlowersSwitchOff( false, end::rear ); + mvOccupied->MotorBlowersSwitch( true, end::rear ); + } + remove_hint( locale::string::driver_hint_rearmotorblowersoff ); + hint( + Action, + [this]( float const Parameter ) -> bool { + auto const &device { mvOccupied->MotorBlowers[ end::rear ] }; + return ( ( device.start_type != start_t::manual ) || ( ( device.is_enabled == true ) && ( device.is_disabled == false ) ) ); } ); + break; + } + // spring brake + case locale::string::driver_hint_springbrakeon: { + if( AIControllFlag ) { + mvOccupied->SpringBrakeActivate( true ); + } + remove_hint( locale::string::driver_hint_springbrakeoff ); + hint( + Action, + [this]( float const Parameter ) -> bool { + return ( mvOccupied->SpringBrake.Activate == true ); } ); + break; + } + case locale::string::driver_hint_springbrakeoff: { + if( AIControllFlag ) { + mvOccupied->SpringBrakeActivate( false ); + } + remove_hint( locale::string::driver_hint_springbrakeon ); + hint( + Action, + [this]( float const Parameter ) -> bool { + return ( mvOccupied->SpringBrake.Activate == false ); } ); + break; + } + // manual brake + case locale::string::driver_hint_manualbrakon: { + if( AIControllFlag ) { + mvOccupied->IncManualBrakeLevel( ManualBrakePosNo ); + } + remove_hint( locale::string::driver_hint_manualbrakoff ); + hint( + Action, + [this]( float const Parameter ) -> bool { + return ( ( mvOccupied->LocalBrake != TLocalBrake::ManualBrake ) || ( mvOccupied->MBrake == false ) || ( mvOccupied->ManualBrakePos == ManualBrakePosNo ) ); } ); + break; + } + case locale::string::driver_hint_manualbrakoff: { + if( AIControllFlag ) { + mvOccupied->DecManualBrakeLevel( ManualBrakePosNo ); + } + remove_hint( locale::string::driver_hint_manualbrakon ); + hint( + Action, + [this]( float const Parameter ) -> bool { + return ( ( mvOccupied->LocalBrake != TLocalBrake::ManualBrake ) || ( mvOccupied->MBrake == false ) || ( mvOccupied->ManualBrakePos == 0 ) ); } ); + break; + } + // master controller + case locale::string::driver_hint_mastercontrollersetidle: { + if( AIControllFlag ) { + while( ( mvControlling->RList[ mvControlling->MainCtrlPos ].Mn == 0 ) + && ( mvControlling->IncMainCtrl( 1 ) ) ) { + ; + } + } + remove_master_controller_hints(); + hint( + Action, + [this]( float const Parameter ) -> bool { + return ( mvControlling->RList[ mvControlling->MainCtrlPos ].Mn > 0 ); } ); + break; + } + case locale::string::driver_hint_mastercontrollersetseriesmode: { + if( AIControllFlag ) { + if( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) { + // limit yourself to series mode + if( mvControlling->ScndCtrlPos ) { + mvControlling->DecScndCtrl( 2 ); + } + while( ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) + && ( mvControlling->DecMainCtrl( 1 ) ) ) { + ; // all work is performed in the header + } + } + } + remove_master_controller_hints(); + hint( + Action, + [this]( float const Parameter ) -> bool { + return ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn < 2 ); } ); + break; + } + case locale::string::driver_hint_mastercontrollersetzerospeed: { + if( AIControllFlag ) { + ZeroSpeed(); + } + remove_master_controller_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvControlling->IsMainCtrlNoPowerPos() ) && ( mvControlling->IsScndCtrlNoPowerPos() ) ); } ); + break; + } + case locale::string::driver_hint_mastercontrollersetreverserunlock: { + if( AIControllFlag ) { + while( ( false == mvControlling->EIMDirectionChangeAllow() ) + && ( mvControlling->DecMainCtrl( 1 ) ) ) { + ; + } + } + remove_master_controller_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvControlling->EIMDirectionChangeAllow() ); }, + mvControlling->MainCtrlMaxDirChangePos ); + break; + } + case locale::string::driver_hint_tractiveforcedecrease: { + if( AIControllFlag ) { + DecSpeed(); + } + remove_master_controller_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( std::abs( mvControlling->Ft ) <= Parameter ); }, + std::min( + 0.0, + std::max( + std::abs( mvControlling->Ft ) * 0.95, + std::abs( mvControlling->Ft ) - 5.0 ) ) ); + break; + } + case locale::string::driver_hint_tractiveforceincrease: { + if( AIControllFlag ) { + IncSpeed(); + } + remove_master_controller_hints(); + // clear potentially remaining braking hints + // TODO: add other brake application hints, refactor into a method + { + remove_hint( locale::string::driver_hint_brakingforceincrease ); + remove_hint( locale::string::driver_hint_independentbrakeapply ); + } + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( AccDesired <= -0.06 ) + || ( AccDesired - AbsAccS <= 0.05 ) + || ( mvOccupied->EIMCtrlType > 0 ? + ( mvControlling->eimic_real >= 1.0 ) : + ( ( mvControlling->IsScndCtrlMaxPowerPos() ) && ( mvControlling->IsMainCtrlMaxPowerPos() ) ) ) ); } ); + break; + } + case locale::string::driver_hint_bufferscompress: { + if( AIControllFlag ) { + if( std::abs( mvControlling->Ft ) < 50000.0 ) { + IncSpeed(); + } + } + remove_master_controller_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( std::abs( mvControlling->Ft ) > 30.0 ) || ( ( OrderCurrentGet() & Disconnect ) == 0 ) ); } ); + break; + } + + case locale::string::driver_hint_secondcontrollersetzero: { + if( AIControllFlag ) { + mvControlling->DecScndCtrl( 2 ); + } + remove_master_controller_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvControlling->IsScndCtrlNoPowerPos() ); } ); + break; + } + + case locale::string::driver_hint_waterpumpon: { + if( AIControllFlag ) { + mvControlling->WaterPumpSwitch( true ); + } + remove_hint( locale::string::driver_hint_waterpumpoff ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvControlling->WaterPump }; + return ( ( device.start_type != start_t::manual ) || ( ( device.is_enabled == true ) && ( device.is_disabled == false ) ) ); } ); + break; + } + case locale::string::driver_hint_waterpumpoff: { + if( AIControllFlag ) { + mvControlling->WaterPumpSwitch( false ); + } + remove_hint( locale::string::driver_hint_waterpumpon ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvControlling->WaterPump }; + return ( ( device.start_type != start_t::manual ) || ( ( device.is_enabled == false ) && ( device.is_active == false ) ) ); } ); + break; + } + case locale::string::driver_hint_waterpumpbreakeron: { + if( AIControllFlag ) { + mvControlling->WaterPumpBreakerSwitch( true ); + } + remove_hint( locale::string::driver_hint_waterpumpbreakeroff ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvControlling->WaterPump }; + return ( device.breaker == true ); } ); + break; + } + case locale::string::driver_hint_waterpumpbreakeroff: { + if( AIControllFlag ) { + mvControlling->WaterPumpBreakerSwitch( false ); + } + remove_hint( locale::string::driver_hint_waterpumpbreakeron ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvControlling->WaterPump }; + return ( device.breaker == false ); } ); + break; + } + case locale::string::driver_hint_waterheateron: { + if( AIControllFlag ) { + mvControlling->WaterHeaterSwitch( true ); + } + remove_hint( locale::string::driver_hint_waterheateroff ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvControlling->WaterHeater }; + return ( device.is_enabled == true ); } ); + break; + } + case locale::string::driver_hint_waterheateroff: { + if( AIControllFlag ) { + mvControlling->WaterHeaterSwitch( false ); + } + remove_hint( locale::string::driver_hint_waterheateron ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvControlling->WaterHeater }; + return ( device.is_enabled == false ); } ); + break; + } + case locale::string::driver_hint_waterheaterbreakeron: { + if( AIControllFlag ) { + mvControlling->WaterHeaterBreakerSwitch( true ); + } + remove_hint( locale::string::driver_hint_waterheaterbreakeroff ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvControlling->WaterHeater }; + return ( device.breaker == true ); } ); + break; + } + case locale::string::driver_hint_waterheaterbreakeroff: { + if( AIControllFlag ) { + mvControlling->WaterHeaterBreakerSwitch( false ); + } + remove_hint( locale::string::driver_hint_waterheaterbreakeron ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvControlling->WaterHeater }; + return ( device.breaker == false ); } ); + break; + } + case locale::string::driver_hint_watercircuitslinkon: { + if( AIControllFlag ) { + mvControlling->WaterCircuitsLinkSwitch( true ); + } + remove_hint( locale::string::driver_hint_watercircuitslinkoff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvControlling->dizel_heat.auxiliary_water_circuit == false ) || ( mvControlling->WaterCircuitsLink == true ) ); } ); + break; + } + case locale::string::driver_hint_watercircuitslinkoff: { + if( AIControllFlag ) { + mvControlling->WaterCircuitsLinkSwitch( false ); + } + remove_hint( locale::string::driver_hint_watercircuitslinkon ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvControlling->dizel_heat.auxiliary_water_circuit == false ) || ( mvControlling->WaterCircuitsLink == false ) ); } ); + break; + } + case locale::string::driver_hint_waittemperaturetoolow: { + // NOTE: this action doesn't have AI component + hint( + Action, + [this](float const Parameter) -> bool { + return ( false == IsHeatingTemperatureTooLow ); } ); + break; + } + case locale::string::driver_hint_waitpressuretoolow: { + // NOTE: this action doesn't have AI component + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvControlling->ScndPipePress > 4.5 ) || ( mvControlling->VeselVolume == 0.0 ) ); } ); + break; + } + case locale::string::driver_hint_waitpantographpressuretoolow: { + // NOTE: this action doesn't have AI component + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvPantographUnit->PantPress >= 4.2 ); } ); + break; + } + case locale::string::driver_hint_waitloadexchange: { + // NOTE: this action doesn't have AI component + hint( + Action, + [this](float const Parameter) -> bool { + return ( ExchangeTime <= 0 ); } ); + break; + } + case locale::string::driver_hint_waitdeparturetime: { + // NOTE: this action doesn't have AI component + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAtPassengerStop == false ); } ); + break; + } + + // train brake and braking force. mutually exclusive: + // driver_hint_trainbrakesetpipeunlock + // driver_hint_trainbrakerelease + // driver_hint_brakingforcedecrease + // driver_hint_brakingforceincrease + // driver_hint_brakingforcesetzero + // driver_hint_brakingforcelap + case locale::string::driver_hint_trainbrakesetpipeunlock: { + if( AIControllFlag ) { + if( mvOccupied->HandleUnlock != -3 ) { + while( ( BrakeCtrlPosition >= mvOccupied->HandleUnlock ) + && ( BrakeLevelAdd( -1 ) ) ) { + // all work is done in the header + ; + } + } + } + remove_train_brake_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvOccupied->HandleUnlock == -3 ) || ( BrakeCtrlPosition == mvOccupied->HandleUnlock ) ); } ); + break; + } + case locale::string::driver_hint_trainbrakerelease: { + if( AIControllFlag ) { +// mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_RP ) ); + BrakeLevelSet( gbh_RP ); + } + remove_train_brake_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( is_equal( mvOccupied->fBrakeCtrlPos, mvOccupied->Handle->GetPos( bh_RP ), 0.2 ) ); } ); +// return ( BrakeCtrlPosition == gbh_RP ); } ); + break; + } + case locale::string::driver_hint_trainbrakeapply: { + if( AIControllFlag ) { + // za radą yB ustawiamy pozycję 3 kranu (ruszanie kranem w innych miejscach powino zostać wyłączone) + if( mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ) { + mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_EPB ) ); + } + else { + BrakeCtrlPosition = 3; + } + } + remove_train_brake_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsConsistBraked /*|| ( ( OrderCurrentGet() & Disconnect ) == 0 ) */ ); } ); + break; + } + case locale::string::driver_hint_brakingforcedecrease: { + if( AIControllFlag ) { + auto const brakingcontrolschange { DecBrake() }; + // set optional delay between brake adjustments + if( ( brakingcontrolschange ) && ( Actionparameter > 0 ) ) { + fBrakeTime = Actionparameter; + } + } + remove_train_brake_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( std::abs( mvControlling->Fb ) <= Parameter ); }, + std::min( 0.0, 0.95 * std::abs( mvControlling->Fb ) ) ); // keep hint until 5% decrease + break; + } + case locale::string::driver_hint_brakingforceincrease: { + if( AIControllFlag ) { + auto const brakingcontrolschange { IncBrake() }; + // set optional delay between brake adjustments + if( ( brakingcontrolschange ) && ( Actionparameter > 0 ) ) { + fBrakeTime = Actionparameter; + } + } + remove_train_brake_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( std::abs( mvControlling->Fb ) > Parameter ) || ( is_equal( mvOccupied->fBrakeCtrlPos, mvOccupied->Handle->GetPos( bh_EB ), 0.2 ) ) ); }, + std::max( + 0.0, + std::max( + std::abs( mvControlling->Fb ) * 1.05, + std::abs( mvControlling->Fb ) + 5.0 ) ) ); + break; + } + case locale::string::driver_hint_brakingforcesetzero: { // releases both train and independent brake + if( AIControllFlag ) { + while( true == DecBrake() ) { ; } + } + remove_train_brake_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( Ready ) && ( fReady < 0.4 ) && ( is_equal( mvOccupied->fBrakeCtrlPos, mvOccupied->Handle->GetPos( bh_RP ), 0.2 ) ) ); } ); + break; + } + case locale::string::driver_hint_brakingforcelap: { + if( AIControllFlag ) { + LapBrake(); + } + remove_train_brake_hints(); + // NOTE: this action doesn't have human component + // TBD, TODO: provide one? + break; + } + // independent brake + case locale::string::driver_hint_independentbrakeapply: { + if( AIControllFlag ) { + if( mvOccupied->LocalBrakePosA < 1.0 ) { + mvOccupied->IncLocalBrakeLevel( LocalBrakePosNo ); + if (mvOccupied->EIMCtrlEmergency) { + mvOccupied->DecLocalBrakeLevel(1); + } + } + } + remove_hint( locale::string::driver_hint_independentbrakerelease ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvOccupied->LocalBrakePosA >= Parameter ); }, + ( LocalBrakePosNo - ( mvOccupied->EIMCtrlEmergency ? 1 : 0 ) ) / LocalBrakePosNo ); + break; + } + case locale::string::driver_hint_independentbrakerelease: { + if( AIControllFlag ) { + ZeroLocalBrake(); + } + remove_hint( locale::string::driver_hint_independentbrakeapply ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvOccupied->LocalBrakePosA < 0.05 ); } ); + break; + } + // reverser + case locale::string::driver_hint_directionforward: { + if( AIControllFlag ) { + OrderDirectionChange( 1, mvOccupied ); + } + remove_reverser_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvOccupied->DirActive * mvOccupied->CabActive > 0 ); } ); + break; + } + case locale::string::driver_hint_directionbackward: { + if( AIControllFlag ) { + OrderDirectionChange( -1, mvOccupied ); + } + remove_reverser_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvOccupied->DirActive * mvOccupied->CabActive < 0 ); } ); + break; + } + case locale::string::driver_hint_directionother: { + if( AIControllFlag ) { +// DirectionForward( mvOccupied->DirAbsolute < 0 ); + OrderDirectionChange( iDirectionOrder, mvOccupied ); + DirectionChange(); + } + remove_reverser_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( iDirection == iDirectionOrder ); } ); + break; + } + case locale::string::driver_hint_directionnone: { + if( AIControllFlag ) { + ZeroDirection(); + } + remove_reverser_hints(); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvOccupied->DirActive == 0 ); } ); + break; + } + // anti-slip systems + case locale::string::driver_hint_sandingon: { + if( AIControllFlag ) { + mvControlling->Sandbox( true ); + } + remove_hint( locale::string::driver_hint_sandingoff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvControlling->Sand == 0 ) || ( mvControlling->SandDose == true ) ); } ); + break; + } + case locale::string::driver_hint_sandingoff: { + if( AIControllFlag ) { + mvControlling->Sandbox( false ); + } + remove_hint( locale::string::driver_hint_sandingon ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvControlling->Sand == 0 ) || ( mvControlling->SandDose == false ) ); } ); + break; + } + case locale::string::driver_hint_antislip: { + if( AIControllFlag ) { + mvControlling->AntiSlippingButton(); + } + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvControlling->ASBType != 1 ) || ( ( mvControlling->Hamulec->GetBrakeStatus() & b_asb ) != 0 ) || ( mvControlling->SlippingWheels == false ) ); } ); + break; + } + // horns + case locale::string::driver_hint_hornon: { + if( AIControllFlag ) { + mvOccupied->WarningSignal = static_cast( Actionparameter ); + } + remove_hint( locale::string::driver_hint_hornoff ); + hint( + Action, + [this](float const Parameter) -> bool { + // NOTE: we provide slightly larger horn activation window for human driver + return ( ( fWarningDuration + 5.0 < 0.05 ) || ( mvOccupied->WarningSignal != 0 ) ); } ); + break; + } + case locale::string::driver_hint_hornoff: { + if( AIControllFlag ) { + mvOccupied->WarningSignal = 0; + } +/* + // NOTE: for human driver the hint to switch horn on will disappear automatically + remove_hint( locale::string::driver_hint_hornon ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvOccupied->WarningSignal == 0 ); } ); +*/ + break; + } + // door locks + case locale::string::driver_hint_consistdoorlockson: { + if( AIControllFlag ) { + mvOccupied->LockDoors( true ); + } + // TODO: remove other door lock hints + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvOccupied->Doors.has_lock == false ) || ( mvOccupied->Doors.lock_enabled == true ) ); } ); + break; + } + // departure signal + case locale::string::driver_hint_departuresignalon: { + if( AIControllFlag ) { + mvOccupied->signal_departure( true ); + } + remove_hint( locale::string::driver_hint_departuresignaloff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvOccupied->Doors.has_warning == false ) || ( mvOccupied->DepartureSignal == true ) || mvOccupied->Vel > 5.0 ); } ); + break; + } + case locale::string::driver_hint_departuresignaloff: { + if( AIControllFlag ) { + mvOccupied->signal_departure( false ); + } + remove_hint( locale::string::driver_hint_departuresignalon ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( ( mvOccupied->Doors.has_warning == false ) || ( mvOccupied->DepartureSignal == false ) ); } ); + break; + } + // consist doors + case locale::string::driver_hint_doorrightopen: { + if( ( AIControllFlag ) + || ( pVehicle->MoverParameters->Doors.open_control == control_t::conductor ) ) { + pVehicle->MoverParameters->OperateDoors( side::right, true ); + } + remove_hint( locale::string::driver_hint_doorrightclose ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyDoorOpen[ side::right ] == true ); } ); + break; + } + case locale::string::driver_hint_doorrightclose: { + if( ( AIControllFlag ) + || ( pVehicle->MoverParameters->Doors.open_control == control_t::conductor ) ) { + pVehicle->MoverParameters->OperateDoors( side::right, false ); + } + remove_hint( locale::string::driver_hint_doorrightopen ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyDoorOpen[ side::right ] == false ); } ); + break; + } + case locale::string::driver_hint_doorleftopen: { + if( ( AIControllFlag ) + || ( pVehicle->MoverParameters->Doors.open_control == control_t::conductor ) ) { + pVehicle->MoverParameters->OperateDoors( side::left, true ); + } + remove_hint( locale::string::driver_hint_doorleftclose ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyDoorOpen[ side::left ] == true ); } ); + break; + } + case locale::string::driver_hint_doorleftclose: { + if( ( AIControllFlag ) + || ( pVehicle->MoverParameters->Doors.open_control == control_t::conductor ) ) { + pVehicle->MoverParameters->OperateDoors( side::left, false ); + } + remove_hint( locale::string::driver_hint_doorleftopen ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyDoorOpen[ side::left ] == false ); } ); + break; + } + case locale::string::driver_hint_doorrightpermiton: { + if( AIControllFlag ) { + pVehicle->MoverParameters->PermitDoors( side::right, true ); + } + remove_hint( locale::string::driver_hint_doorrightpermitoff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyDoorPermitActive[ side::right ] == true ); } ); + break; + } + case locale::string::driver_hint_doorrightpermitoff: { + if( AIControllFlag ) { + pVehicle->MoverParameters->PermitDoors( side::right, false ); + } + remove_hint( locale::string::driver_hint_doorrightpermiton ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyDoorPermitActive[ side::right ] == false ); } ); + break; + } + case locale::string::driver_hint_doorleftpermiton: { + if( AIControllFlag ) { + pVehicle->MoverParameters->PermitDoors( side::left, true ); + } + remove_hint( locale::string::driver_hint_doorleftpermitoff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyDoorPermitActive[ side::left ] == true ); } ); + break; + } + case locale::string::driver_hint_doorleftpermitoff: { + if( AIControllFlag ) { + pVehicle->MoverParameters->PermitDoors( side::left, false ); + } + remove_hint( locale::string::driver_hint_doorleftpermiton ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( IsAnyDoorPermitActive[ side::left ] == false ); } ); + break; + } + // consist lights + case locale::string::driver_hint_consistlightson: { + if( AIControllFlag ) { + mvOccupied->CompartmentLightsSwitch( true ); + mvOccupied->CompartmentLightsSwitchOff( false ); + } + remove_hint( locale::string::driver_hint_consistlightsoff ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvOccupied->CompartmentLights }; + return ( ( Global.fLuminance * ConsistShade > 0.40 ) || ( device.start_type != start_t::manual ) || ( ( device.is_enabled == true ) && ( device.is_disabled == false ) ) ); } ); + break; + } + case locale::string::driver_hint_consistlightsoff: { + if( AIControllFlag ) { + mvOccupied->CompartmentLightsSwitch( false ); + mvOccupied->CompartmentLightsSwitchOff( true ); + } + remove_hint( locale::string::driver_hint_consistlightson ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvOccupied->CompartmentLights }; + return ( ( Global.fLuminance * ConsistShade < 0.35 ) || ( device.start_type != start_t::manual ) || ( ( device.is_enabled == false ) && ( device.is_active == false ) ) ); } ); + break; + } + // consist heating + case locale::string::driver_hint_consistheatingon: { + if( AIControllFlag ) { + mvControlling->HeatingAllow = true; + } + remove_hint( locale::string::driver_hint_consistheatingoff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvControlling->HeatingAllow == true ); } ); + break; + } + case locale::string::driver_hint_consistheatingoff: { + if( AIControllFlag ) { + mvControlling->HeatingAllow = false; + } + remove_hint( locale::string::driver_hint_consistheatingon ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( mvControlling->HeatingAllow == false ); } ); + break; + } + + case locale::string::driver_hint_securitysystemreset: { + if( AIControllFlag ) { + mvOccupied->SecuritySystemReset(); + } + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { mvOccupied->CompartmentLights }; + return ( mvOccupied->SecuritySystem.Status < s_aware ); } ); + break; + } + case locale::string::driver_hint_couplingadapterattach: { + // TODO: run also for potential settings-based virtual assistant + if( AIControllFlag ) { + pVehicles[ end::front ]->attach_coupler_adapter( Actionparameter ); + } + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { pVehicles[ end::front ]->MoverParameters->Couplers[ static_cast( Parameter ) ] }; + return ( device.type() == TCouplerType::Automatic ); } ); + break; + } + case locale::string::driver_hint_couplingadapterremove: { + if( AIControllFlag || Global.AITrainman ) { + pVehicles[ end::front ]->remove_coupler_adapter( Actionparameter ); + } + hint( + Action, + [this](float const Parameter) -> bool { + auto const &device { pVehicles[ end::front ]->MoverParameters->Couplers[ static_cast( Parameter ) ] }; + return ( false == device.has_adapter() ); } ); + break; + } + // lights + case locale::string::driver_hint_headcodepc1: { + if( AIControllFlag ) { + pVehicles[ end::front ]->RaLightsSet( light::headlight_left | light::headlight_right | light::headlight_upper, -1 ); + } + remove_hint( locale::string::driver_hint_headcodepc2 ); + remove_hint( locale::string::driver_hint_headcodetb1 ); + remove_hint( locale::string::driver_hint_lightsoff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( pVehicles[ end::front ]->has_signal_pc1_on() ); } ); + break; + } + case locale::string::driver_hint_headcodepc2: { + if( AIControllFlag ) { + pVehicles[ end::front ]->RaLightsSet( light::redmarker_left | light::headlight_right | light::headlight_upper, -1 ); + } + remove_hint( locale::string::driver_hint_headcodepc1 ); + remove_hint( locale::string::driver_hint_headcodetb1 ); + remove_hint( locale::string::driver_hint_lightsoff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( pVehicles[ end::front ]->has_signal_pc2_on() ); } ); + break; + } + case locale::string::driver_hint_headcodetb1: { + // HACK: the 'front' and 'rear' of the consist is determined by current consist direction + // since direction shouldn't affect Tb1 light configuration, we 'counter' this behaviour by virtually swapping end vehicles + if( AIControllFlag ) { + if( mvOccupied->DirActive >= 0 ) { Lights( light::headlight_right, light::headlight_left ); } + else { Lights( light::headlight_left, light::headlight_right ); } + } + remove_hint( locale::string::driver_hint_headcodepc1 ); + remove_hint( locale::string::driver_hint_headcodepc2 ); + remove_hint( locale::string::driver_hint_headcodepc5 ); + remove_hint( locale::string::driver_hint_lightsoff ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const activeend { mvOccupied->CabActive >= 0 ? end::front : end::rear }; + auto const consistfront { mvOccupied->DirActive >= 0 ? end::front : end::rear }; + return ( + pVehicles[ consistfront ]->has_signal_on( activeend, light::headlight_right ) + && pVehicles[ 1 - consistfront ]->has_signal_on( 1 - activeend, light::headlight_left ) ); } ); + break; + } + case locale::string::driver_hint_headcodepc5: { + if( AIControllFlag || Global.AITrainman ) { + pVehicles[ end::rear ]->RaLightsSet( -1, light::redmarker_left | light::redmarker_right | light::rearendsignals ); + } + remove_hint( locale::string::driver_hint_headcodetb1 ); + remove_hint( locale::string::driver_hint_lightsoff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( pVehicles[ end::rear ]->has_signal_pc5_on() ); } ); + break; + } + case locale::string::driver_hint_lightsoff: { + if( AIControllFlag ) { + Lights( 0, 0 ); + } + remove_hint( locale::string::driver_hint_headcodepc1 ); + remove_hint( locale::string::driver_hint_headcodepc2 ); + remove_hint( locale::string::driver_hint_headcodepc5 ); + remove_hint( locale::string::driver_hint_headcodetb1 ); + hint( + Action, + [this](float const Parameter) -> bool { + auto const activeend { mvOccupied->CabActive >= 0 ? end::front : end::rear }; + auto const consistfront { mvOccupied->DirActive >= 0 ? end::front : end::rear }; + return ( + pVehicles[ consistfront ]->has_signal_on( activeend, 0 ) + && pVehicles[ 1 - consistfront ]->has_signal_on( 1 - activeend, 0 ) ); } ); + break; + } + // releaser + case locale::string::driver_hint_releaseron: { + if( AIControllFlag ) { + mvOccupied->BrakeReleaser( 1 ); + } + remove_hint( locale::string::driver_hint_releaseroff ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( true == mvOccupied->Hamulec->Releaser() ); } ); + break; + } + case locale::string::driver_hint_releaseroff: { + if( AIControllFlag ) { + mvOccupied->BrakeReleaser( 0 ); + } + remove_hint( locale::string::driver_hint_releaseron ); + hint( + Action, + [this](float const Parameter) -> bool { + return ( false == mvOccupied->Hamulec->Releaser() ); } ); + break; + } + + default: { + break; + } + } +} diff --git a/drivermode.cpp b/drivermode.cpp index b615daa3..a3b7646a 100644 --- a/drivermode.cpp +++ b/drivermode.cpp @@ -866,7 +866,8 @@ driver_mode::OnKeyDown(int cKey) { } else if( Global.ctrlState ) { // ctrl + f7 toggles static daylight - simulation::Environment.toggle_daylight(); + Global.FakeLight = !Global.FakeLight; + simulation::Environment.on_daylight_change(); break; } else if( Global.shiftState ) { diff --git a/driveruipanels.cpp b/driveruipanels.cpp index f3e2069a..5aebe839 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -147,12 +147,12 @@ drivingaid_panel::update() { expandedtext = m_buffer.data(); } else { - auto const trackblockdistance{ std::abs( owner->TrackBlock() ) }; - if( trackblockdistance <= 75.0 ) { + auto const trackobstacledistance { std::abs( owner->TrackObstacle() ) }; + if( trackobstacledistance <= 75.0 ) { std::snprintf( m_buffer.data(), m_buffer.size(), locale::strings[ locale::string::driver_aid_vehicleahead ].c_str(), - trackblockdistance ); + trackobstacledistance ); expandedtext = m_buffer.data(); } } @@ -197,8 +197,8 @@ scenario_panel::update() { if( owner == nullptr ) { return; } std::string textline = - locale::strings[ locale::string::driver_scenario_currenttask ] + "\n " - + owner->OrderCurrent(); + locale::strings[ locale::string::driver_scenario_currenttask ] + + "\n " + owner->OrderCurrent(); text_lines.emplace_back( textline, Global.UITextColor ); } @@ -244,6 +244,22 @@ scenario_panel::render() { for( auto const &line : text_lines ) { ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), line.data.c_str() ); } + // hints + if( owner != nullptr ) { + auto const hintheader{ locale::strings[ locale::string::driver_hint_header ] }; + if( true == ImGui::CollapsingHeader( hintheader.c_str() ) ) { + for( auto const &hint : owner->m_hints ) { + auto const isdone { std::get( hint )( std::get( hint ) ) }; + auto const hintcolor{ ( + isdone ? + colors::uitextgreen : + Global.UITextColor ) }; + ImGui::PushStyleColor( ImGuiCol_Text, { hintcolor.r, hintcolor.g, hintcolor.b, hintcolor.a } ); + ImGui::TextWrapped( locale::strings[ std::get( hint ) ].c_str(), std::get( hint ) ); + ImGui::PopStyleColor(); + } + } + } } ImGui::End(); } @@ -340,11 +356,6 @@ timetable_panel::update() { text_lines.emplace_back( locale::strings[ locale::string::driver_timetable_notimetable ], Global.UITextColor ); } else { - - auto const loadingcolor { glm::vec4( 164.0f / 255.0f, 84.0f / 255.0f, 84.0f / 255.0f, 1.f ) }; - auto const waitcolor { glm::vec4( 164.0f / 255.0f, 132.0f / 255.0f, 84.0f / 255.0f, 1.f ) }; - auto const readycolor { glm::vec4( 84.0f / 255.0f, 164.0f / 255.0f, 132.0f / 255.0f, 1.f ) }; - // header m_tablelines.emplace_back( u8"┌─────┬────────────────────────────────────┬─────────┬─────┐", Global.UITextColor ); @@ -397,9 +408,9 @@ timetable_panel::update() { to_minutes_str( std::max( 0.0, CompareTime( table.TimeTable[ i - 1 ].Dh, table.TimeTable[ i - 1 ].Dm, tableline->Dh, tableline->Dm ) - 0.5 ), false, 3 ) ) }; auto const linecolor { ( ( i != owner->iStationStart ) ? Global.UITextColor : - loadchangeinprogress ? loadingcolor : - candepart ? readycolor : // czas minął i odjazd był, to nazwa stacji będzie na zielono - isatpassengerstop ? waitcolor : + loadchangeinprogress ? colors::uitextred : + candepart ? colors::uitextgreen : // czas minął i odjazd był, to nazwa stacji będzie na zielono + isatpassengerstop ? colors::uitextorange : Global.UITextColor ) }; auto const trackcount{ ( tableline->TrackNo == 1 ? u8" ┃ " : u8" ║ " ) }; m_tablelines.emplace_back( @@ -550,12 +561,28 @@ debug_panel::render() { // TODO: refactor status reset into mover method m_input.mover->DamageFlag = 0; m_input.mover->EngDmgFlag = 0; - m_input.mover->V = 0.0001; // HACK: force vehicle position re-calculation - m_input.mover->DistCounter = 0.0; + if( std::abs( m_input.mover->V ) < 0.0001 ) { // don't alter speed of already moving vehicle + // HACK: force vehicle position re-calculation + m_input.mover->V = 0.0001; + } +// m_input.mover->DistCounter = 0.0; m_input.mover->WheelFlat = 0.0; m_input.mover->AlarmChainFlag = false; m_input.mover->OffsetTrackH = 0.0; m_input.mover->OffsetTrackV = 0.0; + // pantographs + for( auto idx = 0; idx < m_input.vehicle->iAnimType[ ANIM_PANTS ]; ++idx ) { + auto &pantograph { *( m_input.vehicle->pants[ idx ].fParamPants ) }; + if( pantograph.PantWys >= 0.0 ) { // skip undamaged instances + continue; } + ++m_input.mover->EnginePowerSource.CollectorParameters.CollectorsNo; + pantograph.fAngleL = pantograph.fAngleL0; + pantograph.fAngleU = pantograph.fAngleU0; + pantograph.PantWys = + pantograph.fLenL1 * std::sin( pantograph.fAngleL ) + + pantograph.fLenU1 * std::sin( pantograph.fAngleU ) + + pantograph.fHeight; + } } } } @@ -626,26 +653,42 @@ debug_panel::render_section_scenario() { GLFW_PRESS, 0 ); } } - // 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" ) ) { - command_relay relay; - relay.post( - user_command::setdatetime, - Global.fMoveLight, - clamp( time, 0, 1439 ), - GLFW_PRESS, 0 ); + // dynamic material update checkbox + ImGui::Checkbox( "Update Item Materials", &Global.UpdateMaterials ); + if( DebugModeFlag ) { + if( ImGui::Checkbox( "Force Daylight", &Global.FakeLight ) ) { + simulation::Environment.on_daylight_change(); + } + } + // advanced options, only visible in debug mode + if( DebugModeFlag ) { + // 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" ) ) { + command_relay relay; + relay.post( + user_command::setdatetime, + Global.fMoveLight, + clamp( time, 0, 1439 ), + GLFW_PRESS, 0 ); + } + } + // time acceleration + { + auto timerate { ( Global.fTimeSpeed == 60 ? 4 : Global.fTimeSpeed == 20 ? 3 : Global.fTimeSpeed == 5 ? 2 : 1 ) }; + if( ImGui::SliderInt( ( "x " + to_string( Global.fTimeSpeed, 0 ) + "###timeacceleration" ).c_str(), &timerate, 1, 4, "Time acceleration" ) ) { + Global.fTimeSpeed = ( timerate == 4 ? 60 : timerate == 3 ? 20 : timerate == 2 ? 5 : 1 ); + } } } - ImGui::Checkbox( "Update Item Materials", &Global.UpdateMaterials ); return true; } @@ -688,17 +731,18 @@ debug_panel::update_section_vehicle( std::vector &Output ) { ( mover.Pantographs[ end::front ].valve.is_active ? 'P' : ( mover.Pantographs[ end::front ].valve.is_enabled ? 'p' : '.' ) ), ( mover.PantPressLockActive ? '!' : ( mover.PantPressSwitchActive ? '*' : '.' ) ), ( mover.WaterPump.is_active ? 'W' : ( false == mover.WaterPump.breaker ? '-' : ( mover.WaterPump.is_enabled ? 'w' : '.' ) ) ), - ( true == mover.WaterHeater.is_damaged ? '!' : ( mover.WaterHeater.is_active ? 'H' : ( false == mover.WaterHeater.breaker ? '-' : ( mover.WaterHeater.is_enabled ? 'h' : '.' ) ) ) ), + ( mover.WaterHeater.is_damaged ? '!' : ( mover.WaterHeater.is_active ? 'H' : ( false == mover.WaterHeater.breaker ? '-' : ( mover.WaterHeater.is_enabled ? 'h' : '.' ) ) ) ), ( mover.FuelPump.is_active ? 'F' : ( mover.FuelPump.is_enabled ? 'f' : '.' ) ), ( mover.OilPump.is_active ? 'O' : ( mover.OilPump.is_enabled ? 'o' : '.' ) ), ( mover.Mains ? 'M' : '.' ), ( mover.FuseFlag ? '!' : '.' ), - ( false == mover.ConverterAllowLocal ? '-' : ( mover.ConverterAllow ? ( mover.ConverterFlag ? 'X' : 'x' ) : '.' ) ), + ( mover.ConverterFlag ? 'X' : ( false == mover.ConverterAllowLocal ? '-' : ( mover.ConverterAllow ? 'x' : '.' ) ) ), ( mover.ConvOvldFlag ? '!' : '.' ), ( mover.CompressorFlag ? 'C' : ( false == mover.CompressorAllowLocal ? '-' : ( ( mover.CompressorAllow || ( mover.CompressorStart == start_t::automatic && mover.CompressorSpeed > 0.0 ) ) ? 'c' : '.' ) ) ), ( mover.CompressorGovernorLock ? '!' : '.' ), ( mover.StLinSwitchOff ? '-' : ( mover.ControlPressureSwitch ? '!' : ( mover.StLinFlag ? '+' : '.' ) ) ), ( mover.Heating ? 'H' : ( mover.HeatingAllow ? 'h' : '.' ) ), + ( mover.Hamulec->Releaser() ? '^' : '.' ), std::string( m_input.mechanik ? locale::strings[ locale::string::debug_vehicle_radio ] + ( mover.Radio ? std::to_string( m_input.mechanik->iRadioChannel ) : "-" ) : "" ).c_str(), std::string( isdieselenginepowered ? locale::strings[ locale::string::debug_vehicle_oilpressure ] + to_string( mover.OilPump.pressure, 2 ) : "" ).c_str(), // power transfers @@ -1001,7 +1045,8 @@ debug_panel::update_section_ai( std::vector &Output ) { // velocity factors textline = "Velocity:\n desired: " + to_string( mechanik.VelDesired, 0 ) - + ", next: " + to_string( mechanik.VelNext, 0 ); + + ", next: " + to_string( mechanik.VelNext, 0 ) + + "\n current: " + to_string( mover.Vel + 0.001f, 2 ); std::vector< std::pair< double, std::string > > const restrictions{ { mechanik.VelSignalLast, "signal" }, @@ -1031,12 +1076,8 @@ debug_panel::update_section_ai( std::vector &Output ) { textline = "Acceleration:\n desired: " + to_string( mechanik.AccDesired, 2 ) + ", corrected: " + to_string( mechanik.AccDesired * mechanik.BrakeAccFactor(), 2 ) - + "\n current: " + to_string( mechanik.AbsAccS_pub + 0.001f, 2 ) + + "\n current: " + to_string( mechanik.AbsAccS + 0.001f, 2 ) + ", slope: " + to_string( mechanik.fAccGravity + 0.001f, 2 ) + " (" + ( mechanik.fAccGravity > 0.01 ? "\\" : ( mechanik.fAccGravity < -0.01 ? "/" : "-" ) ) + ")" - + "\n brake threshold: " + to_string( mechanik.fAccThreshold, 2 ) - + ", delays: " + to_string( mechanik.fBrake_a0[ 0 ], 2 ) - + "+" + to_string( mechanik.fBrake_a1[ 0 ], 2 ) - + "\n virtual brake position: " + to_string(mechanik.BrakeCtrlPosition, 2) + "\n desired diesel percentage: " + to_string(mechanik.DizelPercentage) + "/" + to_string(mechanik.DizelPercentage_Speed) + "/" + to_string(100.4*mechanik.mvControlling->eimic_real, 0); @@ -1045,7 +1086,10 @@ debug_panel::update_section_ai( std::vector &Output ) { // brakes textline = - "Brakes:\n consist: " + to_string( mechanik.fReady, 2 ) + " or less"; + "Brakes:\n highest pressure: " + to_string( mechanik.fReady, 2 ) + ( mechanik.Ready ? " (all brakes released)" : "" ) + + "\n activation threshold: " + to_string( mechanik.fAccThreshold, 2 ) + + "\n activation delays: " + to_string( mechanik.fBrake_a0[ 0 ], 2 ) + " + " + to_string( mechanik.fBrake_a1[ 0 ], 2 ) + + "\n virtual brake position: " + to_string( mechanik.BrakeCtrlPosition, 2 ); Output.emplace_back( textline, Global.UITextColor ); @@ -1096,7 +1140,6 @@ debug_panel::update_section_scenario( std::vector &Output ) { Output.emplace_back( textline, Global.UITextColor ); // current luminance level textline = "Light level: " + to_string( Global.fLuminance, 3 ) + ( Global.FakeLight ? "(*)" : "" ); - if( Global.FakeLight ) { textline += "(*)"; } textline += "\nWind: azimuth " + to_string( simulation::Environment.wind_azimuth(), 0 ) // ma być azymut, czyli 0 na północy i rośnie na wschód @@ -1291,7 +1334,6 @@ debug_panel::render_section_settings() { if( ImGui::SliderFloat( ( to_string( static_cast( Global.EnvironmentAmbientVolume * 100 ) ) + "%###volumeambient" ).c_str(), &Global.EnvironmentAmbientVolume, 0.0f, 1.0f, "Ambient sounds" ) ) { audio::event_volume_change = true; } - ImGui::PushStyleColor( ImGuiCol_Text, { Global.UITextColor.r, Global.UITextColor.g, Global.UITextColor.b, Global.UITextColor.a } ); return true; } diff --git a/maszyna.vcxproj b/maszyna.vcxproj index e4316457..32fb2d24 100644 --- a/maszyna.vcxproj +++ b/maszyna.vcxproj @@ -184,6 +184,7 @@ + diff --git a/maszyna.vcxproj.filters b/maszyna.vcxproj.filters index 6f8a6903..36e7feee 100644 --- a/maszyna.vcxproj.filters +++ b/maszyna.vcxproj.filters @@ -471,6 +471,9 @@ Source Files + + Source Files + diff --git a/opengl33renderer.cpp b/opengl33renderer.cpp index eb93df50..df8eda44 100644 --- a/opengl33renderer.cpp +++ b/opengl33renderer.cpp @@ -4463,7 +4463,7 @@ bool opengl33_renderer::Init_caps() m_shadowbuffersize = std::min(m_shadowbuffersize, texturesize); WriteLog("shadows map size capped at " + std::to_string(m_shadowbuffersize) + "p"); } - Global.DynamicLightCount = std::min(Global.DynamicLightCount, 8); + Global.DynamicLightCount = std::min(Global.DynamicLightCount, 8 - 1); if (Global.iMultisampling) { diff --git a/particles.cpp b/particles.cpp index f2fa5188..9c45717f 100644 --- a/particles.cpp +++ b/particles.cpp @@ -432,7 +432,7 @@ particle_manager::find( std::string const &Template ) { return &( m_sourcetemplates.find( templatename )->second ); } else { - ErrorLog( "Bad file: failed do locate particle source configuration file \"" + std::string( templatepath + templatename + ".txt" ) + "\"", logtype::file ); + ErrorLog( "Bad file: failed to locate particle source configuration file \"" + std::string( templatepath + templatename + ".txt" ) + "\"", logtype::file ); } // if fetching data from the file fails too, give up return nullptr; diff --git a/scene.cpp b/scene.cpp index 1f104e7f..e67f9798 100644 --- a/scene.cpp +++ b/scene.cpp @@ -81,6 +81,7 @@ basic_cell::update_traction( TDynamicObject *Vehicle, int const Pantographindex // i do tego jeszcze wejdzie pod ślizg if( fHorizontal <= 0.0 ) { // 0.635 dla AKP-1 AKP-4E + SetFlag( Vehicle->MoverParameters->DamageFlag, dtrain_pantograph ); pantograph->PantWys = -1.0; // ujemna liczba oznacza połamanie pantograph->hvPowerWire = nullptr; // bo inaczej się zasila w nieskończoność z połamanego if( Vehicle->MoverParameters->EnginePowerSource.CollectorParameters.CollectorsNo > 0 ) { diff --git a/simulationenvironment.cpp b/simulationenvironment.cpp index 816b13cd..dc2796bd 100644 --- a/simulationenvironment.cpp +++ b/simulationenvironment.cpp @@ -21,9 +21,7 @@ world_environment Environment; } // simulation void -world_environment::toggle_daylight() { - - Global.FakeLight = !Global.FakeLight; +world_environment::on_daylight_change() { if( Global.FakeLight ) { // for fake daylight enter fixed hour @@ -64,8 +62,10 @@ void world_environment::compute_weather() const { Global.Weather = ( - Global.Overcast <= 0.25 ? "clear:" : - Global.Overcast <= 1.0 ? "cloudy:" : + Global.Overcast <= 0.10 ? "clear:" : + Global.Overcast <= 0.50 ? "scattered:" : + Global.Overcast <= 0.90 ? "broken:" : + Global.Overcast <= 1.00 ? "overcast:" : ( Global.Season != "winter:" ? "rain:" : "snow:" ) ); diff --git a/simulationenvironment.h b/simulationenvironment.h index d916594f..a146ddf8 100644 --- a/simulationenvironment.h +++ b/simulationenvironment.h @@ -33,7 +33,7 @@ public: void update_precipitation(); void time( int const Hour = -1, int const Minute = -1, int const Second = -1 ); // switches between static and dynamic daylight calculation - void toggle_daylight(); + void on_daylight_change(); // calculates current season of the year based on set simulation date void compute_season( int const Yearday ) const; // calculates current weather diff --git a/translation.cpp b/translation.cpp index a3eed3b4..09894da6 100644 --- a/translation.cpp +++ b/translation.cpp @@ -56,6 +56,110 @@ init() { "Drive according to signals and timetable", "Bank consist ahead", + "Hints", + "Switch on battery", + "Switch off battery", + "Switch on radio", + "Switch off radio", + "Tune into channel %.0f", + "Switch on oil pump", + "Switch off oil pump", + "Switch on fuel pump", + "Switch off fuel pump", + "Switch pantograph 3-way valve to primary air source", + "Switch pantograph 3-way valve to auxiliary air source", + "Switch on pantograph compressor", + "Switch off pantograph compressor", + "Enable pantographs valve", + "Disable pantographcs valve", + "Raise pantograph A", + "Lower pantograph A", + "Raise pantograph B", + "Lower pantograph B", + "Switch on converter", + "Switch off converter", + "Reset converter overload relay", + "Reset main circuit ground relay", + "Reset traction motors overload relay", + "Close line breaker", + "Open line breaker", + "Switch on compressor", + "Switch off compressor", + "Switch on front motor blowers", + "Switch off front motor blowers", + "Switch on rear motor blowers", + "Switch off rear motor blowers", + "Apply spring brake", + "Release spring brake", + "Apply manual brake", + "Release manual brake", + "Set engine to idle", + "Set master controller to series mode", + "Switch on water heater", + "Switch off water heater", + "Switch on water heater breaker", + "Switch off water heater breaker", + "Switch on water pump", + "Switch off water pump", + "Switch on water pump breaker", + "Switch off water pump breaker", + "Link water circuits", + "Unlink water circuits", + "Wait for warm up to complete", + "Set master controller to neutral", + "Set master controller to position %.0f", + "Set brake controller to pipe filling mode", + "Release train brake", + "Apply train brake", + "Set reverser to forward", + "Set reverser to reverse", + "Switch reverser to opposite direction", + "Set reverser to neutral", + "Wait for main reservoir to fill", + "Wait for sufficient air pressure in pantograph subsystem", + "Switch on sanding", + "Switch off sanding", + "Switch on door locks", + "Switch on departure signal", + "Switch off departure signal", + "Open doors", + "Close doors", + "Open doors", + "Close doors", + "Grant permit to open doors", + "Revoke permit to open doors", + "Grant permit to open doors", + "Revoke permit to open doors", + "Sound the horn", + "Switch off horn", + "Switch on consist lights", + "Switch off consist lights", + "Switch on consist heating", + "Switch off consist heating", + "Acknowledge alerter", + "Attach coupling adapter", + "Remove coupling adapter", + "Switch off field shunting", + "Reduce tractive force", + "Increase tractive force", + "Reduce braking force", + "Increase braking force", + "Release train brakes", + "Lap train brake", + "Apply independent brake", + "Release independent brake", + "Apply anti slip brake", + "Wait for passenger exchange to complete", + "Wait for departure time", + "Switch on Pc 1 head lamp code", + "Switch on Pc 2 head lamp code", + "Switch on Pc 5 tail lamp code", + "Switch on Tb 1 head lamp code", + "Switch off lights", + "Actuate", + "Stop actuating", + "Apply tractive force to compress buffers", + "%-*.*s Time: %d:%02d:%02d", // HACK: some manual padding to account for longer 'time' equivalent in polish version "Timetable", "(no timetable)", @@ -70,7 +174,7 @@ init() { "Name: %s%s\nLoad: %.0f %s\nStatus: %s%s\nCouplers:\n front: %s\n rear: %s", ", owned by: ", "none", - "Devices: %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%s%s\nPower transfers: %.0f@%.0f%s[%.0f]%s%.0f@%.0f :: %.0f@%.0f%s[%.0f]%s%.0f@%.0f", + "Devices: %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%s%s\nPower transfers: %.0f@%.0f%s[%.0f]%s%.0f@%.0f :: %.0f@%.0f%s[%.0f]%s%.0f@%.0f", " R", " oil pressure: ", "Controllers:\n master: %d(%d), secondary: %s\nEngine output: %.1f, current: %.0f\nRevolutions:\n engine: %.0f, motors: %.0f\n engine fans: %.0f, motor fans: %.0f+%.0f, cooling fans: %.0f+%.0f", @@ -198,7 +302,7 @@ init() { "selected pantograph", "selected pantograph", "pantograph compressor", - "pantograph 3 way valve", + "pantograph 3-way valve", "heating", "braking indicator", "door locking", @@ -235,13 +339,13 @@ init() { "Nastawnik: %3d+%d %c%s", " Predkosc: %d km/h (limit %d km/h%s)%s", ", nowy limit: %d km/h za %.1f km", - " Nachylenie: %.1f%%%%", + " Nachylenie: %.1f%%%%", "Hamulce: %5.1f+%-2.0f%c%s", " Cisnienie: %.2f kPa (przewod glowny: %.2f kPa)", "!CZUWAK! ", "!SHP!", - " Wymiana pasazerska (do zakonczenia %d s)", - " Inny pojazd na drodze (odleglosc: %.1f m)", + " Wymiana pasazerska (do zakonczenia %d s)", + " Inny pojazd na drodze (odleglosc: %.1f m)", "Scenariusz", "Zlecenie", @@ -261,6 +365,110 @@ init() { "Prowadzic sklad wedlug sygnalow i rozkladu", "Popychac sklad z przodu", + "Podpowiedzi", + "Zalaczyc akumulatory", + "Wylaczyc akumulatory", + "Zalaczyc radio", + "Wylaczyc radio", + "Ustawic radio na kanal %.0f", + "Zalaczyc pompe oleju", + "wylaczyc pompe oleju", + "Zalaczyc pompe paliwa", + "wylaczyc pompe paliwa", + "Ustawic kurek trojdrogowy w pozycji napelniania zbiornika glownego", + "Ustawic kurek trojdrogowy w pozycji napelniania cylindrow pantografow", + "Zalaczyc sprezarke pantografow", + "Wylaczyc sprezarke pantografow", + "Otworzyc zawor pantografow", + "Zamknac zawor pantografow", + "Podniesc pantograf A", + "Opuscic pantograf A", + "Podniesc pantograf B", + "Opuscic pantograf B", + "Zalaczyc przetwornice", + "Wylaczyc przetwornice", + "Odblokowac przekaznik nadmiarowy przetwornicy", + "Odblokowac przekaznik roznicowy obwodu glownego", + "Odblokowac przekaznik nadmiarowy motorow trakcyjnych", + "Zamknac wylacznik szybki", + "Otworzyc wylacznik szybki", + "Zalaczyc sprezarke", + "Wylaczyc sprezarke", + "Zalaczyc wentylatory przednich motorow trakcyjnych", + "Wylaczyc wentylatory przednich motorow trakcyjnych", + "Zalaczyc wentylatory tylnych motorow trakcyjnych", + "Wylaczyc wentylatory tylnych motorow trakcyjnych", + "Zalaczyc hamulec sprezynowy", + "Zwolnic hamules sprezynowy", + "Zalaczyc hamulec reczny", + "Zwolnic hamulec reczny", + "Ustawic nastawnik jazdy na pozycje biegu jalowego", + "Ustawic nastawnik jazdy na pozycje szeregowa", + "Zalaczyc podgrzewacz wody", + "Wylaczyc podgrzewacz wody", + "Zalaczyc bezpiecznik podgrzewacza wody", + "Wylaczyc bezpiecznik podgrzewacza wody", + "Zalaczyc pompe wody", + "Wylaczyc pompe wody", + "Zalaczyc bezpiecznik pompy wody", + "Wylaczyc bezpiecznik pompy wody", + "Polaczyc obiegi wody", + "Rozlaczyc obiegi wody", + "Zaczekac na osiagniecie temperatury roboczej", + "Ustawic nastawnik jazdy na pozycje neutralna", + "Ustawic nastawnik jazdy na pozycje %.0f", + "Ustawic hamulec zespolony na pozycje popelnienia przewodu glownego", + "Ustawic hamulec zespolony na pozycje jazdy", + "Zalaczyc hamulec zespolony", + "Ustawic nastawnik kierunku na jazde naprzod", + "Ustawic nastawnik kierunku na jazde wstecz", + "Zmienic kierunek jazdy na przeciwny", + "Ustawic nastawnik kierunku na pozycje neutralna", + "Zaczekac na napelnienie zbiornika glownego", + "Zaczekac na osiagniecie cisnienia potrzebnego do podniesienia pantografow", + "Zalaczyc piaskowanie", + "Wylaczyc piaskowanie", + "Zalaczyc blokade drzwi", + "Zalaczyc sygnal odjazdu", + "Wylaczyc sygnal odjazdu", + "Otworzyc drzwi", + "Zamknac drzwi", + "Otworzyc drzwi", + "Zamknac drzwi", + "Podac zezwolenie na otwarcie drzwi", + "Cofnac zezwolenie na otwarcie drzwi", + "Podac zezwolenie na otwarcie drzwi", + "Cofnac zezwolenie na otwarcie drzwi", + "Podac sygnal dzwiekowy", + "Wylaczyc syrene", + "Zalaczyc oswietlenie skladu", + "Wylaczyc oswietlenie skladu", + "Zalaczyc ogrzewanie skladu", + "wylaczyc ogrzewanie skladu", + "Zbic czuwak", + "Zalozyc polsprzeg", + "Zdjac polsprzeg", + "Wylaczyc bocznikowanie", + "Zmniejszyc sile pociagowa", + "Zwiekszyc sile pociagowa", + "Zmniejszyc sile hamujaca", + "Zwiekszyc sile hamujaca", + "Odhamowac sklad", + "Utrzymac sile hamujaca", + "Zalaczyc hamulec lokomotywy", + "Zwolnic hamulec lokomotywy", + "Zalaczyc hamulec przeciwposlizgowy", + "Zaczekac na zakonczenie wymiany pasazerskiej", + "Zaczekac na godzine odjazdu", + "Zapalic sygnal jazdy rozkladowej Pc 1", + "Zapalic sygnal jazdy rozkladowej Pc 2", + "Zapalic sygnal konca pociagu Pc 5", + "Zapalic sygnal jazdy manewrowej Tb 1", + "Wylaczyc swiatla", + "Zalaczyc odluzniacz", + "Wylaczyc odluzniacz", + "Aplikowac sile trakcyjna celem scisniecia skladu", + "%-*.*s Godzina: %d:%02d:%02d", "Rozklad jazdy", "(brak rozkladu)", @@ -275,7 +483,7 @@ init() { "Nazwa: %s%s\nLadunek: %.0f %s\nStatus: %s%s\nSprzegi:\n przedni: %s\n tylny: %s", ", wlasciciel: ", "wolny", - "Urzadzenia: %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%s%s\nTransfer pradow: %.0f@%.0f%s[%.0f]%s%.0f@%.0f :: %.0f@%.0f%s[%.0f]%s%.0f@%.0f", + "Urzadzenia: %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%s%s\nTransfer pradow: %.0f@%.0f%s[%.0f]%s%.0f@%.0f :: %.0f@%.0f%s[%.0f]%s%.0f@%.0f", " R", " cisn.oleju: ", "Nastawniki:\n glowny: %d(%d), dodatkowy: %s\nMoc silnika: %.1f, prad silnika: %.0f\nObroty:\n silnik: %.0f, motory: %.0f\n went.silnika: %.0f, went.motorow: %.0f+%.0f, went.chlodnicy: %.0f+%.0f", diff --git a/translation.h b/translation.h index aaa64846..25965ce8 100644 --- a/translation.h +++ b/translation.h @@ -45,6 +45,110 @@ enum string { driver_scenario_obeytrain, driver_scenario_bank, + driver_hint_header, + driver_hint_batteryon, + driver_hint_batteryoff, + driver_hint_radioon, + driver_hint_radiooff, + driver_hint_radiochannel, + driver_hint_oilpumpon, + driver_hint_oilpumpoff, + driver_hint_fuelpumpon, + driver_hint_fuelpumpoff, + driver_hint_pantographairsourcesetmain, + driver_hint_pantographairsourcesetauxiliary, + driver_hint_pantographcompressoron, + driver_hint_pantographcompressoroff, + driver_hint_pantographsvalveon, + driver_hint_pantographsvalveoff, + driver_hint_frontpantographvalveon, + driver_hint_frontpantographvalveoff, + driver_hint_rearpantographvalveon, + driver_hint_rearpantographvalveoff, + driver_hint_converteron, + driver_hint_converteroff, + driver_hint_primaryconverteroverloadreset, + driver_hint_maincircuitgroundreset, + driver_hint_tractionnmotoroverloadreset, + driver_hint_linebreakerclose, + driver_hint_linebreakeropen, + driver_hint_compressoron, + driver_hint_compressoroff, + driver_hint_frontmotorblowerson, + driver_hint_frontmotorblowersoff, + driver_hint_rearmotorblowerson, + driver_hint_rearmotorblowersoff, + driver_hint_springbrakeon, + driver_hint_springbrakeoff, + driver_hint_manualbrakon, + driver_hint_manualbrakoff, + driver_hint_mastercontrollersetidle, + driver_hint_mastercontrollersetseriesmode, + driver_hint_waterheateron, + driver_hint_waterheateroff, + driver_hint_waterheaterbreakeron, + driver_hint_waterheaterbreakeroff, + driver_hint_waterpumpon, + driver_hint_waterpumpoff, + driver_hint_waterpumpbreakeron, + driver_hint_waterpumpbreakeroff, + driver_hint_watercircuitslinkon, + driver_hint_watercircuitslinkoff, + driver_hint_waittemperaturetoolow, + driver_hint_mastercontrollersetzerospeed, + driver_hint_mastercontrollersetreverserunlock, + driver_hint_trainbrakesetpipeunlock, + driver_hint_trainbrakerelease, + driver_hint_trainbrakeapply, + driver_hint_directionforward, + driver_hint_directionbackward, + driver_hint_directionother, + driver_hint_directionnone, + driver_hint_waitpressuretoolow, + driver_hint_waitpantographpressuretoolow, + driver_hint_sandingon, + driver_hint_sandingoff, + driver_hint_consistdoorlockson, + driver_hint_departuresignalon, + driver_hint_departuresignaloff, + driver_hint_doorrightopen, + driver_hint_doorrightclose, + driver_hint_doorleftopen, + driver_hint_doorleftclose, + driver_hint_doorrightpermiton, + driver_hint_doorrightpermitoff, + driver_hint_doorleftpermiton, + driver_hint_doorleftpermitoff, + driver_hint_hornon, + driver_hint_hornoff, + driver_hint_consistlightson, + driver_hint_consistlightsoff, + driver_hint_consistheatingon, + driver_hint_consistheatingoff, + driver_hint_securitysystemreset, + driver_hint_couplingadapterattach, + driver_hint_couplingadapterremove, + driver_hint_secondcontrollersetzero, + driver_hint_tractiveforcedecrease, + driver_hint_tractiveforceincrease, + driver_hint_brakingforcedecrease, + driver_hint_brakingforceincrease, + driver_hint_brakingforcesetzero, + driver_hint_brakingforcelap, + driver_hint_independentbrakeapply, + driver_hint_independentbrakerelease, + driver_hint_antislip, + driver_hint_waitloadexchange, + driver_hint_waitdeparturetime, + driver_hint_headcodepc1, + driver_hint_headcodepc2, + driver_hint_headcodepc5, + driver_hint_headcodetb1, + driver_hint_lightsoff, + driver_hint_releaseron, + driver_hint_releaseroff, + driver_hint_bufferscompress, + driver_timetable_header, driver_timetable_name, driver_timetable_notimetable, diff --git a/uilayer.cpp b/uilayer.cpp index 9d4c6502..c94d01c9 100644 --- a/uilayer.cpp +++ b/uilayer.cpp @@ -117,9 +117,14 @@ ui_layer::init_colors() { auto *colors = style->Colors; auto const background { ImVec4( 38.0f / 255.0f, 38.0f / 255.0f, 38.0f / 255.0f, Global.UIBgOpacity ) }; auto const accent { ImVec4( 44.0f / 255.0f, 88.0f / 255.0f, 72.0f / 255.0f, 0.75f ) }; + auto const darkaccent { ImVec4( 18.0f / 255.0f, 12.0f / 255.0f, 14.0f / 255.0f, 0.75f ) }; auto const itembase { ImVec4( accent.x, accent.y, accent.z, 0.35f ) }; auto const itemhover { ImVec4( accent.x, accent.y, accent.z, 0.65f ) }; auto const itemactive { ImVec4( accent.x, accent.y, accent.z, 0.95f ) }; + auto const trackbase { ImVec4( darkaccent.x, darkaccent.y, darkaccent.z, 0.10f ) }; + auto const thumbbase { ImVec4( darkaccent.x, darkaccent.y, darkaccent.z, 0.45f ) }; + auto const thumbhover { ImVec4( darkaccent.x, darkaccent.y, darkaccent.z, 0.70f ) }; + auto const thumbactive { ImVec4( darkaccent.x, darkaccent.y, darkaccent.z, 0.95f ) }; auto const modalbackground { ImVec4( accent.x, accent.y, accent.z, 0.95f ) }; colors[ ImGuiCol_WindowBg ] = background; @@ -134,6 +139,12 @@ ui_layer::init_colors() { colors[ ImGuiCol_Button ] = itembase; colors[ ImGuiCol_ButtonHovered ] = itemhover; colors[ ImGuiCol_ButtonActive ] = itemactive; + colors[ ImGuiCol_SliderGrab ] = thumbhover; + colors[ ImGuiCol_SliderGrabActive ] = thumbactive; + colors[ ImGuiCol_ScrollbarBg ] = trackbase; + colors[ ImGuiCol_ScrollbarGrab ] = thumbbase; + colors[ ImGuiCol_ScrollbarGrabHovered ] = thumbhover; + colors[ ImGuiCol_ScrollbarGrabActive ] = thumbactive; colors[ ImGuiCol_Header ] = itembase; colors[ ImGuiCol_HeaderHovered ] = itemhover; colors[ ImGuiCol_HeaderActive ] = itemactive; diff --git a/utilities.cpp b/utilities.cpp index 3c7f329a..a2e389d2 100644 --- a/utilities.cpp +++ b/utilities.cpp @@ -461,12 +461,20 @@ len_common_prefix( std::string const &Left, std::string const &Right ) { // returns true if provided string ends with another provided string bool -ends_with( std::string const &String, std::string const &Suffix ) { +ends_with( std::string_view String, std::string_view Suffix ) { return ( String.size() >= Suffix.size() ) && ( 0 == String.compare( String.size() - Suffix.size(), Suffix.size(), Suffix ) ); } +// returns true if provided string begins with another provided string +bool +starts_with( std::string_view const String, std::string_view Prefix ) { + + return ( String.size() >= Prefix.size() ) + && ( 0 == String.compare( 0, Prefix.size(), Prefix ) ); +} + // helper, restores content of a 3d vector from provided input stream // TODO: review and clean up the helper routines, there's likely some redundant ones glm::dvec3 LoadPoint( cParser &Input ) { diff --git a/utilities.h b/utilities.h index fa8c1fea..6d280ab0 100644 --- a/utilities.h +++ b/utilities.h @@ -11,11 +11,7 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" -#include -#include #include -#include -#include /*rozne takie duperele do operacji na stringach w paszczalu, pewnie w delfi sa lepsze*/ /*konwersja zmiennych na stringi, funkcje matematyczne, logiczne, lancuchowe, I/O etc*/ @@ -217,7 +213,9 @@ std::string substr_path( std::string const &Filename ); std::ptrdiff_t len_common_prefix( std::string const &Left, std::string const &Right ); // returns true if provided string ends with another provided string -bool ends_with( std::string const &String, std::string const &Suffix ); +bool ends_with( std::string_view String, std::string_view Suffix ); +// returns true if provided string begins with another provided string +bool starts_with( std::string_view String, std::string_view Suffix ); template void SafeDelete( Type_ &Pointer ) { @@ -231,6 +229,18 @@ void SafeDeleteArray( Type_ &Pointer ) { Pointer = nullptr; } +template +Type_ +is_equal( Type_ const &Left, Type_ const &Right, Type_ const Epsilon = 1e-5 ) { + + if( Epsilon != 0 ) { + return glm::epsilonEqual( Left, Right, Epsilon ); + } + else { + return ( Left == Right ); + } +} + template Type_ clamp( Type_ const Value, Type_ const Min, Type_ const Max ) { diff --git a/version.h b/version.h index ad9cfb85..5c3a3033 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once -#define VERSION_MAJOR 20 -#define VERSION_MINOR 1020 +#define VERSION_MAJOR 21 +#define VERSION_MINOR 303 #define VERSION_REVISION 0 From 4aff0921675f569b245fb7a8579f3e5ac9b065df Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Thu, 4 Mar 2021 23:36:36 +0100 Subject: [PATCH 13/63] minor bug fixes --- Driver.cpp | 178 +++++++++++++++++++++------------------ Driver.h | 6 ++ DynObj.cpp | 219 +++++++++++++++++++++++++++--------------------- DynObj.h | 5 +- Event.cpp | 4 +- driverhints.cpp | 6 +- sound.h | 2 +- translation.cpp | 8 +- version.h | 2 +- 9 files changed, 241 insertions(+), 189 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index f75b71d6..84ec6525 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -37,8 +37,6 @@ http://mozilla.org/MPL/2.0/. #define LOGBACKSCAN 0 #define LOGPRESS 0 -auto const EU07_AI_NOACCELERATION = -0.05; - // finds point of specified track nearest to specified event. returns: distance to that point from the specified end of the track // TODO: move this to file with all generic routines, too easy to forget it's here and it may come useful double @@ -1660,7 +1658,7 @@ TController::braking_distance_multiplier( float const Targetvelocity ) const { if( Targetvelocity > 65.f ) { return 1.f * frictionmultiplier; } if( Targetvelocity < 5.f ) { // HACK: engaged automatic transmission means extra/earlier braking effort is needed for the last leg before full stop - if( ( mvOccupied->TrainType == dt_DMU ) + if( ( is_dmu() ) && ( mvOccupied->Vel < 40.0 ) && ( Targetvelocity == 0.f ) ) { auto const multiplier { clamp( 1.f + iVehicles * 0.5f, 2.f, 4.f ) }; @@ -1814,8 +1812,8 @@ TController::TController(bool AI, TDynamicObject *NewControll, bool InitPsyche, // fAccThreshold może podlegać uczeniu się - hamowanie powinno być rejestrowane, a potem analizowane // próg opóźnienia dla zadziałania hamulca fAccThreshold = ( - mvOccupied->TrainType == dt_EZT ? -0.55 : - mvOccupied->TrainType == dt_DMU ? -0.45 : + is_emu() ? -0.55 : + is_dmu() ? -0.45 : -0.2 ); /* // HACK: emu with induction motors need to start their braking a bit sooner than the ones with series motors @@ -2181,11 +2179,11 @@ void TController::AutoRewident() if( OrderCurrentGet() & ( Shunt | Loose_shunt ) ) { // for uniform behaviour and compatibility with older scenarios set default acceleration table values for shunting fAccThreshold = ( - mvOccupied->TrainType == dt_EZT ? -0.55 : - mvOccupied->TrainType == dt_DMU ? -0.45 : + is_emu() ? -0.55 : + is_dmu() ? -0.45 : -0.2 ); // HACK: emu with induction motors need to start their braking a bit sooner than the ones with series motors - if( ( mvOccupied->TrainType == dt_EZT ) + if( ( is_emu() ) && ( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) ) { fAccThreshold += 0.10; } @@ -2211,8 +2209,8 @@ void TController::AutoRewident() fBrake_a1[i+1] /= (12*fMass); } - IsPassengerTrain = ( mvOccupied->CategoryFlag == 1 ) && ( mvOccupied->TrainType != dt_EZT ) && ( mvOccupied->TrainType != dt_DMU ) && ( ( mvOccupied->BrakeDelayFlag & bdelay_G ) == 0 ); - IsCargoTrain = ( mvOccupied->CategoryFlag == 1 ) && ( ( mvOccupied->BrakeDelayFlag & bdelay_G ) != 0 ); + IsPassengerTrain = ( is_train() ) && ( false == is_emu() ) && ( false == is_dmu() ) && ( ( mvOccupied->BrakeDelayFlag & bdelay_G ) == 0 ); + IsCargoTrain = ( is_train() ) && ( ( mvOccupied->BrakeDelayFlag & bdelay_G ) != 0 ); IsHeavyCargoTrain = ( true == IsCargoTrain ) && ( fBrake_a0[ 1 ] > 0.4 ) && ( iVehicles - ControlledEnginesCount > 0 ) && ( fMass / iVehicles > 50000 ); BrakingInitialLevel = ( @@ -2225,7 +2223,7 @@ void TController::AutoRewident() IsCargoTrain ? 0.25 : 0.25 ); - if( mvOccupied->TrainType == dt_EZT ) { + if( is_emu() ) { if( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) { // HACK: emu with induction motors need to start their braking a bit sooner than the ones with series motors fNominalAccThreshold = std::max( -0.60, -fBrake_a0[ BrakeAccTableSize ] - 8 * fBrake_a1[ BrakeAccTableSize ] ); @@ -2235,7 +2233,7 @@ void TController::AutoRewident() } fBrakeReaction = 0.25; } - else if( mvOccupied->TrainType == dt_DMU ) { + else if( is_dmu() ) { fNominalAccThreshold = std::max( -0.75, -fBrake_a0[ BrakeAccTableSize ] - 8 * fBrake_a1[ BrakeAccTableSize ] ); fBrakeReaction = 0.25; } @@ -2421,37 +2419,39 @@ bool TController::CheckVehicles(TOrders user) if( mvOccupied->LightsPosNo > 0 ) { pVehicle->SetLights(); } - // potentially adjust light state - control_lights(); - // custom ai action for disconnect mode: switch off lights on disconnected vehicle(s) - if( is_train() ) { - if( true == TestFlag( OrderCurrentGet(), Disconnect ) ) { - if( AIControllFlag ) { - // światła manewrowe (Tb1) tylko z przodu, aby nie pozostawić odczepionego ze światłem - if( mvOccupied->DirActive >= 0 ) { // jak ma kierunek do przodu - pVehicles[ end::rear ]->RaLightsSet( -1, 0 ); - } - else { // jak dociska - pVehicles[ end::front ]->RaLightsSet( 0, -1 ); + if( iEngineActive ) { + // potentially adjust light state + control_lights(); + // custom ai action for disconnect mode: switch off lights on disconnected vehicle(s) + if( is_train() ) { + if( true == TestFlag( OrderCurrentGet(), Disconnect ) ) { + if( AIControllFlag ) { + // światła manewrowe (Tb1) tylko z przodu, aby nie pozostawić odczepionego ze światłem + if( mvOccupied->DirActive >= 0 ) { // jak ma kierunek do przodu + pVehicles[ end::rear ]->RaLightsSet( -1, 0 ); + } + else { // jak dociska + pVehicles[ end::front ]->RaLightsSet( 0, -1 ); + } } } } - } - // enable door locks - cue_action( locale::string::driver_hint_consistdoorlockson ); - // potentially enable train heating - { - // HACK: to account for su-45/46 shortcomings diesel-powered engines only activate heating in cold conditions - // TODO: take instead into account presence of converters in attached cars, once said presence is possible to specify - auto const isheatingneeded { ( - IsCargoTrain ? false : - has_diesel_engine() ? ( Global.AirTemperature < 10 ) : - true ) }; - if( mvControlling->HeatingAllow != isheatingneeded ) { - cue_action( - isheatingneeded ? - locale::string::driver_hint_consistheatingon : - locale::string::driver_hint_consistheatingoff ); + // enable door locks + cue_action( locale::string::driver_hint_consistdoorlockson ); + // potentially enable train heating + { + // HACK: to account for su-45/46 shortcomings diesel-powered engines only activate heating in cold conditions + // TODO: take instead into account presence of converters in attached cars, once said presence is possible to specify + auto const isheatingneeded{ ( + IsCargoTrain ? false : + has_diesel_engine() ? ( Global.AirTemperature < 10 ) : + true ) }; + if( mvControlling->HeatingAllow != isheatingneeded ) { + cue_action( + isheatingneeded ? + locale::string::driver_hint_consistheatingon : + locale::string::driver_hint_consistheatingoff ); + } } } if( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) { @@ -2469,8 +2469,8 @@ bool TController::CheckVehicles(TOrders user) } // Ra 2014-09: tymczasowo prymitywne ustawienie warunku pod kątem SN61 - if( ( mvOccupied->TrainType == dt_EZT ) - || ( mvOccupied->TrainType == dt_DMU ) + if( ( is_emu() ) + || ( is_dmu() ) || ( iVehicles == 1 ) ) { // zmiana czoła przez zmianę kabiny iDrivigFlags |= movePushPull; @@ -2539,7 +2539,7 @@ int TController::OrderDirectionChange(int newdir, TMoverParameters *Vehicle) IncBrake(); // niech hamuje if (Vehicle->DirActive == testd * Vehicle->CabActive) VelforDriver = -1; // można jechać, bo kierunek jest zgodny z żądanym - if (Vehicle->TrainType == dt_EZT) + if (is_emu()) if (Vehicle->DirActive > 0) // if () //tylko jeśli jazda pociągowa (tego nie wiemy w momencie odpalania silnika) Vehicle->DirectionForward(); // Ra: z przekazaniem do silnikowego @@ -2642,7 +2642,13 @@ bool TController::PrepareEngine() if( ( mvPantographUnit->EnginePowerSource.SourceType == TPowerSource::CurrentCollector ) && ( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 0 ) ) { // if our pantograph unit isn't a pantograph-devoid fallback - if (mvPantographUnit->PantPress < 4.2) { + auto const sufficientpantographpressure { ( + is_emu() ? + 2.5 : // Ra 2013-12: Niebugocław mówi, że w EZT podnoszą się przy 2.5 + 3.5 ) + + 0.1 }; // leeway margin + + if (mvPantographUnit->PantPress < sufficientpantographpressure) { // załączenie małej sprężarki if( false == mvPantographUnit->PantAutoValve ) { // odłączenie zbiornika głównego, bo z nim nie da rady napompować @@ -2715,10 +2721,6 @@ bool TController::PrepareEngine() cue_action( locale::string::driver_hint_rearmotorblowerson ); } } - // set up train brake - if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_RP ) ) { - cue_action( locale::string::driver_hint_trainbrakerelease ); - } // sync virtual brake state with the 'real' one if( mvOccupied->BrakeHandle != TBrakeHandle::NoHandle ) { std::unordered_map const brakepositions{ @@ -2730,6 +2732,10 @@ bool TController::PrepareEngine() BrakeLevelSet( lookup->second ); // GBH } } + // set up train brake + if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_RP ) ) { + cue_action( locale::string::driver_hint_trainbrakerelease ); + } // sync spring brake state across consist cue_action( mvOccupied->SpringBrake.Activate ? @@ -2885,7 +2891,7 @@ bool TController::IncBrake() standalone = false; } } - else if( mvOccupied->TrainType == dt_DMU ) { + else if( is_dmu() ) { // enforce use of train brake for DMUs standalone = false; } @@ -2964,7 +2970,7 @@ bool TController::IncBrake() if( /*GBH mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition < 0.1 ) { OK = /*mvOccupied->*/BrakeLevelAdd( BrakingInitialLevel ); //GBH // HACK: stronger braking to overcome SA134 engine behaviour - if( ( mvOccupied->TrainType == dt_DMU ) + if( ( is_dmu() ) && ( VelNext == 0.0 ) && ( fBrakeDist < 200.0 ) ) { BrakeLevelAdd( @@ -3099,7 +3105,7 @@ bool TController::DecBrake() { auto deltaAcc { -1.0 }; if( ( fBrake_a0[ 0 ] != 0.0 ) || ( fBrake_a1[ 0 ] != 0.0 ) ) { - auto const pos_diff { ( mvOccupied->TrainType == dt_DMU ? 0.25 : 1.0 ) }; + auto const pos_diff { ( is_dmu() ? 0.25 : 1.0 ) }; deltaAcc = -AccDesired * BrakeAccFactor() - ( fBrake_a0[ 0 ] + 4 * (/*GBH mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition - pos_diff )*fBrake_a1[ 0 ] ); } if (deltaAcc < 0) { @@ -4752,17 +4758,19 @@ TController::Update( double const Timedelta ) { scan_route( awarenessrange ); scan_obstacles( awarenessrange ); // generic actions - control_wheelslip(); control_security_system( reactiontime ); - control_horns( reactiontime ); - control_pantographs(); - CheckTimeControllers(); - if( fActionTime > 0.0 ) { - // potentially delayed and/or low priority actions - control_lights(); - control_doors(); - control_compartment_lights(); + if( iEngineActive ) { + control_wheelslip(); + control_horns( reactiontime ); + control_pantographs(); + if( fActionTime > 0.0 ) { + // potentially delayed and/or low priority actions + control_lights(); + control_doors(); + control_compartment_lights(); + } } + CheckTimeControllers(); // external command actions UpdateCommand(); // mode specific actions @@ -4770,9 +4778,11 @@ TController::Update( double const Timedelta ) { handle_orders(); // situational velocity and acceleration adjustments pick_optimal_speed( awarenessrange ); - control_tractive_and_braking_force(); + if( iEngineActive ) { + control_tractive_and_braking_force(); + } SetTimeControllers(); - // if the route ahead is blocked we might need to head the other way +// if the route ahead is blocked we might need to head the other way check_route_behind( 1000 ); // NOTE: legacy scan range value } @@ -5531,7 +5541,7 @@ void TController::sync_consist_reversers() { auto const currentdirection { mvOccupied->DirActive }; auto const fastforward { ( - ( mvOccupied->TrainType == dt_EZT ) + ( is_emu() ) && ( mvOccupied->EngineType != TEngineType::ElectricInductionMotor ) ) && ( mvOccupied->Imin == mvOccupied->IminHi ) }; @@ -5749,7 +5759,7 @@ TController::determine_consist_state() { fBrake_a0[0] = fBrake_a0[index]; fBrake_a1[0] = fBrake_a1[index]; - if ((mvOccupied->TrainType == dt_EZT) || (mvOccupied->TrainType == dt_DMU)) { + if ((is_emu()) || (is_dmu())) { auto Coeff = clamp( mvOccupied->Vel*0.015 , 0.5 , 1.0); fAccThreshold = fNominalAccThreshold * Coeff - fBrake_a0[BrakeAccTableSize] * (1.0 - Coeff); } @@ -5895,6 +5905,12 @@ TController::determine_consist_state() { } p = p->Next(); // pojazd podłączony z tyłu (patrząc od czoła) } + // test consist state for external events and/or weird things human user could've done + iEngineActive &= + ( false == IsAnyConverterOverloadRelayOpen ) + && ( false == IsAnyLineBreakerOpen ) + && ( true == IsAnyConverterEnabled ) + && ( true == IsAnyCompressorEnabled ); } void @@ -5939,7 +5955,7 @@ TController::control_pantographs() { // raise/lower pantographs as needed auto const useregularpantographlayout { ( pVehicle->NextC( coupling::control ) == nullptr ) // standalone - || ( mvControlling->TrainType == dt_EZT ) // special case + || ( is_emu() ) // special case || ( mvControlling->TrainType == dt_ET41 ) }; // special case if( mvOccupied->Vel > 0.05 ) { @@ -6080,6 +6096,10 @@ TController::control_security_system( double const Timedelta ) { if( mvOccupied->SecuritySystem.Status >= s_aware ) { // jak zadziałało CA/SHP + if( ( false == is_emu() ) + && ( mvOccupied->DirActive == 0 ) ) { + cue_action( locale::string::driver_hint_directionforward ); + } cue_action( locale::string::driver_hint_securitysystemreset ); // to skasuj if( ( BrakeCtrlPosition == 0 ) // TODO: verify whether it's 0 in all vehicle types && ( AccDesired > 0.0 ) @@ -6100,7 +6120,8 @@ TController::control_security_system( double const Timedelta ) { m_radiocontroltime += Timedelta; } } - if( ( false == mvOccupied->Radio ) + if( ( iEngineActive ) + && ( false == mvOccupied->Radio ) && ( false == mvOccupied->RadioStopFlag ) ) { // otherwise if it's safe to do so, turn the radio back on // arbitrary 5 sec delay before switching radio back on @@ -6249,13 +6270,12 @@ TController::control_doors() { void TController::UpdateCommand() { - // TBD, TODO: rework recognizecommand() to use hint system, remove driver type condition? - if (AIControllFlag) { - if (mvOccupied->CommandIn.Command != "") - if( !mvOccupied->RunInternalCommand() ) { - // rozpoznaj komende bo lokomotywa jej nie rozpoznaje - RecognizeCommand(); // samo czyta komendę wstawioną do pojazdu? - } + + if( false == mvOccupied->CommandIn.Command.empty() ) { + if( false == mvOccupied->RunInternalCommand() ) { + // rozpoznaj komende bo lokomotywa jej nie rozpoznaje + RecognizeCommand(); // samo czyta komendę wstawioną do pojazdu? + } } } @@ -6294,7 +6314,7 @@ TController::determine_braking_distance() { } if( ( mvOccupied->Vel > 15.0 ) && ( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) - && ( ( mvControlling->TrainType & dt_EZT ) != 0 ) ) { + && ( is_emu() ) ) { // HACK: make the induction motor powered EMUs start braking slightly earlier fBrakeDist += 10.0; } @@ -6805,9 +6825,7 @@ TController::handle_engine() { } // engine state can potentially deteriorate in one of usual driving modes if( ( OrderCurrentGet() & ( Change_direction | Connect | Disconnect | Shunt | Loose_shunt | Obey_train | Bank ) ) - && ( ( false == iEngineActive ) - || ( IsAnyConverterOverloadRelayOpen ) // wywalił bezpiecznik nadmiarowy przetwornicy - || ( IsAnyLineBreakerOpen ) ) ) { // WS może wywalić z powodu błędu w drutach + && ( false == iEngineActive ) ) { // jeśli coś ma robić to niech odpala do skutku PrepareEngine(); } @@ -6877,7 +6895,7 @@ TController::pick_optimal_speed( double const Range ) { if( false == TestFlag( iDrivigFlags, moveActive ) ) { VelDesired = 0.0; AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION ); - return; +// return; } // basic velocity and acceleration adjustments @@ -7462,7 +7480,7 @@ void TController::control_motor_connectors() { // ensure motor connectors are active if( ( mvControlling->EngineType == TEngineType::ElectricSeriesMotor ) || ( mvControlling->EngineType == TEngineType::DieselElectric ) - || ( mvControlling->TrainType == dt_EZT ) ) { + || ( is_emu() ) ) { if( Need_TryAgain ) { // true, jeśli druga pozycja w elektryku nie załapała cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); @@ -7555,7 +7573,7 @@ void TController::control_braking_force() { } } - if( mvOccupied->TrainType == dt_EZT ) { + if( is_emu() ) { // właściwie, to warunek powinien być na działający EP // Ra: to dobrze hamuje EP w EZT auto const accthreshold { ( diff --git a/Driver.h b/Driver.h index b45ab861..e0011e6c 100644 --- a/Driver.h +++ b/Driver.h @@ -17,6 +17,8 @@ http://mozilla.org/MPL/2.0/. #include "mtable.h" #include "translation.h" +auto const EU07_AI_NOACCELERATION = -0.05; + enum TOrders { // rozkazy dla AI Wait_for_orders = 0, // czekanie na dostarczenie następnych rozkazów @@ -230,6 +232,10 @@ public: return TestFlag( mvOccupied->CategoryFlag, 1 ); } bool is_car() const { return TestFlag( mvOccupied->CategoryFlag, 2 ); } + bool is_emu() const { + return ( mvControlling->TrainType == dt_EZT ); } + bool is_dmu() const { + return ( mvControlling->TrainType == dt_DMU ); } bool has_diesel_engine() const { return ( ( mvControlling->EngineType == TEngineType::DieselElectric ) || ( mvControlling->EngineType == TEngineType::DieselEngine ) ); diff --git a/DynObj.cpp b/DynObj.cpp index ad606f0b..0774401e 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -5476,32 +5476,56 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co m_powertrainsounds.water_heater.owner( this ); } - else if( ( ( token == "tractionmotor:" ) || ( token == "tractionacmotor:" ) ) - && ( MoverParameters->Power > 0 ) ) { - // dc motors are (legacy) default - m_powertrainsounds.dcmotors = ( token == "tractionmotor:" ); + else if( ( token == "tractionmotor:" ) && ( MoverParameters->Power > 0 ) ) { // plik z dzwiekiem silnika, mnozniki i ofsety amp. i czest. sound_source motortemplate { sound_placement::external }; motortemplate.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); + auto const amplitudedivisor { static_cast( MoverParameters->nmax * 60 + MoverParameters->Power * 3 ) }; + motortemplate.m_amplitudefactor /= amplitudedivisor; motortemplate.owner( this ); - if( m_powertrainsounds.dcmotors ) { - auto const amplitudedivisor = static_cast( MoverParameters->nmax * 60 + MoverParameters->Power * 3 ); - motortemplate.m_amplitudefactor /= amplitudedivisor; - } + auto &motors { m_powertrainsounds.motors }; - if( true == m_powertrainsounds.motors.empty() ) { + if( true == motors.empty() ) { // fallback for cases without specified motor locations, convert sound template to a single sound source - m_powertrainsounds.motors.emplace_back( motortemplate ); + motors.emplace_back( motortemplate ); } else { // apply configuration to all defined motors - for( auto &motor : m_powertrainsounds.motors ) { + for( auto &motor : motors ) { // combine potential x- and y-axis offsets of the sound template with z-axis offsets of individual motors auto motoroffset { motortemplate.offset() }; motoroffset.z = motor.offset().z; motor = motortemplate; motor.offset( motoroffset ); + // apply randomized playback start offset for each instance, to reduce potential reverb with identical nearby sources + motor.start( LocalRandom( 0.0, 1.0 ) ); + } + } + } + + else if( ( token == "tractionacmotor:" ) && ( MoverParameters->Power > 0 ) ) { + // plik z dzwiekiem silnika, mnozniki i ofsety amp. i czest. + sound_source motortemplate { sound_placement::external }; + motortemplate.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); + motortemplate.owner( this ); + + auto &motors { m_powertrainsounds.acmotors }; + + if( true == motors.empty() ) { + // fallback for cases without specified motor locations, convert sound template to a single sound source + motors.emplace_back( motortemplate ); + } + else { + // apply configuration to all defined motors + for( auto &motor : motors ) { + // combine potential x- and y-axis offsets of the sound template with z-axis offsets of individual motors + auto motoroffset { motortemplate.offset() }; + motoroffset.z = motor.offset().z; + motor = motortemplate; + motor.offset( motoroffset ); + // apply randomized playback start offset for each instance, to reduce potential reverb with identical nearby sources + motor.start( LocalRandom( 0.0, 1.0 ) ); } } } @@ -5531,6 +5555,8 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co bloweroffset.z = blower.offset().z; blower = blowertemplate; blower.offset( bloweroffset ); + // apply randomized playback start offset for each instance, to reduce potential reverb with identical nearby sources + blower.start( LocalRandom( 0.0, 1.0 ) ); } } } @@ -7587,113 +7613,112 @@ TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, doub // motor sounds volume = 0.0; + // generic traction motor sounds if( false == motors.empty() ) { - // ac and dc motors have significantly different sounds - if( dcmotors ) { - if( std::abs( Vehicle.nrot ) > 0.01 ) { + if( std::abs( Vehicle.nrot ) > 0.01 ) { - auto const &motor { motors.front() }; - // frequency calculation - auto normalizer { 1.f }; - if( true == motor.is_combined() ) { - // for combined motor sound we calculate sound point in rpm, to make .mmd files setup easier - // NOTE: we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range - normalizer = 60.f * 0.01f; + auto const &motor { motors.front() }; + // frequency calculation + auto normalizer { 1.f }; + if( true == motor.is_combined() ) { + // for combined motor sound we calculate sound point in rpm, to make .mmd files setup easier + // NOTE: we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range + normalizer = 60.f * 0.01f; + } + auto const motorrevolutions { std::abs( Vehicle.nrot ) * Vehicle.Transmision.Ratio }; + frequency = + motor.m_frequencyoffset + + motor.m_frequencyfactor * motorrevolutions * normalizer; + + // base volume calculation + switch( Vehicle.EngineType ) { + case TEngineType::ElectricInductionMotor: { + volume = + motor.m_amplitudeoffset + + motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 2 ); + break; } - auto const motorrevolutions { std::abs( Vehicle.nrot ) * Vehicle.Transmision.Ratio }; - frequency = - motor.m_frequencyoffset - + motor.m_frequencyfactor * motorrevolutions * normalizer; + case TEngineType::ElectricSeriesMotor: { + volume = + motor.m_amplitudeoffset + + motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 60.0 ); + break; + } + default: { + volume = + motor.m_amplitudeoffset + + motor.m_amplitudefactor * motorrevolutions * 60.0; + break; + } + } - // base volume calculation - switch( Vehicle.EngineType ) { - case TEngineType::ElectricInductionMotor: { - volume = - motor.m_amplitudeoffset - + motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 2 ); - break; - } - case TEngineType::ElectricSeriesMotor: { - volume = - motor.m_amplitudeoffset - + motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 60.0 ); - break; - } - default: { - volume = - motor.m_amplitudeoffset - + motor.m_amplitudefactor * motorrevolutions * 60.0; - break; + if( Vehicle.EngineType == TEngineType::ElectricSeriesMotor ) { + // volume variation + if( ( volume < 1.0 ) + && ( Vehicle.EnginePower < 100 ) ) { + + auto const volumevariation { LocalRandom( 100 ) * Vehicle.enrot / ( 1 + Vehicle.nmax ) }; + if( volumevariation < 2 ) { + volume += volumevariation / 200; } } - if( Vehicle.EngineType == TEngineType::ElectricSeriesMotor ) { - // volume variation - if( ( volume < 1.0 ) - && ( Vehicle.EnginePower < 100 ) ) { - - auto const volumevariation { LocalRandom( 100 ) * Vehicle.enrot / ( 1 + Vehicle.nmax ) }; - if( volumevariation < 2 ) { - volume += volumevariation / 200; - } - } - - if( ( Vehicle.DynamicBrakeFlag ) - && ( Vehicle.EnginePower > 0.1 ) ) { - // Szociu - 29012012 - jeżeli uruchomiony jest hamulec elektrodynamiczny, odtwarzany jest dźwięk silnika - volume += 0.8; - } + if( ( Vehicle.DynamicBrakeFlag ) + && ( Vehicle.EnginePower > 0.1 ) ) { + // Szociu - 29012012 - jeżeli uruchomiony jest hamulec elektrodynamiczny, odtwarzany jest dźwięk silnika + volume += 0.8; } - // scale motor volume based on whether they're active - motor_momentum = - clamp( - motor_momentum - - 1.0 * Deltatime // smooth out decay - + std::abs( Vehicle.Mm ) / 60.0 * Deltatime, - 0.0, 1.25 ); - volume *= std::max( 0.25f, motor_momentum ); - motor_volume = interpolate( motor_volume, volume, 0.25 ); - if( motor_volume >= 0.05 ) { - // apply calculated parameters to all motor instances - for( auto &motor : motors ) { - motor - .pitch( frequency ) - .gain( motor_volume ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - } - else { - // stop all motor instances - for( auto &motor : motors ) { - motor.stop(); - } + } + // scale motor volume based on whether they're active + motor_momentum = + clamp( + motor_momentum + - 1.0 * Deltatime // smooth out decay + + std::abs( Vehicle.Mm ) / 60.0 * Deltatime, + 0.0, 1.25 ); + volume *= std::max( 0.25f, motor_momentum ); + motor_volume = interpolate( motor_volume, volume, 0.25 ); + if( motor_volume >= 0.05 ) { + // apply calculated parameters to all motor instances + for( auto &motor : motors ) { + motor + .pitch( frequency ) + .gain( motor_volume ) + .play( sound_flags::exclusive | sound_flags::looping ); } } else { // stop all motor instances - motor_volume = 0.0; for( auto &motor : motors ) { motor.stop(); } } } else { - // ac motors - if( Vehicle.EngineType == TEngineType::ElectricInductionMotor ) { - if( Vehicle.InverterFrequency > 0.001 ) { + // stop all motor instances + motor_volume = 0.0; + for( auto &motor : motors ) { + motor.stop(); + } + } + } + // inverter-specific traction motor sounds + if( false == acmotors.empty() ) { + + if( Vehicle.EngineType == TEngineType::ElectricInductionMotor ) { + if( Vehicle.InverterFrequency > 0.001 ) { - for( auto &motor : motors ) { - motor - .pitch( motor.m_frequencyoffset + motor.m_frequencyfactor * Vehicle.InverterFrequency ) - .gain( motor.m_amplitudeoffset + motor.m_amplitudefactor * std::sqrt( std::abs( Vehicle.eimv_pr ) ) ) - .play( sound_flags::exclusive | sound_flags::looping ); - } + for( auto &motor : acmotors ) { + motor + .pitch( motor.m_frequencyoffset + motor.m_frequencyfactor * Vehicle.InverterFrequency ) + .gain( motor.m_amplitudeoffset + motor.m_amplitudefactor * std::sqrt( std::abs( Vehicle.eimv_pr ) ) ) + .play( sound_flags::exclusive | sound_flags::looping ); } - else { - for( auto &motor : motors ) { - motor.stop(); - } + } + else { + for( auto &motor : acmotors ) { + motor.stop(); } } } diff --git a/DynObj.h b/DynObj.h index 79ba289e..c77288df 100644 --- a/DynObj.h +++ b/DynObj.h @@ -373,8 +373,9 @@ private: struct powertrain_sounds { sound_source inverter { sound_placement::engine }; std::vector motorblowers; - std::vector motors; // generally traction motor(s) - bool dcmotors { true }; // traction dc motor(s) + std::vector motors; // generic traction motor sounds + std::vector acmotors; // inverter-specific traction motor sounds +// bool dcmotors { true }; // traction dc motor(s) double motor_volume { 0.0 }; // MC: pomocnicze zeby gladziej silnik buczal float motor_momentum { 0.f }; // recent change in motor revolutions sound_source motor_relay { sound_placement::engine }; diff --git a/Event.cpp b/Event.cpp index 51991a20..3d8324f2 100644 --- a/Event.cpp +++ b/Event.cpp @@ -829,8 +829,8 @@ putvalues_event::export_as_text_( std::ostream &Output ) const { bool putvalues_event::is_command_for_owner( input_data const &Input ) const { - if( Input.data_text.rfind( "Load=", 0 ) == std::string::npos ) { return false; } - if( Input.data_text.rfind( "UnLoad=", 0 ) == std::string::npos ) { return false; } + if( starts_with( Input.data_text, "Load=" ) ) { return false; } + if( starts_with( Input.data_text, "UnLoad=" ) ) { return false; } // TBD, TODO: add other exceptions return true; diff --git a/driverhints.cpp b/driverhints.cpp index d7d121aa..92946ff9 100644 --- a/driverhints.cpp +++ b/driverhints.cpp @@ -550,7 +550,8 @@ TController::cue_action( locale::string const Action, float const Actionparamete hint( Action, [this](float const Parameter) -> bool { - return ( ( AccDesired <= -0.06 ) + return ( ( AccDesired <= EU07_AI_NOACCELERATION ) + || ( ( false == Ready ) && ( false == mvOccupied->ShuntMode ) ) || ( AccDesired - AbsAccS <= 0.05 ) || ( mvOccupied->EIMCtrlType > 0 ? ( mvControlling->eimic_real >= 1.0 ) : @@ -1250,7 +1251,8 @@ TController::cue_action( locale::string const Action, float const Actionparamete break; } case locale::string::driver_hint_headcodepc5: { - if( AIControllFlag || Global.AITrainman ) { + if( ( AIControllFlag ) + || ( Global.AITrainman && ( false == pVehicles[ end::rear ]->is_connected( pVehicle, coupling::control ) ) ) ) { pVehicles[ end::rear ]->RaLightsSet( -1, light::redmarker_left | light::redmarker_right | light::rearendsignals ); } remove_hint( locale::string::driver_hint_headcodetb1 ); diff --git a/sound.h b/sound.h index c08545fb..9a1f01fa 100644 --- a/sound.h +++ b/sound.h @@ -28,7 +28,7 @@ enum class sound_type { enum sound_parameters { range = 0x1, amplitude = 0x2, - frequency = 0x4 + frequency = 0x4, }; enum sound_flags { diff --git a/translation.cpp b/translation.cpp index 09894da6..1f4a0ef5 100644 --- a/translation.cpp +++ b/translation.cpp @@ -441,10 +441,10 @@ init() { "Cofnac zezwolenie na otwarcie drzwi", "Podac sygnal dzwiekowy", "Wylaczyc syrene", - "Zalaczyc oswietlenie skladu", - "Wylaczyc oswietlenie skladu", - "Zalaczyc ogrzewanie skladu", - "wylaczyc ogrzewanie skladu", + "Zalaczyc oswietlenie pociagu", + "Wylaczyc oswietlenie pociagu", + "Zalaczyc ogrzewanie pociagu", + "wylaczyc ogrzewanie pociagu", "Zbic czuwak", "Zalozyc polsprzeg", "Zdjac polsprzeg", diff --git a/version.h b/version.h index 5c3a3033..9c765115 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 303 +#define VERSION_MINOR 304 #define VERSION_REVISION 0 From 83606bc6ca7d0a76ce02c79a1001ccb839d374a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 31 Oct 2020 19:58:25 +0100 Subject: [PATCH 14/63] EIM vehicle can have multiple inverters --- DynObj.cpp | 1 + McZapkie/MOVER.h | 13 +++++++++++++ McZapkie/Mover.cpp | 42 +++++++++++++++++++++++++++++++++++++----- Train.cpp | 18 +++++++++++++++++- 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/DynObj.cpp b/DynObj.cpp index 0774401e..fe6d6ebd 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3089,6 +3089,7 @@ bool TDynamicObject::Update(double dt, double dt1) p->MoverParameters->MED_Vref) * 1000; // sila hamowania pn FmaxED += ((p->MoverParameters->Mains) && (p->MoverParameters->DirActive != 0) && + (p->MoverParameters->InvertersRatio == 1.0) && (p->MoverParameters->eimc[eimc_p_Fh] * p->MoverParameters->NPoweredAxles > 0) ? p->MoverParameters->eimc[eimc_p_Fh] * 1000 : diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index cf89d19f..68b26333 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -769,6 +769,16 @@ struct speed_control { double PowerDownSpeed = 1000; }; +struct inverter { + double Freal = 0.0; + double Request = 0.0; + bool IsActive = true; + bool Activate = true; + bool Error = false; + bool Failure_Drive = false; + bool Failure_Const = false; +}; + class TMoverParameters { // Ra: wrapper na kod pascalowy, przejmujący jego funkcje Q: 20160824 - juz nie wrapper a klasa bazowa :) private: @@ -1270,6 +1280,9 @@ public: bool EIMCLogForce = false; // static std::vector const eimc_labels; double InverterFrequency { 0.0 }; // current frequency of power inverters + int InvertersNo = 0; // number of inverters + double InvertersRatio = 0.0; + std::vector Inverters; //all inverters /* -dla pojazdów z blendingiem EP/ED (MED) */ double MED_Vmax = 0; // predkosc maksymalna dla obliczen chwilowej sily hamowania EP w MED double MED_Vmin = 0; // predkosc minimalna dla obliczen chwilowej sily hamowania EP w MED diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 2cec66eb..be41d0e0 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -5932,6 +5932,12 @@ double TMoverParameters::TractionForce( double dt ) { case TEngineType::ElectricInductionMotor: { if( true == Mains ) { + double ActiveInverters = 0.0; + for (auto &inv : Inverters) { + if (inv.IsActive) + ActiveInverters += 1.0; + } + InvertersRatio = ActiveInverters / (double)InvertersNo; //tempomat if (ScndCtrlPosNo == 4 && SpeedCtrlTypeTime) { @@ -6027,7 +6033,7 @@ double TMoverParameters::TractionForce( double dt ) { PosRatio *= 0.9; Hamulec->SetED(Max0R(0.0, std::min(PosRatio, 1.0))); //ustalenie stopnia zmniejszenia ciśnienia // ustalanie siły hamowania ED - if ((Hamulec->GetEDBCP() > 0.25) && (eimc[eimc_p_abed] < 0.001)) //jeśli PN wyłącza ED + if ((Hamulec->GetEDBCP() > 0.25) && (eimc[eimc_p_abed] < 0.001) || (ActiveInverters < InvertersNo)) //jeśli PN wyłącza ED { PosRatio = 0; eimv[eimv_Fzad] = 0; @@ -6073,6 +6079,11 @@ double TMoverParameters::TractionForce( double dt ) { // switch sandbox off SandboxAuto( false, range_t::unit ); } + if (ActiveInverters == 0.0) + { + PosRatio = 0; + eimv_pr = 0; + } eimv_pr += Max0R(Min0R(PosRatio - eimv_pr, 0.02), -0.02) * 12 * (tmp /*2{+4*byte(PosRatio 0 && Power > 0 && InvertersNo == 0) { + InvertersNo = 1; + } + Inverters.resize(InvertersNo); + /*for (int i = 0; i > InvertersNo; i++) + { + inverter x; + Inverters.emplace_back(x); + }*/ break; } default: { diff --git a/Train.cpp b/Train.cpp index 413b0478..aaf49b10 100644 --- a/Train.cpp +++ b/Train.cpp @@ -645,8 +645,24 @@ dictionary_source *TTrain::GetTrainState() { dict->insert( ( "eimp_u" + std::to_string( i + 1 ) + "_comp_a" ), bComp[ i ][ 0 ] ); dict->insert( ( "eimp_u" + std::to_string( i + 1 ) + "_comp_w" ), bComp[ i ][ 1 ] ); dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_heat" ), bHeat[ i ] ); - } + + bool kier = (DynamicObject->DirectionGet() * mvOccupied->CabOccupied > 0); + TDynamicObject *p = DynamicObject->GetFirstDynamic(mvOccupied->CabOccupied < 0 ? end::rear : end::front, 4); + int in = 0; + while (p && in < 8) + { + if (p->MoverParameters->eimc[eimc_p_Pmax] > 1) + { + in++; + for (int j = 0; j < p->MoverParameters->InvertersNo; j++) { + dict->insert(("eimp_c" + std::to_string(in) + "_inv" + std::to_string(j + 1) + "_act"), p->MoverParameters->Inverters[j].IsActive); + dict->insert(("eimp_c" + std::to_string(in) + "_inv" + std::to_string(j + 1) + "_error"), p->MoverParameters->Inverters[j].Error); + dict->insert(("eimp_c" + std::to_string(in) + "_inv" + std::to_string(j + 1) + "_allow"), p->MoverParameters->Inverters[j].Activate); + } + } + p = (kier ? p->NextC(4) : p->PrevC(4)); + } for( int i = 0; i < 20; ++i ) { for( int j = 0; j < 4; ++j ) { dict->insert( ( "eimp_pn" + std::to_string( i + 1 ) + "_" + TXTP[ j ] ), fPress[ i ][ j ] ); From 6582370484c8925310226f52f31e87a7088a518b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 31 Oct 2020 22:42:21 +0100 Subject: [PATCH 15/63] Better calculation when slipping with disabled inverters --- McZapkie/Mover.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index be41d0e0..9e4bfb7e 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -4813,6 +4813,11 @@ void TMoverParameters::ComputeTotalForce(double dt) { Power > 0 ? TractionForce( dt ) : 0 ); + double FT_factor = 1.0; + if (EngineType == TEngineType::ElectricInductionMotor && InvertersRatio > 0.0) { + FT_factor = 1.0 / InvertersRatio; + FTrain *= FT_factor; + } Fb = BrakeForce(RunningTrack); // poslizg @@ -4867,6 +4872,7 @@ void TMoverParameters::ComputeTotalForce(double dt) { FStand += Fb; // doliczenie składowej stycznej grawitacji + FTrain /= FT_factor; FTrain += TotalMassxg * RunningShape.dHtrack; //!niejawne przypisanie zmiennej! FTotal = FTrain - Sign(V) * FStand; From 3cdce8e7542600f61f2ff690c3158f1238d5468d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Thu, 5 Nov 2020 21:43:48 +0100 Subject: [PATCH 16/63] Additional spring brake parameters for Impuls EMU --- McZapkie/MOVER.h | 2 ++ McZapkie/Mover.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 68b26333..c91695d8 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1149,6 +1149,8 @@ public: bool MBrake = false; /*Czy jest hamulec reczny*/ double StopBrakeDecc = 0.0; bool ReleaseParkingBySpringBrake { false }; + bool SpringBrakeCutsOffDrive { true }; + double SpringBrakeDriveEmergencyVel { -1 }; TSecuritySystem SecuritySystem; int EmergencyBrakeWarningSignal{ 0 }; // combined with basic WarningSignal when manual emergency brake is active TUniversalCtrlTable UniCtrlList; /*lista pozycji uniwersalnego nastawnika*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 9e4bfb7e..d1e7e326 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -4310,6 +4310,9 @@ void TMoverParameters::UpdatePipePressure(double dt) || ( true == TestFlag( EngDmgFlag, 32 ) ) */ || ( true == s_CAtestebrake ) + || ( ( SpringBrakeDriveEmergencyVel >= 0 ) + && ( Vel > SpringBrakeDriveEmergencyVel ) + && ( SpringBrake.IsActive ) ) || ( ( true == securitysystempresent ) && ( false == lowvoltagepower ) ) ) { EmergencyValveFlow = PF( 0, PipePress, 0.15 ) * dt; @@ -7114,7 +7117,7 @@ void TMoverParameters::CheckEIMIC(double dt) ( ( true == Mains ) || ( Power == 0.0 ) ) && ( ( Doors.instances[ side::left ].open_permit == false ) && ( Doors.instances[ side::right ].open_permit == false ) ) - && ( !SpringBrake.IsActive ) + && ( !SpringBrake.IsActive || !SpringBrakeCutsOffDrive ) && ( !LockPipe ) }; eimic = clamp(eimic, -1.0, eimicpowerenabled ? 1.0 : 0.0); } @@ -10190,6 +10193,8 @@ void TMoverParameters::LoadFIZ_Cntrl( std::string const &line ) { extract_value( StopBrakeDecc, "SBD", line, "" ); extract_value( ReleaseParkingBySpringBrake, "ReleaseParkingBySpringBrake", line, "" ); + extract_value( SpringBrakeCutsOffDrive, "SpringBrakeCutsOffDrive", line, ""); + extract_value( SpringBrakeDriveEmergencyVel, "SpringBrakeDriveEmergencyVel", line, ""); std::map starts { { "Disabled", start_t::disabled }, From 20b8c10e3a5560f7bf7068e3ba63d85e15feeb66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 7 Nov 2020 16:11:25 +0100 Subject: [PATCH 17/63] New brake operation mode PNEP --- McZapkie/Mover.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index d1e7e326..c21dacde 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -10058,6 +10058,7 @@ void TMoverParameters::LoadFIZ_Cntrl( std::string const &line ) { { std::map brakeopmodes{ { "PN", bom_PS + bom_PN }, + { "PNEP", bom_PS + bom_PN + bom_EP }, { "PNEPMED", bom_PS + bom_PN + bom_EP + bom_MED } }; auto lookup = brakeopmodes.find( extract_value( "BrakeOpModes", line ) ); From 52dcd5bf5f3dba657e12400916018db4089373bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 7 Nov 2020 21:03:37 +0100 Subject: [PATCH 18/63] More python dictionary entries for compressors --- Train.cpp | 30 ++++++++++++++++++++++++++++-- Train.h | 1 + 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Train.cpp b/Train.cpp index aaf49b10..cfc77a78 100644 --- a/Train.cpp +++ b/Train.cpp @@ -452,8 +452,11 @@ TTrain::TTrain() { bConv[ i ] = false; bComp[ i ][ 0 ] = false; bComp[ i ][ 1 ] = false; + //bComp[ i ][ 2 ] = false; + //bComp[ i ][ 3 ] = false; bHeat[ i ] = false; } + bCompressors.clear(); for( int i = 0; i < 9; ++i ) for (int j = 0; j < 10; ++j) { @@ -598,6 +601,7 @@ dictionary_source *TTrain::GetTrainState() { bPN = ( static_cast( temp_ham )->GetEDBCP() > 0.2 ); } dict->insert( "indir_brake", bPN ); + dict->insert( "emergency_brake", mvOccupied->AlarmChainFlag ); dict->insert( "brake_delay_flag", mvOccupied->BrakeDelayFlag ); dict->insert( "brake_op_mode_flag", mvOccupied->BrakeOpModeFlag ); // other controls @@ -609,6 +613,7 @@ dictionary_source *TTrain::GetTrainState() { dict->insert( "radio_channel", RadioChannel() ); dict->insert( "radio_volume", Global.RadioVolume ); dict->insert( "door_lock", mvOccupied->Doors.lock_enabled ); + dict->insert( "door_step", mvOccupied->Doors.step_enabled ); // movement data dict->insert( "velocity", std::abs( mvOccupied->Vel ) ); dict->insert( "tractionforce", std::abs( mvOccupied->Ft ) ); @@ -644,9 +649,20 @@ dictionary_source *TTrain::GetTrainState() { dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_conv" ), bConv[ i ] ); dict->insert( ( "eimp_u" + std::to_string( i + 1 ) + "_comp_a" ), bComp[ i ][ 0 ] ); dict->insert( ( "eimp_u" + std::to_string( i + 1 ) + "_comp_w" ), bComp[ i ][ 1 ] ); + //dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_comp_a" ), bComp[ i ][ 2 ]); + //dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_comp_w" ), bComp[ i ][ 3 ]); dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_heat" ), bHeat[ i ] ); } + dict->insert( "compressors_no", (int)bCompressors.size() ); + for (int i = 0; i < bCompressors.size(); i++) + { + dict->insert("compressors_" + std::to_string(i + 1) + "_allow", std::get<0>(bCompressors[i])); + dict->insert("compressors_" + std::to_string(i + 1) + "_work", std::get<1>(bCompressors[i])); + dict->insert("compressors_" + std::to_string(i + 1) + "_car_no", std::get<2>(bCompressors[i])); + } + + bool kier = (DynamicObject->DirectionGet() * mvOccupied->CabOccupied > 0); TDynamicObject *p = DynamicObject->GetFirstDynamic(mvOccupied->CabOccupied < 0 ? end::rear : end::front, 4); int in = 0; @@ -655,6 +671,7 @@ dictionary_source *TTrain::GetTrainState() { if (p->MoverParameters->eimc[eimc_p_Pmax] > 1) { in++; + dict->insert(("eimp_c" + std::to_string(in) + "_invno"), p->MoverParameters->InvertersNo); for (int j = 0; j < p->MoverParameters->InvertersNo; j++) { dict->insert(("eimp_c" + std::to_string(in) + "_inv" + std::to_string(j + 1) + "_act"), p->MoverParameters->Inverters[j].IsActive); dict->insert(("eimp_c" + std::to_string(in) + "_inv" + std::to_string(j + 1) + "_error"), p->MoverParameters->Inverters[j].Error); @@ -6176,8 +6193,11 @@ bool TTrain::Update( double const Deltatime ) bConv[i] = false; bComp[i][0] = false; bComp[i][1] = false; + //bComp[i][2] = false; + //bComp[i][3] = false; bHeat[i] = false; } + bCompressors.clear(); for (int i = 0; i < 20; i++) { if (p) @@ -6185,7 +6205,7 @@ bool TTrain::Update( double const Deltatime ) fPress[i][0] = p->MoverParameters->BrakePress; fPress[i][1] = p->MoverParameters->PipePress; fPress[i][2] = p->MoverParameters->ScndPipePress; - fPress[i][3] = p->MoverParameters->CntrlPipePress; + fPress[i][3] = p->MoverParameters->CntrlPipePress; bBrakes[i][0] = p->MoverParameters->SpringBrake.IsActive; bBrakes[i][1] = p->MoverParameters->SpringBrake.ShuttOff; bDoors[i][1] = ( p->MoverParameters->Doors.instances[ side::left ].position > 0.f ); @@ -6206,6 +6226,10 @@ bool TTrain::Update( double const Deltatime ) if (p->MoverParameters->CompressorSpeed > 0.00001) { bComp[iUnitNo - 1][1] = (bComp[iUnitNo - 1][1] || p->MoverParameters->CompressorFlag); + bCompressors.emplace_back( + p->MoverParameters->CompressorAllow || (p->MoverParameters->CompressorStart == start_t::automatic), + p->MoverParameters->CompressorFlag, + i); } if ((in < 8) && (p->MoverParameters->eimc[eimc_p_Pmax] > 1)) { @@ -6227,6 +6251,8 @@ bool TTrain::Update( double const Deltatime ) bBatt[in] = p->MoverParameters->Battery; bConv[in] = p->MoverParameters->ConverterFlag; bHeat[in] = p->MoverParameters->Heating; + //bComp[in][2] = (p->MoverParameters->CompressorAllow || (p->MoverParameters->CompressorStart == start_t::automatic)); + //bComp[in][3] = (p->MoverParameters->CompressorFlag); in++; iPowerNo = in; } @@ -6264,7 +6290,7 @@ bool TTrain::Update( double const Deltatime ) fPress[i][0] = fPress[i][1] = fPress[i][2] - = fPress[i][3] + = fPress[i][3] = 0; bDoors[i][0] = bDoors[i][1] diff --git a/Train.h b/Train.h index e285a10c..dd31f515 100644 --- a/Train.h +++ b/Train.h @@ -784,6 +784,7 @@ private: bool bBatt[8]; // baterie bool bConv[8]; // przetwornice bool bComp[8][2]; // sprezarki + std::vector> bCompressors; bool bHeat[8]; // grzanie // McZapkie: do syczenia float fPPress, fNPress; From a065f9664b040b6e024fdfc6a748f407ded43d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 7 Nov 2020 21:48:12 +0100 Subject: [PATCH 19/63] Additional compressor's pressure switch parameters dependant on activated cab --- McZapkie/MOVER.h | 5 +++++ McZapkie/Mover.cpp | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index c91695d8..cd2b7f0e 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1121,6 +1121,11 @@ public: double Spg = 0.0; double MinCompressor = 0.0; double MaxCompressor = 0.0; + double MinCompressor_cabA = 0.0; + double MaxCompressor_cabA = 0.0; + double MinCompressor_cabB = 0.0; + double MaxCompressor_cabB = 0.0; + bool CabDependentCompressor = false; double CompressorSpeed = 0.0; int CompressorList[4][9]; // pozycje świateł, przód - tył, 1 .. 16 double EmergencyValveOn = 0.0; diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index c21dacde..4911446d 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -4061,6 +4061,20 @@ void TMoverParameters::CompressorCheck(double dt) { return; } + if (CabDependentCompressor) + { + if (CabActive > 0) + { + MinCompressor = MinCompressor_cabA; + MaxCompressor = MaxCompressor_cabA; + } + if (CabActive < 0) + { + MinCompressor = MinCompressor_cabB; + MaxCompressor = MaxCompressor_cabB; + } + } + //EmergencyValve EmergencyValveOpen = (Compressor > (EmergencyValveOpen ? EmergencyValveOff : EmergencyValveOn)); if (EmergencyValveOpen) { @@ -9789,6 +9803,8 @@ void TMoverParameters::LoadFIZ_Brake( std::string const &line ) { */ extract_value( MinCompressor, "MinCP", line, "" ); extract_value( MaxCompressor, "MaxCP", line, "" ); + extract_value( MinCompressor, "MinCP_B", line, "" ); + extract_value( MaxCompressor, "MaxCP_B", line, "" ); extract_value( CompressorTankValve, "CompressorTankValve", line, "" ); extract_value( CompressorSpeed, "CompressorSpeed", line, "" ); extract_value( EmergencyValveOff, "MinEVP", line, "" ); @@ -9825,6 +9841,22 @@ void TMoverParameters::LoadFIZ_Brake( std::string const &line ) { extract_value( ReleaserEnabledOnlyAtNoPowerPos, "ReleaserPowerPosLock", line, ( ( EngineType == TEngineType::DieselEngine ) || ( EngineType == TEngineType::DieselElectric ) ) ? "yes" : "no" ); + + if (MinCompressor_cabB > 0.0) { + MinCompressor_cabA = MinCompressor; + CabDependentCompressor = true; + } + else { + MinCompressor_cabB = MinCompressor; + } + if (MaxCompressor_cabB > 0.0) + { + MaxCompressor_cabA = MaxCompressor; + CabDependentCompressor = true; + } + else { + MaxCompressor_cabB = MaxCompressor; + } } void TMoverParameters::LoadFIZ_Doors( std::string const &line ) { From 11ba7696a58c63673cc1829dbf4d1b84e4ae2f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sun, 8 Nov 2020 21:25:45 +0100 Subject: [PATCH 20/63] Brakes positions to python dictionary --- DynObj.cpp | 3 ++- McZapkie/MOVER.h | 1 + McZapkie/Mover.cpp | 1 + Train.cpp | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/DynObj.cpp b/DynObj.cpp index fe6d6ebd..e59e63d1 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3111,12 +3111,13 @@ bool TDynamicObject::Update(double dt, double dt1) RapidMult = MoverParameters->RapidMult; auto const amax = RapidMult * std::min(FmaxPN / masamax, MoverParameters->MED_amax); - auto const doorisopen { + auto doorisopen { ( false == MoverParameters->Doors.instances[ side::left ].is_closed ) || ( false == MoverParameters->Doors.instances[ side::right ].is_closed ) || ( MoverParameters->Doors.permit_needed && ( MoverParameters->Doors.instances[ side::left ].open_permit || MoverParameters->Doors.instances[ side::right ].open_permit ) ) }; + //doorisopen &= !(MoverParameters->ReleaseParkingBySpringBrakeWhenDoorIsOpen && MoverParameters->SpringBrake.IsActive); if ((MoverParameters->Vel < 0.5) && (eimic < 0 || doorisopen || MoverParameters->Hamulec->GetEDBCP())) { diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index cd2b7f0e..68fc4bed 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1154,6 +1154,7 @@ public: bool MBrake = false; /*Czy jest hamulec reczny*/ double StopBrakeDecc = 0.0; bool ReleaseParkingBySpringBrake { false }; + bool ReleaseParkingBySpringBrakeWhenDoorIsOpen{ false }; bool SpringBrakeCutsOffDrive { true }; double SpringBrakeDriveEmergencyVel { -1 }; TSecuritySystem SecuritySystem; diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 4911446d..b9e17887 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -10226,6 +10226,7 @@ void TMoverParameters::LoadFIZ_Cntrl( std::string const &line ) { extract_value( StopBrakeDecc, "SBD", line, "" ); extract_value( ReleaseParkingBySpringBrake, "ReleaseParkingBySpringBrake", line, "" ); + extract_value(ReleaseParkingBySpringBrakeWhenDoorIsOpen, "ReleaseParkingBySpringBrakeWhenDoorIsOpen", line, ""); extract_value( SpringBrakeCutsOffDrive, "SpringBrakeCutsOffDrive", line, ""); extract_value( SpringBrakeDriveEmergencyVel, "SpringBrakeDriveEmergencyVel", line, ""); diff --git a/Train.cpp b/Train.cpp index cfc77a78..cc98cccd 100644 --- a/Train.cpp +++ b/Train.cpp @@ -584,6 +584,8 @@ dictionary_source *TTrain::GetTrainState() { dict->insert( "main_ctrl_actual_pos", mvControlled->MainCtrlActualPos ); dict->insert( "scndctrl_pos", mvControlled->ScndCtrlPos ); dict->insert( "scnd_ctrl_actual_pos", mvControlled->ScndCtrlActualPos ); + dict->insert( "brakectrl_pos", mvControlled->fBrakeCtrlPos ); + dict->insert( "localbrake_pos", mvControlled->LocalBrakePosA ); dict->insert( "new_speed", mvOccupied->NewSpeed); dict->insert( "speedctrl", mvOccupied->SpeedCtrlValue); dict->insert( "speedctrlpower", mvOccupied->SpeedCtrlUnit.DesiredPower ); From b0342c74473cafd33939177f26dc5cf2fa8951ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Mon, 9 Nov 2020 21:33:18 +0100 Subject: [PATCH 21/63] ReleaseParkingBySpringBrakeWhenDoorIsOpen for Impuls EMU --- DynObj.cpp | 4 ++-- McZapkie/Mover.cpp | 24 ++++++++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/DynObj.cpp b/DynObj.cpp index e59e63d1..a34b5c06 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3117,7 +3117,7 @@ bool TDynamicObject::Update(double dt, double dt1) || ( MoverParameters->Doors.permit_needed && ( MoverParameters->Doors.instances[ side::left ].open_permit || MoverParameters->Doors.instances[ side::right ].open_permit ) ) }; - //doorisopen &= !(MoverParameters->ReleaseParkingBySpringBrakeWhenDoorIsOpen && MoverParameters->SpringBrake.IsActive); + doorisopen &= !(MoverParameters->ReleaseParkingBySpringBrakeWhenDoorIsOpen && MoverParameters->SpringBrake.IsActive); if ((MoverParameters->Vel < 0.5) && (eimic < 0 || doorisopen || MoverParameters->Hamulec->GetEDBCP())) { @@ -3144,7 +3144,7 @@ bool TDynamicObject::Update(double dt, double dt1) { Fzad = std::min(LBR * FmaxED, FfulED); } - if (((MoverParameters->ShuntMode) && (eimic <= 0)) /*|| + if (((MoverParameters->ShuntMode) && (eimic <= 0) || (doorisopen)) /*|| (MoverParameters->V * MoverParameters->DirAbsolute < -0.2)*/) { auto const sbd { ( ( MoverParameters->SpringBrake.IsActive && MoverParameters->ReleaseParkingBySpringBrake ) ? 0.0 : MoverParameters->StopBrakeDecc ) }; diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index b9e17887..95764ae0 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -7129,11 +7129,27 @@ void TMoverParameters::CheckEIMIC(double dt) auto const eimicpowerenabled { ( ( true == Mains ) || ( Power == 0.0 ) ) - && ( ( Doors.instances[ side::left ].open_permit == false ) - && ( Doors.instances[ side::right ].open_permit == false ) ) && ( !SpringBrake.IsActive || !SpringBrakeCutsOffDrive ) && ( !LockPipe ) }; - eimic = clamp(eimic, -1.0, eimicpowerenabled ? 1.0 : 0.0); + auto const eimicdoorenabled { + (SpringBrake.IsActive && ReleaseParkingBySpringBrakeWhenDoorIsOpen) + }; + double eimic_max = 0.0; + if ((Doors.instances[side::left].open_permit == false) + && (Doors.instances[side::right].open_permit == false)) { + if (eimicpowerenabled) { + eimic_max = 1.0; + } + else { + eimic_max = 0.001; + } + } + else { + if (eimicdoorenabled) { + eimic_max = 0.001; + } + } + eimic = clamp(eimic, -1.0, eimicpowerenabled ? eimic_max : 0.0); } void TMoverParameters::CheckSpeedCtrl(double dt) @@ -10226,7 +10242,7 @@ void TMoverParameters::LoadFIZ_Cntrl( std::string const &line ) { extract_value( StopBrakeDecc, "SBD", line, "" ); extract_value( ReleaseParkingBySpringBrake, "ReleaseParkingBySpringBrake", line, "" ); - extract_value(ReleaseParkingBySpringBrakeWhenDoorIsOpen, "ReleaseParkingBySpringBrakeWhenDoorIsOpen", line, ""); + extract_value( ReleaseParkingBySpringBrakeWhenDoorIsOpen, "ReleaseParkingBySpringBrakeWhenDoorIsOpen", line, "" ); extract_value( SpringBrakeCutsOffDrive, "SpringBrakeCutsOffDrive", line, ""); extract_value( SpringBrakeDriveEmergencyVel, "SpringBrakeDriveEmergencyVel", line, ""); From 8dd1ca19d41ae514cb010fa39ac41e30619e7e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Mon, 21 Dec 2020 12:20:26 +0100 Subject: [PATCH 22/63] Added optional train category to the timetable --- mtable.cpp | 37 +++++++++++++++++++++++++++++++------ mtable.h | 1 + 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/mtable.cpp b/mtable.cpp index 2fce8841..fafc852e 100644 --- a/mtable.cpp +++ b/mtable.cpp @@ -310,14 +310,38 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) // pliku} // ConversionError:=-7 {błąd niezgodności} TrainName = s; // nadanie nazwy z pliku TXT (bez ścieżki do pliku) + bool isCategory = false; + while (fin >> s || fin.bad()) + { + if (s.find("_______|") != std::string::npos) + { + break; + } + if (s == "Kategoria") + { + isCategory = true; + break; + } + } // while (!(s == "Seria")); + if (isCategory) + { + do + { + fin >> s; + } while (!((s == "|") || (fin.bad()))); + fin >> TrainCategory; + } // else { /*czytaj naglowek*/ - while (fin >> s || !fin.bad()) - { - if (s.find("_______|") != std::string::npos) - break; - // fin >> s; - } // while (!(s.find("_______|") != std::string::npos) || fin.eof()); + if (isCategory) + { + while (fin >> s || !fin.bad()) + { + if (s.find("_______|") != std::string::npos) + break; + // fin >> s; + } // while (!(s.find("_______|") != std::string::npos) || fin.eof()); + } while (fin >> s || !fin.bad()) { if (s == "[") @@ -622,6 +646,7 @@ bool TTrainParameters::DirectionChange() void TTrainParameters::serialize( dictionary_source *Output ) const { Output->insert( "trainnumber", TrainName ); + Output->insert( "traincategory", TrainCategory ); Output->insert( "train_brakingmassratio", BrakeRatio ); Output->insert( "train_enginetype", LocSeries ); Output->insert( "train_engineload", LocLoad ); diff --git a/mtable.h b/mtable.h index 353af85f..a30757b7 100644 --- a/mtable.h +++ b/mtable.h @@ -50,6 +50,7 @@ class TTrainParameters { public: std::string TrainName; + std::string TrainCategory; double TTVmax; std::string Relation1; std::string Relation2; // nazwy stacji danego odcinka From f32b1efb1896723b47103a4a85db3d7add4597cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sun, 3 Jan 2021 19:46:04 +0100 Subject: [PATCH 23/63] Additional parameters for cooling fans --- McZapkie/MOVER.h | 3 +++ McZapkie/Mover.cpp | 25 +++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 68fc4bed..5b44f128 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -821,8 +821,11 @@ private: struct cooling_fan : public basic_device { // config float speed { 0.f }; // cooling fan rpm; either fraction of parent rpm, or absolute value if negative + float sustain_time { 0.f }; // time of sustaining work of cooling fans after stop + float min_start_velocity { -1.f }; // minimal velocity of vehicle, when cooling fans activate // ld outputs float revolutions { 0.f }; // current fan rpm + float stop_timer { 0.f }; // current time, when shut off condition is active }; // basic approximation of a fuel pump diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 95764ae0..f612f759 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -1997,7 +1997,26 @@ void TMoverParameters::OilPumpCheck( double const Timestep ) { void TMoverParameters::MotorBlowersCheck( double const Timestep ) { // activation check for( auto &blower : MotorBlowers ) { - + auto disable = blower.is_disabled; + if (blower.min_start_velocity >= 0) + { + if ( Vel < 0.5 && Im < 0.5 ) + { + blower.stop_timer += Timestep; + if (blower.stop_timer > blower.sustain_time) + { + disable = true; + } + } + else if (Vel >= blower.min_start_velocity && Im > 0.5) + { + blower.stop_timer = 0; + } + else + { + disable |= !blower.is_active; + } + } blower.is_active = ( // TODO: bind properly power source when ld is in place ( blower.start_type == start_t::battery ? Power24vIsAvailable : @@ -2005,7 +2024,7 @@ void TMoverParameters::MotorBlowersCheck( double const Timestep ) { Mains ) // power source // breaker condition disabled until it's implemented in the class data // && ( true == blower.breaker ) - && ( false == blower.is_disabled ) + && ( false == disable) && ( ( true == blower.is_active ) || ( blower.start_type == start_t::manual ? blower.is_enabled : @@ -10743,6 +10762,8 @@ void TMoverParameters::LoadFIZ_Engine( std::string const &Input ) { // traction motors extract_value( MotorBlowers[ end::front ].speed, "MotorBlowersSpeed", Input, "" ); + extract_value( MotorBlowers[ end::front ].sustain_time, "MotorBlowersSustainTime", Input, "" ); + extract_value( MotorBlowers[ end::front ].min_start_velocity, "MotorBlowersStartVelocity", Input, "" ); MotorBlowers[ end::rear ] = MotorBlowers[ end::front ]; // pressure switch extract_value( HasControlPressureSwitch, "PressureSwitch", Input, ( TrainType != dt_EZT ? "yes" : "no" ) ); From 84e800120ca3fefb9fd1d5e090a2393f31523b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 13 Feb 2021 23:52:53 +0100 Subject: [PATCH 24/63] Added sound of working retarder --- DynObj.cpp | 23 +++++++++++++++++++++-- DynObj.h | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/DynObj.cpp b/DynObj.cpp index a34b5c06..19a79059 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -6058,7 +6058,11 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co m_powertrainsounds.rsEngageSlippery.m_frequencyfactor /= ( 1 + MoverParameters->nmax ); } - else if( token == "linebreakerclose:" ) { + else if (token == "retarder:") { + m_powertrainsounds.retarder.deserialize(parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency); + m_powertrainsounds.retarder.owner(this); + } + else if( token == "linebreakerclose:" ) { m_powertrainsounds.linebreaker_close.deserialize( parser, sound_type::single ); m_powertrainsounds.linebreaker_close.owner( this ); } @@ -7359,7 +7363,7 @@ TDynamicObject::powertrain_sounds::position( glm::vec3 const Location ) { &inverter, &motor_relay, &dsbWejscie_na_bezoporow, &motor_parallel, &motor_shuntfield, &rsWentylator, &engine, &engine_ignition, &engine_shutdown, &engine_revving, &engine_turbo, &oil_pump, &fuel_pump, &water_pump, &water_heater, &radiator_fan, &radiator_fan_aux, - &transmission, &rsEngageSlippery + &transmission, &rsEngageSlippery, &retarder }; for( auto sound : enginesounds ) { if( sound->offset() == nullvector ) { @@ -7613,6 +7617,21 @@ TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, doub rsEngageSlippery.stop(); } + if (Vehicle.hydro_R) { + float speed = std::abs(Vehicle.hydro_R_Fill * Vehicle.hydro_R_n); + + retarder + .pitch(retarder.m_frequencyoffset + speed * retarder.m_frequencyfactor) + .gain(retarder.m_amplitudeoffset + Vehicle.hydro_R_Fill * retarder.m_amplitudefactor); + + if (retarder.gain() > 0.01) { + retarder.play(sound_flags::exclusive | sound_flags::looping); + } + else { + retarder.stop(); + } + } + // motor sounds volume = 0.0; // generic traction motor sounds diff --git a/DynObj.h b/DynObj.h index c77288df..12d831b5 100644 --- a/DynObj.h +++ b/DynObj.h @@ -403,6 +403,7 @@ private: sound_source radiator_fan_aux { sound_placement::engine }; sound_source transmission { sound_placement::engine }; sound_source rsEngageSlippery { sound_placement::engine }; // moved from cab + sound_source retarder { sound_placement::engine }; void position( glm::vec3 const Location ); void render( TMoverParameters const &Vehicle, double const Deltatime ); From 029a0639704ee4fac10712d8226173a8df85b933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Fri, 19 Feb 2021 22:14:05 +0100 Subject: [PATCH 25/63] Better sound of retarder --- DynObj.cpp | 4 ++-- McZapkie/MOVER.h | 4 ++++ McZapkie/Mover.cpp | 22 ++++++++++++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/DynObj.cpp b/DynObj.cpp index 19a79059..ca0982be 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -7618,13 +7618,13 @@ TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, doub } if (Vehicle.hydro_R) { - float speed = std::abs(Vehicle.hydro_R_Fill * Vehicle.hydro_R_n); + float speed = std::abs(Vehicle.hydro_R_n); retarder .pitch(retarder.m_frequencyoffset + speed * retarder.m_frequencyfactor) .gain(retarder.m_amplitudeoffset + Vehicle.hydro_R_Fill * retarder.m_amplitudefactor); - if (retarder.gain() > 0.01) { + if ((retarder.gain() > 0.01)&&(speed > 1)&&(Vehicle.hydro_R_ClutchActive)) { retarder.play(sound_flags::exclusive | sound_flags::looping); } else { diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 5b44f128..c1ed8f04 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1269,6 +1269,9 @@ public: double hydro_R_FillRateDec = 1.0; /*szybkosc oprozniania sprzegla*/ double hydro_R_MinVel = 1.0; /*minimalna predkosc, przy ktorej retarder dziala*/ double hydro_R_EngageVel = 1.0; /*minimalna predkosc hamowania, przy ktorej sprzeglo jest wciaz wlaczone*/ + bool hydro_R_Clutch = false; /*czy retarder ma rozłączalne sprzęgło*/ + double hydro_R_ClutchSpeed = 10.0; /*szybkość narastania obrotów po włączeniu sprzęgła retardera*/ + bool hydro_R_WithIndividual = false; /*czy dla autobusów jest to łączone*/ /*- dla lokomotyw spalinowo-elektrycznych -*/ double AnPos = 0.0; // pozycja sterowania dokladnego (analogowego) bool AnalogCtrl = false; // @@ -1567,6 +1570,7 @@ public: double hydro_R_Torque = 0.0; /*moment*/ double hydro_R_Request = 0.0; /*zadanie sily hamowania*/ double hydro_R_n = 0.0; /*predkosc obrotowa retardera*/ + bool hydro_R_ClutchActive = false; /*czy retarder jest napędzany*/ /*- zmienne dla lokomotyw z silnikami indukcyjnymi -*/ double eimic = 0; /*aktualna pozycja zintegrowanego sterowania jazda i hamowaniem*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index f612f759..4c60d90b 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -7829,6 +7829,7 @@ double TMoverParameters::dizel_Momentum(double dizel_fill, double n, double dt) double TMoverParameters::dizel_MomentumRetarder(double n, double dt) { double RetarderRequest = (Mains ? std::max(0.0, -eimic_real) : 0); + if (hydro_R_WithIndividual) RetarderRequest = LocalBrakeRatio(); if (Vel < hydro_R_MinVel) RetarderRequest = 0; if ((hydro_R_Placement == 2) && (enrot < dizel_nmin)) @@ -7836,7 +7837,21 @@ double TMoverParameters::dizel_MomentumRetarder(double n, double dt) RetarderRequest = 0; } - hydro_R_n = n * 60; + hydro_R_ClutchActive = (!hydro_R_Clutch) || (RetarderRequest > 0); + if ((!hydro_R_Clutch) + || ((hydro_R_ClutchActive) && (hydro_R_ClutchSpeed == 0))) + { + hydro_R_n = n * 60; + } + else if (hydro_R_ClutchActive) + { + hydro_R_n = sign(n)*std::min(std::abs(hydro_R_n + hydro_R_ClutchSpeed * dt), std::abs(n * 60)); + } + else + { + hydro_R_n = 0; + } + n = hydro_R_n / 60.f; if (hydro_R_Fill < RetarderRequest) //gdy zadane hamowanie { @@ -7848,7 +7863,7 @@ double TMoverParameters::dizel_MomentumRetarder(double n, double dt) } double Moment = hydro_R_MaxTorque; - double pwr = Moment * n * M_PI * 2 * 0.001; + double pwr = Moment * std::abs(n) * M_PI * 2 * 0.001; if (pwr > hydro_R_MaxPower) Moment = Moment * hydro_R_MaxPower / pwr; double moment_in = n*n*hydro_R_TorqueInIn; @@ -10653,6 +10668,9 @@ void TMoverParameters::LoadFIZ_Engine( std::string const &Input ) { extract_value(hydro_R_FillRateDec, "R_FRD", Input, ""); extract_value(hydro_R_MinVel, "R_MinVel", Input, ""); extract_value(hydro_R_EngageVel, "R_EngageVel", Input, ""); + extract_value(hydro_R_Clutch, "R_IsClutch", Input, ""); + extract_value(hydro_R_ClutchSpeed, "R_ClutchSpeed", Input, ""); + extract_value(hydro_R_WithIndividual, "R_WithIndividual", Input, ""); } } break; From 98ef06e8fdd46d8db5011c1a5de691e641d9f51a Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sat, 6 Mar 2021 05:24:44 +0100 Subject: [PATCH 26/63] build 210305. ai route scanning fix, power use meter, minor bug fixes --- Driver.cpp | 34 ++++++++++++++++------------------ Driver.h | 7 +++++++ DynObj.cpp | 2 ++ McZapkie/MOVER.h | 3 ++- McZapkie/Mover.cpp | 4 ++-- driveruipanels.cpp | 28 ++++++++++++++++++++++++++++ translation.cpp | 4 ++++ translation.h | 2 ++ version.h | 2 +- 9 files changed, 64 insertions(+), 22 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 84ec6525..d8af6c5c 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -2751,17 +2751,18 @@ bool TController::PrepareEngine() && ( ( mvOccupied->fBrakeCtrlPos == mvOccupied->Handle->GetPos( bh_RP ) || ( mvOccupied->BrakeHandle == TBrakeHandle::NoHandle ) ) ); } - iEngineActive = isready; - - if( true == iEngineActive ) { + if( true == isready ) { // jeśli dotychczas spał teraz nie ma powodu do stania eAction = TAction::actUnknown; if( eStopReason == stopSleep ) { eStopReason = stopNone; } - iDrivigFlags |= moveActive; // może skanować sygnały i reagować na komendy + // może skanować sygnały i reagować na komendy + iDrivigFlags |= moveActive; } + iEngineActive = isready; + return iEngineActive; } @@ -4755,7 +4756,9 @@ TController::Update( double const Timedelta ) { mvOccupied->Vel > 5.0 ? 400 + fBrakeDist : 30.0 * fDriverDist ) }; // 1500m dla stojących pociągów; - scan_route( awarenessrange ); + if( is_active() ) { + scan_route( awarenessrange ); + } scan_obstacles( awarenessrange ); // generic actions control_security_system( reactiontime ); @@ -4782,8 +4785,6 @@ TController::Update( double const Timedelta ) { control_tractive_and_braking_force(); } SetTimeControllers(); -// if the route ahead is blocked we might need to head the other way - check_route_behind( 1000 ); // NOTE: legacy scan range value } // configures vehicle heating given current situation; returns: true if vehicle can be operated normally, false otherwise @@ -6330,15 +6331,8 @@ TController::determine_braking_distance() { void TController::scan_route( double const Range ) { - // Ra 2015-01: przy dłuższej drodze skanowania AI jeździ spokojniej - // 2. Sprawdzić, czy tabelka pokrywa założony odcinek (nie musi, jeśli jest STOP). - // 3. Sprawdzić, czy trajektoria ruchu przechodzi przez zwrotnice - jeśli tak, to sprawdzić, czy stan się nie zmienił. - // 4. Ewentualnie uzupełnić tabelkę informacjami o sygnałach i ograniczeniach, jeśli się "zużyła". - TableCheck( Range ); // wypełnianie tabelki i aktualizacja odległości - // 5. Sprawdzić stany sygnalizacji zapisanej w tabelce, wyznaczyć prędkości. - // 6. Z tabelki wyznaczyć krytyczną odległość i prędkość (najmniejsze przyspieszenie). - // 7. Jeśli jest inny pojazd z przodu, ewentualnie skorygować odległość i prędkość. - // 8. Ustalić częstotliwość świadomości AI (zatrzymanie precyzyjne - częściej, brak atrakcji - rzadziej). + + TableCheck( Range ); } // check for potential collisions @@ -6892,10 +6886,11 @@ TController::pick_optimal_speed( double const Range ) { SwitchClearDist = -1; // if we're idling bail out early - if( false == TestFlag( iDrivigFlags, moveActive ) ) { + if( false == is_active() ) { VelDesired = 0.0; + VelNext = 0.0; AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION ); -// return; + return; } // basic velocity and acceleration adjustments @@ -6961,6 +6956,9 @@ TController::pick_optimal_speed( double const Range ) { AccDesired, ( is_car() ? -2.0 : -0.9 ), ( is_car() ? 2.0 : 0.9 ) ); + + // if the route ahead is blocked we might need to head the other way + check_route_behind( 1000 ); // NOTE: legacy scan range value } void diff --git a/Driver.h b/Driver.h index e0011e6c..b445a27e 100644 --- a/Driver.h +++ b/Driver.h @@ -228,14 +228,21 @@ public: TAction const & action() const { return eAction; } inline + bool is_active() const { + return TestFlag( iDrivigFlags, moveActive ); } + inline bool is_train() const { return TestFlag( mvOccupied->CategoryFlag, 1 ); } + inline bool is_car() const { return TestFlag( mvOccupied->CategoryFlag, 2 ); } + inline bool is_emu() const { return ( mvControlling->TrainType == dt_EZT ); } + inline bool is_dmu() const { return ( mvControlling->TrainType == dt_DMU ); } + inline bool has_diesel_engine() const { return ( ( mvControlling->EngineType == TEngineType::DieselElectric ) || ( mvControlling->EngineType == TEngineType::DieselEngine ) ); diff --git a/DynObj.cpp b/DynObj.cpp index ca0982be..f0139b4e 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3636,6 +3636,7 @@ bool TDynamicObject::Update(double dt, double dt1) } else MoverParameters->PantFrontVolt = 0.0; + ( ( fPantCurrent > 0.0 ) ? MoverParameters->EnergyMeter.first : MoverParameters->EnergyMeter.second ) += MoverParameters->PantRearVolt * fPantCurrent * dt1 / 3600000.0; break; case 1: if( ( false == Global.bLiveTraction ) @@ -3680,6 +3681,7 @@ bool TDynamicObject::Update(double dt, double dt1) // Global.iPause ^= 2; MoverParameters->PantRearVolt = 0.0; } + ( ( fPantCurrent > 0.0 ) ? MoverParameters->EnergyMeter.first : MoverParameters->EnergyMeter.second ) += MoverParameters->PantFrontVolt * fPantCurrent * dt1 / 3600000.0; break; } // pozostałe na razie nie obsługiwane if( MoverParameters->PantPress > ( diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index c1ed8f04..79c081aa 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1361,7 +1361,8 @@ public: int SoundFlag = 0; /*!o patrz stale sound_ */ int AIFlag{ 0 }; // HACK: events of interest for consist owner double DistCounter = 0.0; /*! licznik kilometrow */ - double V = 0.0; //predkosc w [m/s] względem sprzęgów (dodania gdy jedzie w stronę 0) + std::pair EnergyMeter; // energy from grid [kWh] + double V = 0.0; //predkosc w [m/s] względem sprzęgów (dodania gdy jedzie w stronę 0) double Vel = 0.0; //moduł prędkości w [km/h], używany przez AI double AccS = 0.0; //efektywne przyspieszenie styczne w [m/s^2] (wszystkie siły) double AccSVBased {}; // tangential acceleration calculated from velocity change diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 4c60d90b..04bdf734 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -9853,8 +9853,8 @@ void TMoverParameters::LoadFIZ_Brake( std::string const &line ) { */ extract_value( MinCompressor, "MinCP", line, "" ); extract_value( MaxCompressor, "MaxCP", line, "" ); - extract_value( MinCompressor, "MinCP_B", line, "" ); - extract_value( MaxCompressor, "MaxCP_B", line, "" ); + extract_value( MinCompressor_cabB, "MinCP_B", line, "" ); + extract_value( MaxCompressor_cabB, "MaxCP_B", line, "" ); extract_value( CompressorTankValve, "CompressorTankValve", line, "" ); extract_value( CompressorSpeed, "CompressorSpeed", line, "" ); extract_value( EmergencyValveOff, "MinEVP", line, "" ); diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 5aebe839..15e95f29 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -867,6 +867,34 @@ debug_panel::update_section_vehicle( std::vector &Output ) { Output.emplace_back( m_buffer.data(), Global.UITextColor ); + if( mover.EnginePowerSource.SourceType == TPowerSource::CurrentCollector ) { + std::snprintf( + m_buffer.data(), m_buffer.size(), + locale::strings[ locale::string::debug_vehicle_poweruse ].c_str(), + std::abs( mover.EnergyMeter.first ), + std::abs( mover.EnergyMeter.second ), + mover.EnergyMeter.first + mover.EnergyMeter.second ); + textline = m_buffer.data(); + if( DebugTractionFlag ) { + for( int i = 0; i < vehicle.iAnimType[ ANIM_PANTS ]; ++i ) { // pętla po wszystkich pantografach + auto const *p { vehicle.pants[ i ].fParamPants }; + if( p && p->hvPowerWire ) { + auto const *powerwire { p->hvPowerWire }; + std::snprintf( + m_buffer.data(), m_buffer.size(), + locale::strings[ locale::string::debug_vehicle_powerwire ].c_str(), + i, + powerwire->psPower[ 0 ] ? powerwire->psPower[ 0 ]->name() : "none", + powerwire->psPower[ 1 ] ? powerwire->psPower[ 1 ]->name() : "none", + powerwire->pPoint1[ 0 ], + powerwire->pPoint1[ 1 ], + powerwire->pPoint1[ 2 ] ); + textline += m_buffer.data(); + } + } + } + } + Output.emplace_back( textline, Global.UITextColor ); } std::string diff --git a/translation.cpp b/translation.cpp index 1f4a0ef5..5df19be4 100644 --- a/translation.cpp +++ b/translation.cpp @@ -183,6 +183,8 @@ init() { "Brakes:\n train: %.2f (mode: %d, delay: %s, load flag: %d)\n independent: %.2f (%.2f), manual: %.2f, spring: %.2f\nBrake cylinder pressures:\n train: %.2f, independent: %.2f, status: 0x%.2x\nPipe pressures:\n brake: %.2f (hat: %.2f), main: %.2f, control: %.2f\nTank pressures:\n auxiliary: %.2f, main: %.2f, control: %.2f", " pantograph: %.2f%cMT", "Forces:\n tractive: %.1f, brake: %.1f, friction: %.2f%s\nAcceleration:\n tangential: %.2f, normal: %.2f (path radius: %s)\nVelocity: %.2f, distance traveled: %.2f\nPosition: [%.2f, %.2f, %.2f]", + "Power use:\n drawn: %.1f kWh, returned: %.1f kWh, balance: %.1f kWh", + "\n pantograph %d: drawing from: [%s, %s] through: [%.2f, %.2f, %.2f]", "master controller", "master controller", @@ -492,6 +494,8 @@ init() { "Hamulce:\n zespolony: %.2f (tryb: %d, nastawa: %s, ladunek: %d)\n pomocniczy: %.2f (%.2f), postojowy: %.2f, sprezynowy: %.2f\nCisnienie w cylindrach:\n zespolony: %.2f, pomocniczy: %.2f, status: 0x%.2x\nCisnienia w przewodach:\n glowny: %.2f (kapturek: %.2f), zasilajacy: %.2f, kontrolny: %.2f\nCisnienia w zbiornikach:\n pomocniczy: %.2f, glowny: %.2f, sterujacy: %.2f", " pantograf: %.2f%cZG", "Sily:\n napedna: %.1f, hamowania: %.1f, tarcie: %.2f%s\nPrzyspieszenia:\n styczne: %.2f, normalne: %.2f (promien: %s)\nPredkosc: %.2f, pokonana odleglosc: %.2f\nPozycja: [%.2f, %.2f, %.2f]", + "Zuzycie pradu:\n pobrane: %.1f kWh, oddane: %.1f kWh, zuzyte: %.1f kWh", + "\n pantograf %d: zrodla: [%s, %s] przeslo: [%.2f, %.2f, %.2f]", "nastawnik jazdy", "nastawnik jazdy", diff --git a/translation.h b/translation.h index 25965ce8..3dae8018 100644 --- a/translation.h +++ b/translation.h @@ -172,6 +172,8 @@ enum string { debug_vehicle_brakespressures, debug_vehicle_pantograph, debug_vehicle_forcesaccelerationvelocityposition, + debug_vehicle_poweruse, + debug_vehicle_powerwire, cab_mainctrl, cab_jointctrl, diff --git a/version.h b/version.h index 9c765115..35df6122 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 304 +#define VERSION_MINOR 305 #define VERSION_REVISION 0 From c216329bfa1b0860d3833f8a0da94bfdcefde1e1 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Thu, 11 Mar 2021 03:27:22 +0100 Subject: [PATCH 27/63] driving aid panel enhancement, event queue text filter, minor bug fixes --- Driver.cpp | 125 +++++-- Driver.h | 1 + Globals.cpp | 837 ++++++++++++++++++++++--------------------- Globals.h | 1 + McZapkie/Mover.cpp | 2 +- drivermode.cpp | 4 +- driveruipanels.cpp | 112 ++++-- driveruipanels.h | 3 + editormode.cpp | 2 +- opengl33renderer.cpp | 31 +- 10 files changed, 634 insertions(+), 484 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index d8af6c5c..4fb16a2a 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -876,6 +876,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN auto d { 0.0 }; // droga auto d_to_next_sem { 10000.0 }; //ustaiwamy na pewno dalej niż widzi AI auto go { TCommandType::cm_Unknown }; + auto speedlimitiscontinuous { true }; // stays true if potential active speed limit is unbroken to the last (relevant) point in scan table eSignNext = nullptr; IsAtPassengerStop = false; IsScheduledPassengerStopVisible = false; @@ -903,11 +904,13 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN } auto const railwaytrackend { ( true == TestFlag( point.iFlags, spEnd ) ) && ( is_train() ) }; - if( ( v >= 0.0 ) + if( ( v >= 0.0 ) // pozycje z prędkością -1 można spokojnie pomijać || ( railwaytrackend ) ) { - // pozycje z prędkością -1 można spokojnie pomijać + d = point.fDist; + if( v >= 0.0 ) { + // points located in front of us can potentially affect our acceleration and target speed if( ( d > 0.0 ) && ( false == TestFlag( point.iFlags, spElapsed ) ) ) { // sygnał lub ograniczenie z przodu (+32=przejechane) @@ -942,6 +945,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN } } } + // points located behind can affect our current speed, but little else else if ( point.iFlags & spTrack) // jeśli tor { // tor ogranicza prędkość, dopóki cały skład nie przejedzie, if( v >= 1.0 ) { // EU06 się zawieszało po dojechaniu na koniec toru postojowego @@ -959,6 +963,9 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN if( v < VelLimitLastDist.first ) { VelLimitLastDist.second = d + point.trTrack->Length() + fLength; } + else if( VelLimitLastDist.second > 0 ) { // the speed limit can potentially start afterwards, so don't mark it as broken too soon + speedlimitiscontinuous = false; + } if( false == railwaytrackend ) { continue; // i tyle wystarczy } @@ -974,8 +981,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN } // track can potentially end, which creates another virtual point of interest with speed limit of 0 at the end of it // TBD, TODO: when tracing the route create a dedicated table entry for it, to simplify the code? - if( ( true == TestFlag( point.iFlags, spEnd ) ) - && ( is_train() ) ) { + if( railwaytrackend ) { // if the railway track ends here set the velnext accordingly as well // TODO: test this with turntables and such auto const stopatendacceleration = ( -1.0 * mvOccupied->Vel * mvOccupied->Vel ) / ( 25.92 * ( d + point.trTrack->Length() ) ); @@ -990,27 +996,53 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN } } - if ((a < fAcc) && (v == min_speed(v, fNext))) { + if( ( a <= fAcc ) + && ( ( v < fNext ) || ( fNext < 0 ) ) ) { // filter out consecutive, farther out blocks with the same speed limit; they'd make us accelerate slower due to their lower a value // mniejsze przyspieszenie to mniejsza możliwość rozpędzenia się albo konieczność hamowania // jeśli droga wolna, to może być a>1.0 i się tu nie załapuje fAcc = a; // zalecane przyspieszenie (nie musi być uwzględniane przez AI) fNext = v; // istotna jest prędkość na końcu tego odcinka fDist = d; // dlugość odcinka } - else if ((fAcc > 0) && (v >= 0) && (v <= fNext)) { +/* + else if ((fAcc > 0) && (v >= 0) && (v > fNext)) { // jeśli nie ma wskazań do hamowania, można podać drogę i prędkość na jej końcu fNext = v; // istotna jest prędkość na końcu tego odcinka fDist = d; // dlugość odcinka (kolejne pozycje mogą wydłużać drogę, jeśli prędkość jest stała) } - if( ( v < VelLimitLastDist.first ) /* && ( d < VelLimitLastDist.second ) */ ) { - // if we encounter another speed limit before we can clear current/last registered one, - // update our calculation where we'll be able to resume regular speed +*/ + // we'll pick first scanned target speed as our goal + // farther scan table points can override it through previous clause, if they require lower acceleration or speed reduction + else if( ( a > 0 ) && ( a <= fAcc ) && ( v >= 0 ) && ( fNext < 0 ) ) { + fAcc = a; + fNext = v; + fDist = d; + } + // potentially update our current speed limit + if( ( v < VelLimitLastDist.first ) + && ( ( d < 0 ) // the point counts as part of last speed limit either if it's behind us + || ( VelLimitLastDist.second > 0 ) ) ) { // or if we already have the last limit ongoing VelLimitLastDist.second = d + fLength; if( ( point.iFlags & spTrack ) != 0 ) { VelLimitLastDist.second += point.trTrack->Length(); } } + else { + // if we found a point which isn't part of the current speed limit mark the limit as broken + // NOTE: we exclude switches from this rule, as speed limits generally extend through these + if( ( point.iFlags & ( spSwitch | spPassengerStopPoint ) ) == 0 ) { + speedlimitiscontinuous = false; + } + } } // if (v>=0.0) + // finding a point which doesn't impose speed limit means any potential active speed limit can't extend through entire scan table + // NOTE: we test actual speed value for the given point, as events may receive (v = -1) as a way to disable them in irrelevant modes + if( point.fVelNext < 0 ) { + // NOTE: we exclude switches from this rule, as speed limits generally extend through these + if( ( point.iFlags & ( spSwitch | spPassengerStopPoint ) ) == 0 ) { + speedlimitiscontinuous = false; + } + } if (fNext >= 0.0) { // jeśli ograniczenie if( ( point.iFlags & ( spEnabled | spEvent ) ) == ( spEnabled | spEvent ) ) { // tylko sygnał przypisujemy @@ -1030,6 +1062,11 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN && ( true == TestFlag( OrderCurrentGet(), Obey_train ) ) ) { VelSignalLast = -1.0; } + // if there's unbroken speed limit through our scan table take a note of it + if( ( VelLimitLastDist.second > 0 ) + && ( speedlimitiscontinuous ) ) { + VelLimitLastDist.second = EU07_AI_SPEEDLIMITEXTENDSBEYONDSCANRANGE; + } // take into account the effect switches have on duration of signal-imposed speed limit, in calculation of speed limit end point if( ( VelSignalLast >= 0.0 ) && ( SwitchClearDist >= 0.0 ) ) { VelLimitLastDist.second = std::max( VelLimitLastDist.second, SwitchClearDist ); @@ -1054,7 +1091,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN bool TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, double const Signaldistance ) { // stop points are irrelevant when not in one of the basic modes - if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) { return true; } + if( ( OrderCurrentGet() & ( /*Shunt | Loose_shunt |*/ Obey_train | Bank ) ) == 0 ) { return true; } // jeśli przystanek, trzeba obsłużyć wg rozkładu iDrivigFlags |= moveStopPointFound; // first 19 chars of the command is expected to be "PassengerStopPoint:" so we skip them @@ -1498,12 +1535,17 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo // HACK: if so, make it a stop point, to prevent non-signals farther down affect us auto const isforsomeoneelse { ( is_train() ) && ( Obstacle.distance < Point.fDist ) }; if( Point.fDist <= Signaldistance ) { - VelSignalNext = ( isforsomeoneelse ? 0.0 : Point.fVelNext ); - } - if( isforsomeoneelse ) { - Velocity = 0.0; -// VelNext = 0.0; -// return true; + if( isforsomeoneelse ) { + VelSignalNext = 0.0; + Velocity = 0.0; + } + else { + VelSignalNext - Point.fVelNext; + if( Velocity < 0 ) { + Velocity = fVelMax; + VelSignal = fVelMax; + } + } } } } @@ -6877,29 +6919,33 @@ TController::pick_optimal_speed( double const Range ) { // if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank | Connect | Disconnect | Change_direction ) ) == 0 ) { return; } // set initial velocity and acceleration values - VelDesired = fVelMax; // wstępnie prędkość maksymalna dla pojazdu(-ów), będzie następnie ograniczana - VelNext = VelDesired; // maksymalna prędkość wynikająca z innych czynników niż trajektoria ruchu SetDriverPsyche(); // ustawia AccPreferred (potrzebne tu?) + VelDesired = fVelMax; // wstępnie prędkość maksymalna dla pojazdu(-ów), będzie następnie ograniczana AccDesired = AccPreferred; // AccPreferred wynika z osobowości mechanika - ActualProximityDist = Range; // funkcja Update() może pozostawić wartości bez zmian - VelLimitLastDist = { VelDesired, -1 }; - SwitchClearDist = -1; - // if we're idling bail out early - if( false == is_active() ) { - VelDesired = 0.0; - VelNext = 0.0; - AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION ); - return; + // nie przekraczać rozkladowej + if( ( ( OrderCurrentGet() & Obey_train ) != 0 ) + && ( TrainParams.TTVmax > 0.0 ) ) { + VelDesired = + min_speed( + VelDesired, + TrainParams.TTVmax ); } - // basic velocity and acceleration adjustments +// VelNext = VelDesired; // maksymalna prędkość wynikająca z innych czynników niż trajektoria ruchu + // HACK: -1 means we can pick up 0 speed limits in ( point.velnext > velnext ) tests aimed to find highest next speed, + // but also doubles as 'max speed' in min_speed tests, so it shouldn't interfere if we don't find any speed limits + VelNext = -1.0; + // basic velocity and acceleration adjustments // jeśli manewry, to ograniczamy prędkość if( ( OrderCurrentGet() & ( Obey_train | Bank ) ) == 0 ) { // spokojne manewry SetVelocity( fShuntVelocity, fShuntVelocity ); + VelDesired = + min_speed( + VelDesired, + fShuntVelocity ); } - // uncoupling mode changes velocity/acceleration between stages if( ( OrderCurrentGet() & Disconnect ) != 0 ) { if( iVehicleCount >= 0 ) { @@ -6920,6 +6966,19 @@ TController::pick_optimal_speed( double const Range ) { } } } + + VelLimitLastDist = { VelDesired, -1 }; + SwitchClearDist = -1; + ActualProximityDist = Range; // funkcja Update() może pozostawić wartości bez zmian + + // if we're idling bail out early + if( false == is_active() ) { + VelDesired = 0.0; + VelNext = 0.0; + AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION ); + return; + } + // Ra: odczyt (ActualProximityDist), (VelNext) i (AccPreferred) z tabelki prędkosci check_route_ahead( Range ); @@ -7087,14 +7146,6 @@ TController::adjust_desired_speed_for_limits() { VelDesired, VelSignal ); } - if( ( ( OrderCurrentGet() & Obey_train ) != 0 ) - && ( TrainParams.TTVmax > 0.0 ) ) { - // jesli nie spozniony to nie przekraczać rozkladowej - VelDesired = - min_speed( - VelDesired, - TrainParams.TTVmax ); - } if( mvOccupied->RunningTrack.Velmax >= 0 ) { // ograniczenie prędkości z trajektorii ruchu VelDesired = diff --git a/Driver.h b/Driver.h index b445a27e..d8f8a6fa 100644 --- a/Driver.h +++ b/Driver.h @@ -18,6 +18,7 @@ http://mozilla.org/MPL/2.0/. #include "translation.h" auto const EU07_AI_NOACCELERATION = -0.05; +auto const EU07_AI_SPEEDLIMITEXTENDSBEYONDSCANRANGE = 10000.0; enum TOrders { // rozkazy dla AI diff --git a/Globals.cpp b/Globals.cpp index bc042f2a..dc310c17 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -43,86 +43,81 @@ global_settings::ConfigParse(cParser &Parser) { std::string token; do { - token = ""; Parser.getTokens(); Parser >> token; - if (token == "sceneryfile") + if( ConfigParse_gfx( Parser, token ) ) { + continue; + } + else if (token == "sceneryfile") { - Parser.getTokens(); Parser >> SceneryFile; } else if (token == "humanctrlvehicle") { - Parser.getTokens(); Parser >> local_start_vehicle; } - else if( token == "fieldofview" ) { - - Parser.getTokens( 1, false ); + else if (token == "fieldofview") + { + Parser.getTokens(1, false); Parser >> FieldOfView; // guard against incorrect values - FieldOfView = clamp( FieldOfView, 10.0f, 75.0f ); + FieldOfView = clamp(FieldOfView, 10.0f, 75.0f); } else if (token == "width") { - Parser.getTokens(1, false); Parser >> iWindowWidth; } else if (token == "height") { - Parser.getTokens(1, false); Parser >> iWindowHeight; } - else if (token == "targetfps") - { - Parser.getTokens(1, false); - Parser >> targetfps; - } - else if (token == "basedrawrange") - { - Parser.getTokens(1); - Parser >> BaseDrawRange; - BaseDrawRange = clamp( BaseDrawRange, 500.f, 5000.f ); // arbitrary limits to keep users from hurting themselves - } + else if (token == "targetfps") + { + Parser.getTokens(1, false); + Parser >> targetfps; + } + else if (token == "basedrawrange") + { + Parser.getTokens(1); + Parser >> BaseDrawRange; + BaseDrawRange = clamp(BaseDrawRange, 500.f, + 5000.f); // arbitrary limits to keep users from hurting themselves + } else if (token == "fullscreen") { Parser.getTokens(); Parser >> bFullScreen; } else if (token == "fullscreenmonitor") - { - Parser.getTokens(1, false); - Parser >> fullscreen_monitor; - } - else if( token == "fullscreenwindowed" ) { + { + Parser.getTokens(1, false); + Parser >> fullscreen_monitor; + } + else if (token == "fullscreenwindowed") + { Parser.getTokens(); Parser >> fullscreen_windowed; } - else if( token == "vsync" ) { - + else if (token == "vsync") + { Parser.getTokens(); Parser >> VSync; } else if (token == "freefly") { // Mczapkie-130302 - Parser.getTokens(); Parser >> FreeFlyModeFlag; Parser.getTokens(3, false); - Parser >> - FreeCameraInit[0].x, - FreeCameraInit[0].y, - FreeCameraInit[0].z; + Parser >> FreeCameraInit[0].x, FreeCameraInit[0].y, FreeCameraInit[0].z; } else if (token == "wireframe") { - Parser.getTokens(); Parser >> bWireFrame; } @@ -139,39 +134,45 @@ global_settings::ConfigParse(cParser &Parser) { Parser.getTokens(); Parser >> bSoundEnabled; } - else if( token == "sound.openal.renderer" ) { + else if (token == "sound.openal.renderer") + { // selected device for audio renderer - AudioRenderer = Parser.getToken( false ); // case-sensitive + AudioRenderer = Parser.getToken(false); // case-sensitive } - else if( token == "sound.volume" ) { + else if (token == "sound.volume") + { // selected device for audio renderer Parser.getTokens(); Parser >> AudioVolume; - AudioVolume = clamp( AudioVolume, 0.f, 2.f ); + AudioVolume = clamp(AudioVolume, 0.f, 2.f); } - else if( token == "sound.volume.radio" ) { + else if (token == "sound.volume.radio") + { // selected device for audio renderer Parser.getTokens(); Parser >> RadioVolume; - RadioVolume = clamp( RadioVolume, 0.f, 1.f ); + RadioVolume = clamp(RadioVolume, 0.f, 1.f); } - else if( token == "sound.volume.vehicle" ) { + else if (token == "sound.volume.vehicle") + { // selected device for audio renderer Parser.getTokens(); Parser >> VehicleVolume; - VehicleVolume = clamp( VehicleVolume, 0.f, 1.f ); + VehicleVolume = clamp(VehicleVolume, 0.f, 1.f); } - else if( token == "sound.volume.positional" ) { + else if (token == "sound.volume.positional") + { // selected device for audio renderer Parser.getTokens(); Parser >> EnvironmentPositionalVolume; - EnvironmentPositionalVolume = clamp( EnvironmentPositionalVolume, 0.f, 1.f ); + EnvironmentPositionalVolume = clamp(EnvironmentPositionalVolume, 0.f, 1.f); } - else if( token == "sound.volume.ambient" ) { + else if (token == "sound.volume.ambient") + { // selected device for audio renderer Parser.getTokens(); Parser >> EnvironmentAmbientVolume; - EnvironmentAmbientVolume = clamp( EnvironmentAmbientVolume, 0.f, 1.f ); + EnvironmentAmbientVolume = clamp(EnvironmentAmbientVolume, 0.f, 1.f); } // else if (str==AnsiString("renderalpha")) //McZapkie-1312302 - dwuprzebiegowe renderowanie // bRenderAlpha=(GetNextSymbol().LowerCase()==AnsiString("yes")); @@ -183,7 +184,6 @@ global_settings::ConfigParse(cParser &Parser) { } else if (token == "fullphysics") { // McZapkie-291103 - usypianie fizyki - Parser.getTokens(); Parser >> FullPhysics; } @@ -202,14 +202,16 @@ global_settings::ConfigParse(cParser &Parser) { } else { - iWriteLogEnabled = stol_def(token,3); + iWriteLogEnabled = stol_def(token, 3); } } - else if( token == "multiplelogs" ) { + else if (token == "multiplelogs") + { Parser.getTokens(); Parser >> MultipleLogs; } - else if( token == "logs.filter" ) { + else if (token == "logs.filter") + { Parser.getTokens(); Parser >> DisabledLogTypes; } @@ -219,7 +221,8 @@ global_settings::ConfigParse(cParser &Parser) { Parser.getTokens(2, false); Parser >> fMouseXScale >> fMouseYScale; } - else if( token == "mousecontrol" ) { + else if (token == "mousecontrol") + { // whether control pick mode can be activated Parser.getTokens(); Parser >> InputMouse; @@ -238,7 +241,6 @@ global_settings::ConfigParse(cParser &Parser) { } else if (token == "friction") { // mnożnik tarcia - KURS90 - Parser.getTokens(1, false); Parser >> fFriction; } @@ -266,47 +268,40 @@ global_settings::ConfigParse(cParser &Parser) { // domyślnie od TGA szDefaultExt = szTexturesTGA; } - else { - szDefaultExt = - ( token[ 0 ] == '.' ? - token : - "." + token ); + else + { + szDefaultExt = (token[0] == '.' ? token : "." + token); } } else if (token == "newaircouplers") { - Parser.getTokens(); Parser >> bnewAirCouplers; } - else if( token == "anisotropicfiltering" ) { - - Parser.getTokens( 1, false ); - Parser >> AnisotropicFiltering; - if (AnisotropicFiltering < 1.0f) - AnisotropicFiltering = 1.0f; - } - else if( token == "usevbo" ) + else if (token == "anisotropicfiltering") + { + Parser.getTokens(1, false); + Parser >> AnisotropicFiltering; + if (AnisotropicFiltering < 1.0f) + AnisotropicFiltering = 1.0f; + } + else if (token == "usevbo") { - Parser.getTokens(); Parser >> bUseVBO; } else if (token == "feedbackmode") { - Parser.getTokens(1, false); Parser >> iFeedbackMode; } else if (token == "feedbackport") { - Parser.getTokens(1, false); Parser >> iFeedbackPort; } else if (token == "multiplayer") { - Parser.getTokens(1, false); Parser >> iMultiplayer; } @@ -316,15 +311,15 @@ global_settings::ConfigParse(cParser &Parser) { Parser.getTokens(1, false); int size; Parser >> size; - iMaxTextureSize = clamp_power_of_two( size, 512, 8192 ); + iMaxTextureSize = clamp_power_of_two(size, 512, 8192); } else if (token == "maxcabtexturesize") { // wymuszenie przeskalowania tekstur - Parser.getTokens( 1, false ); + Parser.getTokens(1, false); int size; Parser >> size; - iMaxCabTextureSize = clamp_power_of_two( size, 512, 8192 ); + iMaxCabTextureSize = clamp_power_of_two(size, 512, 8192); } else if (token == "movelight") { @@ -337,156 +332,131 @@ global_settings::ConfigParse(cParser &Parser) { std::tm *localtime = std::localtime(&timenow); fMoveLight = localtime->tm_yday + 1; // numer bieżącego dnia w roku } - simulation::Environment.compute_season( fMoveLight ); + simulation::Environment.compute_season(fMoveLight); } - else if( token == "dynamiclights" ) { + else if (token == "dynamiclights") + { // number of dynamic lights in the scene - Parser.getTokens( 1, false ); + Parser.getTokens(1, false); Parser >> DynamicLightCount; // clamp the light number - // max 8 lights per opengl specs, minus one used for sun. at least one light for controlled vehicle - DynamicLightCount = clamp( DynamicLightCount, 1, 7 ); + // max 8 lights per opengl specs, minus one used for sun. at least one light for + // controlled vehicle + DynamicLightCount = clamp(DynamicLightCount, 1, 7); } - else if( token == "scenario.time.override" ) { + else if (token == "scenario.time.override") + { // shift (in hours) applied to train timetables - Parser.getTokens( 1, false ); - std::string token; - Parser >> token; - std::istringstream stream(token); - if (token.find(':') != -1) { - float a, b; - char s; - stream >> a >> s >> b; - ScenarioTimeOverride = a + b / 60.0; - } - else - stream >> ScenarioTimeOverride; - ScenarioTimeOverride = clamp( ScenarioTimeOverride, 0.f, 24 * 1439 / 1440.f ); + Parser.getTokens(1, false); + std::string token; + Parser >> token; + std::istringstream stream(token); + if (token.find(':') != -1) + { + float a, b; + char s; + stream >> a >> s >> b; + ScenarioTimeOverride = a + b / 60.0; + } + else + stream >> ScenarioTimeOverride; + ScenarioTimeOverride = clamp(ScenarioTimeOverride, 0.f, 24 * 1439 / 1440.f); } - else if( token == "scenario.time.offset" ) { + else if (token == "scenario.time.offset") + { // shift (in hours) applied to train timetables - Parser.getTokens( 1, false ); + Parser.getTokens(1, false); Parser >> ScenarioTimeOffset; } - else if( token == "scenario.time.current" ) { + else if (token == "scenario.time.current") + { // sync simulation time with local clock - Parser.getTokens( 1, false ); + Parser.getTokens(1, false); Parser >> ScenarioTimeCurrent; } - else if( token == "scenario.weather.temperature" ) { + else if (token == "scenario.weather.temperature") + { // selected device for audio renderer Parser.getTokens(); Parser >> AirTemperature; - if( false == DebugModeFlag ) { - AirTemperature = clamp( AirTemperature, -15.f, 45.f ); + if (false == DebugModeFlag) + { + AirTemperature = clamp(AirTemperature, -15.f, 45.f); } } - else if( token == "scalespeculars" ) { - // whether strength of specular highlights should be adjusted (generally needed for legacy 3d models) + else if (token == "scalespeculars") + { + // whether strength of specular highlights should be adjusted (generally needed for + // legacy 3d models) Parser.getTokens(); Parser >> ScaleSpecularValues; } - else if( token == "gfxrenderer" ) { + else if (token == "gfxrenderer") + { // shadow render toggle Parser.getTokens(); Parser >> GfxRenderer; - if( GfxRenderer == "full" ) { + if (GfxRenderer == "full") + { GfxRenderer = "default"; } - BasicRenderer = ( GfxRenderer == "simple" ); - LegacyRenderer = ( GfxRenderer != "default" ); + BasicRenderer = (GfxRenderer == "simple"); + LegacyRenderer = (GfxRenderer != "default"); } - else if( token == "shadows" ) { + else if (token == "shadows") + { // shadow render toggle Parser.getTokens(); Parser >> RenderShadows; - } - else if( token == "shadowtune" ) { - Parser.getTokens( 4, false ); + } + else if (token == "shadowtune") + { + Parser.getTokens(4, false); float discard; - Parser - >> shadowtune.map_size - >> discard - >> shadowtune.range - >> discard; - shadowtune.map_size = clamp_power_of_two( shadowtune.map_size, 512, 8192 ); + Parser >> shadowtune.map_size >> discard >> shadowtune.range >> discard; + shadowtune.map_size = clamp_power_of_two(shadowtune.map_size, 512, 8192); // make sure we actually make effective use of all csm stages shadowtune.range = - std::max( - ( shadowtune.map_size <= 2048 ? - 75.f : - 75.f * shadowtune.map_size / 2048 ), - shadowtune.range ); - } - else if( token == "gfx.shadows.cab.range" ) { - // shadow render toggle - Parser.getTokens(); - Parser >> RenderCabShadowsRange; + std::max((shadowtune.map_size <= 2048 ? 75.f : 75.f * shadowtune.map_size / 2048), + shadowtune.range); } - else if( token == "gfx.smoke" ) { - // smoke visualization toggle - Parser.getTokens(); - Parser >> Smoke; - } - else if( token == "gfx.smoke.fidelity" ) { - // smoke visualization fidelity - float smokefidelity; - Parser.getTokens(); - Parser >> smokefidelity; - SmokeFidelity = clamp( smokefidelity, 1.f, 4.f ); - } - else if( token == "smoothtraction" ) { + else if (token == "smoothtraction") + { // podwójna jasność ambient Parser.getTokens(); Parser >> bSmoothTraction; } - else if( token == "splinefidelity" ) { + else if (token == "splinefidelity") + { // segment size during spline->geometry conversion float splinefidelity; Parser.getTokens(); Parser >> splinefidelity; - SplineFidelity = clamp( splinefidelity, 1.f, 4.f ); + SplineFidelity = clamp(splinefidelity, 1.f, 4.f); } - else if (token == "rendercab") { - Parser.getTokens(); - Parser >> render_cab; - } - else if( token == "createswitchtrackbeds" ) { + else if (token == "rendercab") + { + Parser.getTokens(); + Parser >> render_cab; + } + else if (token == "createswitchtrackbeds") + { // podwójna jasność ambient Parser.getTokens(); Parser >> CreateSwitchTrackbeds; } - else if( token == "gfx.resource.sweep" ) { - - Parser.getTokens(); - Parser >> ResourceSweep; - } - else if( token == "gfx.resource.move" ) { - - Parser.getTokens(); - Parser >> ResourceMove; - } - else if( token == "gfx.reflections.framerate" ) { - - auto const updatespersecond { std::abs( Parser.getToken() ) }; - reflectiontune.update_interval = 1.0 / updatespersecond; - } - else if( token == "gfx.reflections.fidelity" ) { - Parser.getTokens( 1, false ); - Parser >> reflectiontune.fidelity; - reflectiontune.fidelity = clamp( reflectiontune.fidelity, 0, 2 ); - } - else if( token == "timespeed" ) { - // przyspieszenie czasu, zmienna do testów - Parser.getTokens( 1, false ); - Parser >> fTimeSpeed; + else if (token == "timespeed") + { + // przyspieszenie czasu, zmienna do testów + Parser.getTokens(1, false); + Parser >> fTimeSpeed; } else if (token == "multisampling") { // tryb antyaliasingu: 0=brak,1=2px,2=4px Parser.getTokens(1, false); Parser >> iMultisampling; - iMultisampling = clamp( iMultisampling, 0, 3 ); + iMultisampling = clamp(iMultisampling, 0, 3); } else if (token == "latitude") { @@ -500,14 +470,12 @@ global_settings::ConfigParse(cParser &Parser) { Parser.getTokens(1, false); Parser >> iConvertModels; // temporary override, to prevent generation of .e3d not compatible with old exe - iConvertModels = - ( iConvertModels > 128 ? - iConvertModels - 128 : - 0 ); + iConvertModels = (iConvertModels > 128 ? iConvertModels - 128 : 0); } - else if( token == "file.binary.terrain" ) { + else if (token == "file.binary.terrain") + { // binary terrain (de)serialization - Parser.getTokens( 1, false ); + Parser.getTokens(1, false); Parser >> file_binary_terrain; } else if (token == "inactivepause") @@ -557,8 +525,7 @@ global_settings::ConfigParse(cParser &Parser) { in = 5; // na ostatni, bo i tak trzeba pominąć wartości } Parser.getTokens(4, false); - Parser - >> fCalibrateIn[in][0] // wyraz wolny + Parser >> fCalibrateIn[in][0] // wyraz wolny >> fCalibrateIn[in][1] // mnożnik >> fCalibrateIn[in][2] // mnożnik dla kwadratu >> fCalibrateIn[in][3]; // mnożnik dla sześcianu @@ -623,9 +590,8 @@ global_settings::ConfigParse(cParser &Parser) { { // maksymalne wartości jakie można wyświetlić na mierniku Parser.getTokens(7, false); - Parser >> fCalibrateOutMax[0] >> fCalibrateOutMax[1] >> - fCalibrateOutMax[2] >> fCalibrateOutMax[3] >> - fCalibrateOutMax[4] >> fCalibrateOutMax[5] >> + Parser >> fCalibrateOutMax[0] >> fCalibrateOutMax[1] >> fCalibrateOutMax[2] >> + fCalibrateOutMax[3] >> fCalibrateOutMax[4] >> fCalibrateOutMax[5] >> fCalibrateOutMax[6]; } else if (token == "calibrateoutdebuginfo") @@ -659,9 +625,10 @@ global_settings::ConfigParse(cParser &Parser) { Parser.getTokens(1, false); Parser >> iHiddenEvents; } - else if( token == "ai.trainman" ) { + else if (token == "ai.trainman") + { // czy łączyć eventy z torami poprzez nazwę toru - Parser.getTokens( 1, false ); + Parser.getTokens(1, false); Parser >> AITrainman; } else if (token == "pause") @@ -677,283 +644,168 @@ global_settings::ConfigParse(cParser &Parser) { Parser.getTokens(1, false); Parser >> asLang; } - else if( token == "pyscreenrendererpriority" ) + else if (token == "pyscreenrendererpriority") { // old variable, repurposed as update rate of python screen renderer Parser.getTokens(); Parser >> token; - auto const priority { token }; - PythonScreenUpdateRate = ( - priority == "normal" ? 200 : - priority == "lower" ? 500 : - priority == "lowest" ? 1000 : - priority == "off" ? 0 : - stol_def( priority, 200 ) ); + auto const priority{token}; + PythonScreenUpdateRate = + (priority == "normal" ? + 200 : + priority == "lower" ? + 500 : + priority == "lowest" ? 1000 : priority == "off" ? 0 : stol_def(priority, 200)); } - else if( token == "python.updatetime" ) + else if (token == "python.updatetime") { Parser.getTokens(); - Parser >> PythonScreenUpdateRate; + Parser >> PythonScreenUpdateRate; } - else if( token == "uitextcolor" ) { + else if (token == "uitextcolor") + { // color of the ui text. NOTE: will be obsolete once the real ui is in place - Parser.getTokens( 3, false ); - Parser - >> UITextColor.r - >> UITextColor.g - >> UITextColor.b; - glm::clamp( UITextColor, 0.f, 255.f ); + Parser.getTokens(3, false); + Parser >> UITextColor.r >> UITextColor.g >> UITextColor.b; + glm::clamp(UITextColor, 0.f, 255.f); UITextColor = UITextColor / 255.f; UITextColor.a = 1.f; } - else if( token == "ui.bg.opacity" ) { + else if (token == "ui.bg.opacity") + { // czy grupować eventy o tych samych nazwach Parser.getTokens(); Parser >> UIBgOpacity; - UIBgOpacity = clamp( UIBgOpacity, 0.f, 1.f ); + UIBgOpacity = clamp(UIBgOpacity, 0.f, 1.f); } - else if( token == "input.gamepad" ) { + else if (token == "input.gamepad") + { // czy grupować eventy o tych samych nazwach Parser.getTokens(); Parser >> InputGamepad; - } + } #ifdef WITH_UART - else if( token == "uart" ) { + else if (token == "uart") + { uart_conf.enable = true; - Parser.getTokens( 3, false ); - Parser - >> uart_conf.port - >> uart_conf.baud - >> uart_conf.updatetime; + Parser.getTokens(3, false); + Parser >> uart_conf.port >> uart_conf.baud >> uart_conf.updatetime; } - else if( token == "uarttune" ) { - Parser.getTokens( 16 ); - Parser - >> uart_conf.mainbrakemin - >> uart_conf.mainbrakemax - >> uart_conf.localbrakemin - >> uart_conf.localbrakemax - >> uart_conf.tankmax - >> uart_conf.tankuart - >> uart_conf.pipemax - >> uart_conf.pipeuart - >> uart_conf.brakemax - >> uart_conf.brakeuart - >> uart_conf.hvmax - >> uart_conf.hvuart - >> uart_conf.currentmax - >> uart_conf.currentuart - >> uart_conf.lvmax - >> uart_conf.lvuart; + else if (token == "uarttune") + { + Parser.getTokens(16); + Parser >> uart_conf.mainbrakemin >> uart_conf.mainbrakemax >> uart_conf.localbrakemin >> + uart_conf.localbrakemax >> uart_conf.tankmax >> uart_conf.tankuart >> + uart_conf.pipemax >> uart_conf.pipeuart >> uart_conf.brakemax >> + uart_conf.brakeuart >> uart_conf.hvmax >> uart_conf.hvuart >> + uart_conf.currentmax >> uart_conf.currentuart >> uart_conf.lvmax >> + uart_conf.lvuart; } - else if ( token == "uarttachoscale" ) { - Parser.getTokens( 1 ); - Parser >> uart_conf.tachoscale; - } - else if( token == "uartfeature" ) { - Parser.getTokens( 4 ); - Parser - >> uart_conf.mainenable - >> uart_conf.scndenable - >> uart_conf.trainenable - >> uart_conf.localenable; + else if (token == "uarttachoscale") + { + Parser.getTokens(1); + Parser >> uart_conf.tachoscale; } - else if( token == "uartdebug" ) { - Parser.getTokens( 1 ); + else if (token == "uartfeature") + { + Parser.getTokens(4); + Parser >> uart_conf.mainenable >> uart_conf.scndenable >> uart_conf.trainenable >> + uart_conf.localenable; + } + else if (token == "uartdebug") + { + Parser.getTokens(1); Parser >> uart_conf.debug; } #endif #ifdef USE_EXTCAM_CAMERA - else if( token == "extcam.cmd" ) { - Parser.getTokens( 1 ); - Parser >> extcam_cmd; - } - else if( token == "extcam.rec" ) { - Parser.getTokens( 1 ); - Parser >> extcam_rec; - } - else if( token == "extcam.res" ) { - Parser.getTokens( 2 ); - Parser >> extcam_res.x >> extcam_res.y; - } + else if (token == "extcam.cmd") + { + Parser.getTokens(1); + Parser >> extcam_cmd; + } + else if (token == "extcam.rec") + { + Parser.getTokens(1); + Parser >> extcam_rec; + } + else if (token == "extcam.res") + { + Parser.getTokens(2); + Parser >> extcam_res.x >> extcam_res.y; + } #endif - else if (token == "compresstex") { - Parser.getTokens( 1 ); - if( false == gfx_usegles ) { + else if (token == "compresstex") + { + Parser.getTokens(1); + if (false == gfx_usegles) + { // ogl es use requires compression to be disabled Parser >> compress_tex; } - } - else if (token == "gfx.framebuffer.width") - { - Parser.getTokens(1, false); - Parser >> gfx_framebuffer_width; } - else if (token == "gfx.framebuffer.height") - { - Parser.getTokens(1, false); - Parser >> gfx_framebuffer_height; - } - else if (token == "gfx.framebuffer.fidelity") - { - Parser.getTokens(1, false); - Parser >> gfx_framebuffer_fidelity; - } - else if (token == "gfx.shadowmap.enabled") + else if (token == "fpslimit") { Parser.getTokens(1); - Parser >> gfx_shadowmap_enabled; + float fpslimit; + Parser >> fpslimit; + minframetime = std::chrono::duration(1.0f / fpslimit); } - else if (token == "fpslimit") - { - Parser.getTokens(1); - float fpslimit; - Parser >> fpslimit; - minframetime = std::chrono::duration(1.0f / fpslimit); - } - else if (token == "randomseed") - { - Parser.getTokens(1); - Parser >> Global.random_seed; - } - else if (token == "gfx.envmap.enabled") + else if (token == "randomseed") { Parser.getTokens(1); - Parser >> gfx_envmap_enabled; + Parser >> Global.random_seed; } - else if (token == "gfx.postfx.motionblur.enabled") + else if (token == "python.enabled") { Parser.getTokens(1); - Parser >> gfx_postfx_motionblur_enabled; + Parser >> python_enabled; } - else if (token == "gfx.postfx.motionblur.shutter") + else if (token == "python.threadedupload") { Parser.getTokens(1); - Parser >> gfx_postfx_motionblur_shutter; + Parser >> python_threadedupload; } - else if (token == "gfx.postfx.motionblur.format") + else if (token == "python.uploadmain") { Parser.getTokens(1); - std::string token; - Parser >> token; - if (token == "rg16f") - gfx_postfx_motionblur_format = GL_RG16F; - else if (token == "rg32f") - gfx_postfx_motionblur_format = GL_RG32F; + Parser >> python_uploadmain; } - else if (token == "gfx.postfx.chromaticaberration.enabled") + else if (token == "python.mipmaps") { Parser.getTokens(1); - Parser >> gfx_postfx_chromaticaberration_enabled; + Parser >> python_mipmaps; } - else if (token == "gfx.format.color") + else if (token == "network.server") + { + Parser.getTokens(2); + + std::string backend; + std::string conf; + Parser >> backend >> conf; + + network_servers.push_back(std::make_pair(backend, conf)); + } + else if (token == "network.client") + { + Parser.getTokens(2); + + network_client.emplace(); + Parser >> network_client->first; + Parser >> network_client->second; + } + else if (token == "execonexit") { Parser.getTokens(1); - std::string token; - Parser >> token; - if (token == "rgb8") - gfx_format_color = GL_RGB8; - else if (token == "rgb16f") - gfx_format_color = GL_RGB16F; - else if (token == "rgb32f") - gfx_format_color = GL_RGB32F; - else if (token == "r11f_g11f_b10f") - gfx_format_color = GL_R11F_G11F_B10F; - } - else if (token == "gfx.format.depth") - { - Parser.getTokens(1); - std::string token; - Parser >> token; - if (token == "z16") - gfx_format_depth = GL_DEPTH_COMPONENT16; - else if (token == "z24") - gfx_format_depth = GL_DEPTH_COMPONENT24; - else if (token == "z32") - gfx_format_depth = GL_DEPTH_COMPONENT32; - else if (token == "z32f") - gfx_format_depth = GL_DEPTH_COMPONENT32F; - } - else if (token == "gfx.skiprendering") - { - Parser.getTokens(1); - Parser >> gfx_skiprendering; - } - else if (token == "gfx.skippipeline") - { - Parser.getTokens(1); - Parser >> gfx_skippipeline; - } - else if (token == "gfx.extraeffects") - { - Parser.getTokens(1); - Parser >> gfx_extraeffects; + Parser >> exec_on_exit; + std::replace(std::begin(exec_on_exit), std::end(exec_on_exit), '_', ' '); } /* - else if (token == "gfx.usegles") - { - Parser.getTokens(1); - Parser >> gfx_usegles; - if( true == gfx_usegles ) { - compress_tex = false; - gfx_shadergamma = true; - } - } - else if (token == "gfx.shadergamma") - { - Parser.getTokens(1); - Parser >> gfx_shadergamma; - } + else if (token == "crashdamage") { + Parser.getTokens(1); + Parser >> crash_damage; + } */ - else if (token == "python.enabled") - { - Parser.getTokens(1); - Parser >> python_enabled; - } - else if (token == "python.threadedupload") - { - Parser.getTokens(1); - Parser >> python_threadedupload; - } - else if (token == "python.uploadmain") - { - Parser.getTokens(1); - Parser >> python_uploadmain; - } - else if (token == "python.mipmaps") - { - Parser.getTokens(1); - Parser >> python_mipmaps; - } - else if (token == "network.server") - { - Parser.getTokens(2); - - std::string backend; - std::string conf; - Parser >> backend >> conf; - - network_servers.push_back(std::make_pair(backend, conf)); - } - else if (token == "network.client") - { - Parser.getTokens(2); - - network_client.emplace(); - Parser >> network_client->first; - Parser >> network_client->second; - } - else if (token == "execonexit") { - Parser.getTokens(1); - Parser >> exec_on_exit; - std::replace(std::begin(exec_on_exit), std::end(exec_on_exit), '_', ' '); - } -/* - else if (token == "crashdamage") { - Parser.getTokens(1); - Parser >> crash_damage; - } -*/ } while ((token != "") && (token != "endconfig")); //(!Parser->EndOfFile) // na koniec trochę zależności if (!bLoadTraction) // wczytywanie drutów i słupów @@ -971,12 +823,12 @@ global_settings::ConfigParse(cParser &Parser) { // pauzowanie jest zablokowane dla (iMultiplayer&2)>0, więc iMultiplayer=1 da się zapauzować // (tryb instruktora) } -/* - fFpsMin = fFpsAverage - - fFpsDeviation; // dolna granica FPS, przy której promień scenerii będzie zmniejszany - fFpsMax = fFpsAverage + - fFpsDeviation; // górna granica FPS, przy której promień scenerii będzie zwiększany -*/ + /* + fFpsMin = fFpsAverage - + fFpsDeviation; // dolna granica FPS, przy której promień scenerii będzie + zmniejszany fFpsMax = fFpsAverage + fFpsDeviation; // górna granica FPS, przy której promień + scenerii będzie zwiększany + */ if (iPause) iTextMode = GLFW_KEY_F1; // jak pauza, to pokazać zegar @@ -985,6 +837,170 @@ global_settings::ConfigParse(cParser &Parser) { #endif } +bool +global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token ) { + + // TODO: move other graphics switches here + auto tokenparsed { true }; + + if (Token == "gfx.shadows.cab.range") + { + // shadow render toggle + Parser.getTokens(); + Parser >> RenderCabShadowsRange; + } + else if (Token == "gfx.smoke") + { + // smoke visualization toggle + Parser.getTokens(); + Parser >> Smoke; + } + else if (Token == "gfx.smoke.fidelity") + { + // smoke visualization fidelity + float smokefidelity; + Parser.getTokens(); + Parser >> smokefidelity; + SmokeFidelity = clamp(smokefidelity, 1.f, 4.f); + } + else if (Token == "gfx.resource.sweep") + { + Parser.getTokens(); + Parser >> ResourceSweep; + } + else if (Token == "gfx.resource.move") + { + Parser.getTokens(); + Parser >> ResourceMove; + } + else if (Token == "gfx.reflections.framerate") + { + auto const updatespersecond{std::abs(Parser.getToken())}; + reflectiontune.update_interval = 1.0 / updatespersecond; + } + else if (Token == "gfx.reflections.fidelity") + { + Parser.getTokens(1, false); + Parser >> reflectiontune.fidelity; + reflectiontune.fidelity = clamp(reflectiontune.fidelity, 0, 2); + } + else if (Token == "gfx.framebuffer.width") + { + Parser.getTokens(1, false); + Parser >> gfx_framebuffer_width; + } + else if (Token == "gfx.framebuffer.height") + { + Parser.getTokens(1, false); + Parser >> gfx_framebuffer_height; + } + else if (Token == "gfx.framebuffer.fidelity") + { + Parser.getTokens(1, false); + Parser >> gfx_framebuffer_fidelity; + } + else if (Token == "gfx.shadowmap.enabled") + { + Parser.getTokens(1); + Parser >> gfx_shadowmap_enabled; + } + else if (Token == "gfx.envmap.enabled") + { + Parser.getTokens(1); + Parser >> gfx_envmap_enabled; + } + else if (Token == "gfx.postfx.motionblur.enabled") + { + Parser.getTokens(1); + Parser >> gfx_postfx_motionblur_enabled; + } + else if (Token == "gfx.postfx.motionblur.shutter") + { + Parser.getTokens(1); + Parser >> gfx_postfx_motionblur_shutter; + } + else if (Token == "gfx.postfx.motionblur.format") + { + Parser.getTokens(1); + std::string value; + Parser >> value; + if (value == "rg16f") + gfx_postfx_motionblur_format = GL_RG16F; + else if (value == "rg32f") + gfx_postfx_motionblur_format = GL_RG32F; + } + else if (Token == "gfx.postfx.chromaticaberration.enabled") + { + Parser.getTokens(1); + Parser >> gfx_postfx_chromaticaberration_enabled; + } + else if (Token == "gfx.format.color") + { + Parser.getTokens(1); + std::string value; + Parser >> value; + if (value == "rgb8") + gfx_format_color = GL_RGB8; + else if (value == "rgb16f") + gfx_format_color = GL_RGB16F; + else if (value == "rgb32f") + gfx_format_color = GL_RGB32F; + else if (value == "r11f_g11f_b10f") + gfx_format_color = GL_R11F_G11F_B10F; + } + else if (Token == "gfx.format.depth") + { + Parser.getTokens(1); + std::string value; + Parser >> value; + if (value == "z16") + gfx_format_depth = GL_DEPTH_COMPONENT16; + else if (value == "z24") + gfx_format_depth = GL_DEPTH_COMPONENT24; + else if (value == "z32") + gfx_format_depth = GL_DEPTH_COMPONENT32; + else if (value == "z32f") + gfx_format_depth = GL_DEPTH_COMPONENT32F; + } + else if (Token == "gfx.skiprendering") + { + Parser.getTokens(1); + Parser >> gfx_skiprendering; + } + else if (Token == "gfx.skippipeline") + { + Parser.getTokens(1); + Parser >> gfx_skippipeline; + } + else if (Token == "gfx.extraeffects") + { + Parser.getTokens(1); + Parser >> gfx_extraeffects; + } + /* + else if (Token == "gfx.usegles") + { + Parser.getTokens(1); + Parser >> gfx_usegles; + if( true == gfx_usegles ) { + compress_tex = false; + gfx_shadergamma = true; + } + } + */ + else if (Token == "gfx.shadergamma") + { + Parser.getTokens(1); + Parser >> gfx_shadergamma; + } + else + { + tokenparsed = false; + } + + return tokenparsed; +} + void global_settings::export_as_text( std::ostream &Output ) const { @@ -1194,6 +1210,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.skiprendering", gfx_skiprendering ); export_as_text( Output, "gfx.skippipeline", gfx_skippipeline ); export_as_text( Output, "gfx.extraeffects", gfx_extraeffects ); + export_as_text( Output, "gfx.shadergamma", gfx_shadergamma ); export_as_text( Output, "python.enabled", python_enabled ); export_as_text( Output, "python.threadedupload", python_threadedupload ); export_as_text( Output, "python.uploadmain", python_uploadmain ); diff --git a/Globals.h b/Globals.h index 06cdd629..72be273b 100644 --- a/Globals.h +++ b/Globals.h @@ -237,6 +237,7 @@ struct global_settings { // methods void LoadIniFile( std::string asFileName ); void ConfigParse( cParser &parser ); + bool ConfigParse_gfx( cParser &parser, std::string_view const Token ); // sends basic content of the class in legacy (text) format to provided stream void export_as_text( std::ostream &Output ) const; diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 04bdf734..0434ad35 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -3485,7 +3485,7 @@ void TMoverParameters::MainSwitch_( bool const State ) { return; } - bool const initialstate { Mains || dizel_startup }; + bool const initialstate { Mains }; if( ( false == State ) || ( true == MainSwitchCheck() ) ) { diff --git a/drivermode.cpp b/drivermode.cpp index a3b7646a..a927f18c 100644 --- a/drivermode.cpp +++ b/drivermode.cpp @@ -464,7 +464,9 @@ driver_mode::on_key( int const Key, int const Scancode, int const Action, int co void driver_mode::on_char( unsigned int const Char ) { - // TODO: implement + + // give the ui first shot at the input processing... + if( true == m_userinterface->on_char( Char ) ) { return; } } void diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 15e95f29..34614998 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -63,22 +63,54 @@ drivingaid_panel::update() { gradetext = m_buffer.data(); } // next speed limit - auto const speedlimit { static_cast( std::floor( owner->VelDesired ) ) }; + auto const speedlimit { static_cast( owner->VelDesired ) }; auto nextspeedlimit { speedlimit }; - auto nextspeedlimitdistance { 0.0 }; - if( speedlimit != 0 ) { - // if we aren't allowed to move then any next speed limit is irrelevant - if( ( owner->VelLimitLastDist.first > 0.0 ) && ( owner->VelLimitLastDist.second > 0.0 ) ) { - // first take note of any speed change which should occur after passing potential current speed limit - nextspeedlimit = static_cast( std::floor( owner->VelLimitLastDist.first ) ); + auto nextspeedlimitdistance { std::numeric_limits::max() }; + if( speedlimit != 0 ) { // if we aren't allowed to move then any next speed limit is irrelevant + // nie przekraczać rozkladowej + auto const schedulespeedlimit { ( + ( ( owner->OrderCurrentGet() & ( Obey_train | Bank ) ) != 0 ) && ( owner->TrainParams.TTVmax > 0.0 ) ? static_cast( owner->TrainParams.TTVmax ) : + ( ( owner->OrderCurrentGet() & ( Obey_train | Bank ) ) == 0 ) ? static_cast( owner->fShuntVelocity ) : + -1 ) }; + // first take note of any speed change which should occur after passing potential current speed limit + if( owner->VelLimitLastDist.second > 0 ) { + nextspeedlimit = min_speed( schedulespeedlimit, static_cast( owner->VelLimitLastDist.first ) ); nextspeedlimitdistance = owner->VelLimitLastDist.second; } - auto const speedatproximitydistance{ static_cast( std::floor( owner->VelNext ) ) }; - if( ( speedatproximitydistance != speedlimit ) && ( speedatproximitydistance < nextspeedlimit ) ) { - // if there's speed reduction down the road then it's more important than any potential speedup + // then take into account speed change ahead, compare it with speed after potentially clearing last limit + // lower of these two takes priority; otherwise limit lasts at least until potential last limit is cleared + auto const noactivespeedlimit { owner->VelLimitLastDist.second < 0 }; + auto const speedatproximitydistance { min_speed( schedulespeedlimit, static_cast( owner->VelNext ) ) }; + if( speedatproximitydistance == nextspeedlimit ) { + if( noactivespeedlimit ) { + nextspeedlimit = speedatproximitydistance; + nextspeedlimitdistance = owner->ActualProximityDist; + } + } + else if( speedatproximitydistance < nextspeedlimit ) { + // if the speed limit ahead is more strict than our current limit, it's important enough to report + if( speedatproximitydistance < owner->VelDesired ) { + nextspeedlimit = speedatproximitydistance; + nextspeedlimitdistance = owner->ActualProximityDist; + } + // otherwise report it only if it's located after our current (lower) limit ends + else if( owner->ActualProximityDist > nextspeedlimitdistance ) { + nextspeedlimit = speedatproximitydistance; + nextspeedlimitdistance = owner->ActualProximityDist; + } + } + else if( noactivespeedlimit ) { // implicit proximity > last, report only if last limit isn't present nextspeedlimit = speedatproximitydistance; nextspeedlimitdistance = owner->ActualProximityDist; } + // HACK: if our current speed limit extends beyond our scan range don't display potentially misleading information about its length + if( nextspeedlimitdistance >= EU07_AI_SPEEDLIMITEXTENDSBEYONDSCANRANGE ) { + nextspeedlimit = speedlimit; + } + // HACK: hide next speed limit if the 'limit' is a vehicle in front of us + else if( owner->ActualProximityDist == std::abs( owner->TrackObstacle() ) ) { + nextspeedlimit = speedlimit; + } } std::string nextspeedlimittext; if( nextspeedlimit != speedlimit ) { @@ -590,10 +622,7 @@ debug_panel::render() { render_section( "Vehicle AI", m_ailines ); render_section( "Vehicle Scan Table", m_scantablelines ); render_section_scenario(); - if( true == render_section( "Scenario Event Queue", m_eventqueuelines ) ) { - // event queue filter - ImGui::Checkbox( "By This Vehicle Only", &m_eventqueueactivevehicleonly ); - } + render_section_eventqueue(); if( true == render_section( "Power Grid", m_powergridlines ) ) { // traction state debug ImGui::Checkbox( "Debug Traction", &DebugTractionFlag ); @@ -693,6 +722,22 @@ debug_panel::render_section_scenario() { return true; } +bool +debug_panel::render_section_eventqueue() { + + if( false == ImGui::CollapsingHeader( "Scenario Event Queue" ) ) { return false; } + // event queue name filter + ImGui::PushItemWidth( -1 ); + ImGui::InputTextWithHint( "", "Search event queue", m_eventsearch.data(), m_eventsearch.size() ); + ImGui::PopItemWidth(); + // event queue + render_section( m_eventqueuelines ); + // event queue activator filter + ImGui::Checkbox( "By This Vehicle Only", &m_eventqueueactivevehicleonly ); + + return true; +} + void debug_panel::update_section_vehicle( std::vector &Output ) { @@ -1061,6 +1106,15 @@ debug_panel::update_section_ai( std::vector &Output ) { textline = "Distances:\n proximity: " + to_string( mechanik.ActualProximityDist, 0 ) + ", braking: " + to_string( mechanik.fBrakeDist, 0 ); + if( mechanik.VelLimitLastDist.second > 0 ) { + textline += ", last limit: " + ( + mechanik.VelLimitLastDist.second < EU07_AI_SPEEDLIMITEXTENDSBEYONDSCANRANGE ? + to_string( mechanik.VelLimitLastDist.second, 0 ) : + "???" ); + } + if( mechanik.SwitchClearDist > 0 ) { + textline += ", switches: " + to_string( mechanik.SwitchClearDist, 0 ); + } if( mechanik.Obstacle.distance < 5000 ) { textline += @@ -1116,7 +1170,7 @@ debug_panel::update_section_ai( std::vector &Output ) { textline = "Brakes:\n highest pressure: " + to_string( mechanik.fReady, 2 ) + ( mechanik.Ready ? " (all brakes released)" : "" ) + "\n activation threshold: " + to_string( mechanik.fAccThreshold, 2 ) - + "\n activation delays: " + to_string( mechanik.fBrake_a0[ 0 ], 2 ) + " + " + to_string( mechanik.fBrake_a1[ 0 ], 2 ) + + ", delays: " + to_string( mechanik.fBrake_a0[ 0 ], 2 ) + " + " + to_string( mechanik.fBrake_a1[ 0 ], 2 ) + "\n virtual brake position: " + to_string( mechanik.BrakeCtrlPosition, 2 ); Output.emplace_back( textline, Global.UITextColor ); @@ -1188,6 +1242,7 @@ debug_panel::update_section_eventqueue( std::vector &Output ) { // current event queue auto const time { Timer::GetTime() }; auto const *event { simulation::Events.begin() }; + auto const searchfilter { std::string( m_eventsearch.data() ) }; Output.emplace_back( "Delay: Event:", Global.UITextColor ); @@ -1199,18 +1254,29 @@ debug_panel::update_section_eventqueue( std::vector &Output ) { && ( ( false == m_eventqueueactivevehicleonly ) || ( event->m_activator == m_input.vehicle ) ) ) { + if( ( false == searchfilter.empty() ) + && ( event->m_name.find( searchfilter ) == std::string::npos ) ) { + event = event->m_next; + continue; + } + auto const delay { " " + to_string( std::max( 0.0, event->m_launchtime - time ), 1 ) }; - textline = delay.substr( delay.length() - 6 ) - + " " + event->m_name - + ( event->m_activator ? " (by: " + event->m_activator->asName + ")" : "" ) - + ( event->m_sibling ? " (joint event)" : "" ); + auto const label { event->m_name + ( event->m_activator ? " (by: " + event->m_activator->asName + ")" : "" ) }; + textline = + delay.substr( delay.length() - 6 ) + + " " + + label + ( event->m_sibling ? " (joint event)" : "" ); Output.emplace_back( textline, Global.UITextColor ); } event = event->m_next; } if( Output.size() == 1 ) { - Output.front().data = "(no queued events)"; + // event queue can be empty either because no event got through active filters, or because it is genuinely empty + Output.front().data = ( + simulation::Events.begin() == nullptr ? + "(no queued events)" : + "(no matching events)" ); } } @@ -1328,6 +1394,12 @@ debug_panel::render_section( std::string const &Header, std::vector c if( true == Lines.empty() ) { return false; } if( false == ImGui::CollapsingHeader( Header.c_str() ) ) { return false; } + return render_section( Lines ); +} + +bool +debug_panel::render_section( std::vector const &Lines ) { + for( auto const &line : Lines ) { ImGui::PushStyleColor( ImGuiCol_Text, { line.color.r, line.color.g, line.color.b, line.color.a } ); ImGui::TextUnformatted( line.data.c_str() ); diff --git a/driveruipanels.h b/driveruipanels.h index f9e78575..4c8bcb97 100644 --- a/driveruipanels.h +++ b/driveruipanels.h @@ -95,10 +95,13 @@ private: std::string update_vehicle_brake() const; // renders provided lines, under specified collapsing header bool render_section( std::string const &Header, std::vector const &Lines ); + bool render_section( std::vector const &Lines ); bool render_section_scenario(); + bool render_section_eventqueue(); bool render_section_settings(); // members std::array m_buffer; + std::array m_eventsearch; input_data m_input; std::vector m_vehiclelines, diff --git a/editormode.cpp b/editormode.cpp index 8b92506d..71631eca 100644 --- a/editormode.cpp +++ b/editormode.cpp @@ -105,7 +105,7 @@ editor_mode::enter() { m_statebackup = { Global.pCamera, FreeFlyModeFlag, Global.ControlPicking }; - Global.pCamera = Camera; + Camera = Global.pCamera; FreeFlyModeFlag = true; Global.ControlPicking = true; EditorModeFlag = true; diff --git a/opengl33renderer.cpp b/opengl33renderer.cpp index df8eda44..8b9e7ef6 100644 --- a/opengl33renderer.cpp +++ b/opengl33renderer.cpp @@ -386,31 +386,31 @@ bool opengl33_renderer::init_viewport(viewport_config &vp) vp.msaa_fb->attach(*vp.msaa_rbc, GL_COLOR_ATTACHMENT0); vp.msaa_fb->attach(*vp.msaa_rbd, GL_DEPTH_ATTACHMENT); + vp.main_tex = std::make_unique(); + vp.main_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE); + + vp.main_fb = std::make_unique(); + vp.main_fb->attach(*vp.main_tex, GL_COLOR_ATTACHMENT0); + if (Global.gfx_postfx_motionblur_enabled) { vp.msaa_rbv = std::make_unique(); vp.msaa_rbv->alloc(Global.gfx_postfx_motionblur_format, vp.width, vp.height, samples); vp.msaa_fb->attach(*vp.msaa_rbv, GL_COLOR_ATTACHMENT1); - vp.main_tex = std::make_unique(); - vp.main_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE); - - vp.main_fb = std::make_unique(); - vp.main_fb->attach(*vp.main_tex, GL_COLOR_ATTACHMENT0); - vp.main_texv = std::make_unique(); vp.main_texv->alloc_rendertarget(Global.gfx_postfx_motionblur_format, GL_RG, vp.width, vp.height); vp.main_fb->attach(*vp.main_texv, GL_COLOR_ATTACHMENT1); vp.main_fb->setup_drawing(2); - if( !vp.main_fb->is_complete() ) { - ErrorLog( "main framebuffer setup failed" ); - return false; - } - WriteLog("motion blur enabled"); } + if( !vp.main_fb->is_complete() ) { + ErrorLog( "main framebuffer setup failed" ); + return false; + } + if( !vp.msaa_fb->is_complete() ) { ErrorLog( "msaa framebuffer setup failed" ); return false; @@ -782,14 +782,16 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) if (Global.gfx_postfx_motionblur_enabled) { gl::program::unbind(); - vp.main_fb->clear(GL_COLOR_BUFFER_BIT); + vp.main_fb->clear(GL_COLOR_BUFFER_BIT); vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0); vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT1); model_ubs.param[0].x = m_framerate / (1.0 / Global.gfx_postfx_motionblur_shutter); model_ubo->update(model_ubs); m_pfx_motionblur->apply({vp.main_tex.get(), vp.main_texv.get()}, vp.main2_fb.get()); - } + + vp.main_fb->setup_drawing(1); // restore draw buffers after blit operation + } else { vp.main2_fb->clear(GL_COLOR_BUFFER_BIT); @@ -802,6 +804,7 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) glViewport(0, 0, target_size.x, target_size.y); if( Global.gfx_postfx_chromaticaberration_enabled ) { + // NOTE: for some unexplained reason need this setup_drawing() call here for the tonemapping effects to show up on the main_tex? m_pfx_tonemapping->apply( *vp.main2_tex, vp.main_fb.get() ); m_pfx_chromaticaberration->apply( *vp.main_tex, nullptr ); } @@ -3844,7 +3847,7 @@ void opengl33_renderer::Render_Alpha(TSubModel *Submodel) auto lightcolor = glm::vec3(Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override Submodel->f4Diffuse : Submodel->DiffuseOverride); - lightcolor = glm::pow( lightcolor, gammacorrection ); +// lightcolor = glm::pow( lightcolor, gammacorrection ); m_freespot_shader->bind(); From 195b7cb17b84ab568a0cdaab4ba90ebb9574469c Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Wed, 24 Mar 2021 22:00:23 +0100 Subject: [PATCH 28/63] build 210324. event queue text filter enhancement, contextual uncoupling sounds, parameter support for python screens, radio and odometer state exposed to python scripts, configurable audio volume when paused, skydome color calculation tweak, fog color calculation tweak, doorstep cab control enhancement, tempomat cab control enhancement, pantograph selector preset list support, minor bug fixes --- Driver.cpp | 102 ++++++++------ Driver.h | 2 + DynObj.cpp | 73 ++++++---- Gauge.h | 8 ++ Globals.cpp | 7 + Globals.h | 1 + McZapkie/MOVER.h | 3 +- McZapkie/Mover.cpp | 40 ++++-- Train.cpp | 287 ++++++++++++++++++++++++-------------- Train.h | 32 +++-- audiorenderer.cpp | 2 +- driveruipanels.cpp | 7 +- driveruipanels.h | 3 +- simulationenvironment.cpp | 7 +- skydome.cpp | 8 +- uilayer.cpp | 1 + version.h | 2 +- 17 files changed, 383 insertions(+), 202 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 4fb16a2a..fd24f51b 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -964,7 +964,9 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN VelLimitLastDist.second = d + point.trTrack->Length() + fLength; } else if( VelLimitLastDist.second > 0 ) { // the speed limit can potentially start afterwards, so don't mark it as broken too soon - speedlimitiscontinuous = false; + if( ( point.iFlags & ( spSwitch | spPassengerStopPoint ) ) == 0 ) { + speedlimitiscontinuous = false; + } } if( false == railwaytrackend ) { continue; // i tyle wystarczy @@ -1213,7 +1215,7 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub //jeśli nie widzi następnego sygnału ustawia dotychczasową eSignNext = Point.evEvent; } - if( mvOccupied->Vel > 0.3 ) { + if( mvOccupied->Vel > EU07_AI_MOVEMENT ) { // jeśli jedzie (nie trzeba czekać, aż się drgania wytłumią - drzwi zamykane od 1.0) to będzie zatrzymanie Point.fVelNext = 0; // potentially announce pending stop @@ -1540,7 +1542,7 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo Velocity = 0.0; } else { - VelSignalNext - Point.fVelNext; + VelSignalNext = Point.fVelNext; if( Velocity < 0 ) { Velocity = fVelMax; VelSignal = fVelMax; @@ -2510,10 +2512,8 @@ bool TController::CheckVehicles(TOrders user) */ } - // Ra 2014-09: tymczasowo prymitywne ustawienie warunku pod kątem SN61 - if( ( is_emu() ) - || ( is_dmu() ) - || ( iVehicles == 1 ) ) { + // detect push-pull train configurations and mark them accordingly + if( pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::control ) ) { // zmiana czoła przez zmianę kabiny iDrivigFlags |= movePushPull; } @@ -2540,7 +2540,7 @@ void TController::Lights(int head, int rear) void TController::DirectionInitial() { // ustawienie kierunku po wczytaniu trainset (może jechać na wstecznym mvOccupied->CabActivisation(); // załączenie rozrządu (wirtualne kabiny) - if (mvOccupied->Vel > 0.0) + if (mvOccupied->Vel > EU07_AI_NOMOVEMENT) { // jeśli na starcie jedzie iDirection = iDirectionOrder = (mvOccupied->V > 0 ? 1 : -1); // początkowa prędkość wymusza kierunek jazdy @@ -2825,7 +2825,7 @@ bool TController::ReleaseEngine() { } */ // don't bother with the rest until we're standing still - if( mvOccupied->Vel > 0.01 ) { return false; } + if( mvOccupied->Vel > EU07_AI_NOMOVEMENT ) { return false; } LastReactionTime = 0.0; ReactionTime = PrepareTime; @@ -3248,7 +3248,7 @@ bool TController::DecBrakeEIM() { case 0: { if( mvOccupied->MED_amax != 9.81 ) { - auto const desiredacceleration { ( mvOccupied->Vel > 0.01 ? AccDesired : std::max( 0.0, AccDesired ) ) }; + auto const desiredacceleration { ( mvOccupied->Vel > EU07_AI_NOMOVEMENT ? AccDesired : std::max( 0.0, AccDesired ) ) }; auto const brakeposition { clamp( -1.0 * mvOccupied->AIHintLocalBrakeAccFactor * desiredacceleration / mvOccupied->MED_amax, 0.0, 1.0 ) }; OK = ( brakeposition != mvOccupied->LocalBrakePosA ); mvOccupied->LocalBrakePosA = brakeposition; @@ -3684,7 +3684,7 @@ void TController::SpeedSet() { else { // jak ma jechać if( fActionTime < 0.0 ) { break; } - if( fReady > ( mvOccupied->Vel > 5.0 ? 0.5 : 0.4 ) ) { break; } + if( false == Ready ) { break; } if( mvOccupied->DirActive > 0 ) { mvOccupied->DirectionForward(); //żeby EN57 jechały na drugiej nastawie @@ -4210,7 +4210,7 @@ void TController::Doors( bool const Open, int const Side ) { if( ( true == ismanualdoor ) && ( ( vehicle->LoadExchangeTime() == 0.f ) - || ( vehicle->MoverParameters->Vel > 0.1 ) ) ) { + || ( vehicle->MoverParameters->Vel > EU07_AI_MOVEMENT ) ) ) { vehicle->MoverParameters->OperateDoors( side::right, false, range_t::local ); vehicle->MoverParameters->OperateDoors( side::left, false, range_t::local ); } @@ -4535,7 +4535,7 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N OrderNext(o); // to samo robić po zmianie else if (!o) // jeśli wcześniej było czekanie OrderNext(Shunt); // to dalej jazda manewrowa - if (mvOccupied->Vel >= 1.0) // jeśli jedzie + if (mvOccupied->Vel >= EU07_AI_MOVEMENT) // jeśli jedzie iDrivigFlags &= ~moveStartHorn; // to bez trąbienia po ruszeniu z zatrzymania // Change_direction wykona się samo i następnie przejdzie do kolejnej komendy return true; @@ -4795,7 +4795,7 @@ TController::Update( double const Timedelta ) { auto const awarenessrange { std::max( 750.0, - mvOccupied->Vel > 5.0 ? + mvOccupied->Vel > EU07_AI_MOVEMENT ? 400 + fBrakeDist : 30.0 * fDriverDist ) }; // 1500m dla stojących pociągów; if( is_active() ) { @@ -4893,8 +4893,9 @@ TController::PrepareDirection() { if( iDirection == 0 ) { // jeśli nie ma ustalonego kierunku - if( mvOccupied->Vel < 0.01 ) { // ustalenie kierunku, gdy stoi + if( mvOccupied->Vel < EU07_AI_NOMOVEMENT ) { // ustalenie kierunku, gdy stoi iDirection = mvOccupied->CabActive; // wg wybranej kabiny +/* if( iDirection == 0 ) { // jeśli nie ma ustalonego kierunku if( mvOccupied->Couplers[ end::rear ].Connected == nullptr ) { @@ -4906,6 +4907,7 @@ TController::PrepareDirection() { iDirection = 1; // jazda w kierunku sprzęgu 0 } } +*/ } else { // ustalenie kierunku, gdy jedzie @@ -5354,7 +5356,7 @@ TCommandType TController::BackwardScan( double const Range ) } else { if( ( scandist > fMinProximityDist ) - && ( ( mvOccupied->Vel > 0.0 ) + && ( ( mvOccupied->Vel > EU07_AI_NOMOVEMENT ) && ( ( OrderCurrentGet() & ( Shunt | Loose_shunt ) ) == 0 ) ) ) { // jeśli semafor jest daleko, a pojazd jedzie, to informujemy o zmianie prędkości // jeśli jedzie manewrowo, musi dostać SetVelocity, żeby sie na pociągowy przełączył @@ -5393,12 +5395,12 @@ TCommandType TController::BackwardScan( double const Range ) if (move ? true : e->input_command() == TCommandType::cm_ShuntVelocity) { // jeśli powyżej było SetVelocity 0 0, to dociągamy pod S1 if ((scandist > fMinProximityDist) && - (mvOccupied->Vel > 0.0) || (vmechmax == 0.0) ) + (mvOccupied->Vel > EU07_AI_NOMOVEMENT) || (vmechmax == 0.0) ) { // jeśli tarcza jest daleko, to: //- jesli pojazd jedzie, to informujemy o zmianie prędkości //- jeśli stoi, to z własnej inicjatywy może podjechać pod zamkniętą // tarczę - if (mvOccupied->Vel > 0.0) // tylko jeśli jedzie + if (mvOccupied->Vel > EU07_AI_NOMOVEMENT) // tylko jeśli jedzie { // Mechanik->PutCommand("SetProximityVelocity",scandist,vmechmax,sl); #if LOGBACKSCAN // WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist)+" @@ -5907,7 +5909,7 @@ TController::determine_consist_state() { fAccGravity /= fMass; { auto absaccs { fAccGravity }; // Ra 2014-03: jesli skład stoi, to działa na niego składowa styczna grawitacji - if( mvOccupied->Vel > 0.01 ) { + if( mvOccupied->Vel > EU07_AI_NOMOVEMENT ) { absaccs = 0; auto *d = pVehicles[ end::front ]; // pojazd na czele składu while( d ) { @@ -5939,7 +5941,7 @@ TController::determine_consist_state() { if( has_diesel_engine() ) { Ready = ( - ( vehicle->Vel > 0.5 ) // already moving + ( vehicle->Vel > EU07_AI_MOVEMENT ) // already moving || ( false == vehicle->Mains ) // deadweight vehicle || ( vehicle->enrot > 0.8 * ( vehicle->EngineType == TEngineType::DieselEngine ? @@ -6001,7 +6003,7 @@ TController::control_pantographs() { || ( is_emu() ) // special case || ( mvControlling->TrainType == dt_ET41 ) }; // special case - if( mvOccupied->Vel > 0.05 ) { + if( mvOccupied->Vel > EU07_AI_NOMOVEMENT ) { if( ( fOverhead2 >= 0.0 ) || iOverheadZero ) { // jeśli jazda bezprądowa albo z opuszczonym pantografem @@ -6116,7 +6118,7 @@ TController::control_horns( double const Timedelta ) { cue_action( locale::string::driver_hint_hornoff ); // a tu się kończy } } - if( mvOccupied->Vel >= 5.0 ) { + if( mvOccupied->Vel > EU07_AI_MOVEMENT ) { // jesli jedzie, można odblokować trąbienie, bo się wtedy nie włączy iDrivigFlags &= ~moveStartHornDone; // zatrąbi dopiero jak następnym razem stanie iDrivigFlags |= moveStartHorn; // i trąbić przed następnym ruszeniem @@ -6292,7 +6294,7 @@ TController::control_doors() { if( false == doors_open() ) { return; } // jeżeli jedzie // nie zamykać drzwi przy drganiach, bo zatrzymanie na W4 akceptuje niewielkie prędkości - if( mvOccupied->Vel > 1.0 ) { + if( mvOccupied->Vel > EU07_AI_MOVEMENT ) { Doors( false ); return; } @@ -6600,25 +6602,30 @@ TController::check_departure() { void TController::UpdateChangeDirection() { // TODO: rework into driver mode independent routine - if( ( true == AIControllFlag) - && ( true == TestFlag( OrderCurrentGet(), Change_direction ) ) ) { + if( false == TestFlag( OrderCurrentGet(), Change_direction ) ) { return; } + + if( true == AIControllFlag ) { // sprobuj zmienic kierunek (może być zmieszane z jeszcze jakąś komendą) - if( mvOccupied->Vel < 0.1 ) { + if( mvOccupied->Vel < EU07_AI_NOMOVEMENT ) { // jeśli się zatrzymał, to zmieniamy kierunek jazdy, a nawet kabinę/człon Activation(); // ustawienie zadanego wcześniej kierunku i ewentualne przemieszczenie AI - PrepareEngine(); - JumpToNextOrder(); // następnie robimy, co jest do zrobienia (Shunt albo Obey_train) - if( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect ) ) { - // jeśli dalej mamy manewry - if( false == TestFlag( iDrivigFlags, moveStopHere ) ) { - // o ile nie ma stać w miejscu, - // jechać od razu w przeciwną stronę i nie trąbić z tego tytułu: - iDrivigFlags &= ~moveStartHorn; - SetVelocity( fShuntVelocity, fShuntVelocity ); - } + } // Change_direction (tylko dla AI) + } + // shared part of the routine, implement if direction matches what was requested + if( ( mvOccupied->Vel < EU07_AI_NOMOVEMENT ) + && ( iDirection == iDirectionOrder ) ) { + PrepareEngine(); + JumpToNextOrder(); // następnie robimy, co jest do zrobienia (Shunt albo Obey_train) + if( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect ) ) { + // jeśli dalej mamy manewry + if( false == TestFlag( iDrivigFlags, moveStopHere ) ) { + // o ile nie ma stać w miejscu, + // jechać od razu w przeciwną stronę i nie trąbić z tego tytułu: + iDrivigFlags &= ~moveStartHorn; + SetVelocity( fShuntVelocity, fShuntVelocity ); } } - } // Change_direction (tylko dla AI) + } } void @@ -7116,7 +7123,7 @@ TController::adjust_desired_speed_for_obstacles() { } } ReactionTime = ( - mvOccupied->Vel > 0.01 ? + mvOccupied->Vel > EU07_AI_NOMOVEMENT ? 0.1 : // orientuj się, bo jest goraco 2.0 ); // we're already standing still, so take it easy } @@ -7225,7 +7232,7 @@ TController::adjust_desired_speed_for_target_speed( double const Range ) { && ( ActualProximityDist <= Range ) && ( vel >= VelNext ) ) { // gdy zbliża się i jest za szybki do nowej prędkości, albo stoi na zatrzymaniu - if (vel > 0.0) { + if (vel > EU07_AI_NOMOVEMENT) { // jeśli jedzie if( ( vel < VelNext ) && ( ActualProximityDist > fMaxProximityDist * ( 1.0 + 0.1 * vel ) ) ) { @@ -7402,7 +7409,7 @@ TController::adjust_desired_speed_for_current_speed() { clamp( VelDesired - speedestimate, 0.0, fVelMinus ) / fVelMinus ) ); } // final tweaks - if( vel > 0.1 ) { + if( vel > EU07_AI_NOMOVEMENT ) { // going downhill also take into account impact of gravity AccDesired -= fAccGravity; // HACK: if the max allowed speed was exceeded something went wrong; brake harder @@ -7450,7 +7457,7 @@ TController::adjust_desired_speed_for_current_speed() { if( vel < VelDesired ) { // don't adjust acceleration when going above current target speed if( -AccDesired * BrakeAccFactor() < ( - ( ( fReady > ( IsHeavyCargoTrain ? 0.4 : ( mvOccupied->Vel > 5.0 ) ? 0.45 : 0.4 ) ) + ( ( false == Ready ) || ( VelNext > vel - 40.0 ) ) ? fBrake_a0[ 0 ] * 0.8 : -fAccThreshold ) @@ -7483,7 +7490,7 @@ TController::control_tractive_and_braking_force() { // if the radio-stop was issued don't waste effort trying to fight it if( ( true == mvOccupied->RadioStopFlag ) // radio-stop - && ( mvOccupied->Vel > 0.0 ) ) { // and still moving + && ( mvOccupied->Vel > EU07_AI_NOMOVEMENT ) ) { // and still moving cue_action( locale::string::driver_hint_mastercontrollersetzerospeed ); // just throttle down... return; // ...and don't touch any other controls } @@ -7702,15 +7709,18 @@ void TController::control_braking_force() { } // odhamowywanie składu po zatrzymaniu i zabezpieczanie lokomotywy - if( ( mvOccupied->Vel < 0.01 ) + if( ( mvOccupied->Vel < EU07_AI_NOMOVEMENT ) && ( ( VelDesired == 0.0 ) || ( AccDesired <= EU07_AI_NOACCELERATION ) ) ) { - if( ( ( OrderCurrentGet() & ( Disconnect | Connect ) ) == 0 ) // przy (p)odłączaniu nie zwalniamy tu hamulca + if( ( ( OrderCurrentGet() & ( Disconnect | Connect | Change_direction ) ) == 0 ) // przy (p)odłączaniu nie zwalniamy tu hamulca && ( std::abs( fAccGravity ) < 0.01 ) ) { // only do this on flats, on slopes keep applied the train brake - // do it only if the vehicle actually has the independent brake apply_independent_brake_only(); } + // if told to change direction don't confuse human driver with request to leave applied brake in the cab they're about to leave + if( ( OrderCurrentGet() & ( Change_direction ) ) != 0 ) { + cue_action( locale::string::driver_hint_independentbrakerelease ); + } } } @@ -7924,7 +7934,7 @@ TController::check_route_behind( double const Range ) { void TController::UpdateBrakingHelper() { - if (( HelperState > 0 ) && (-AccDesired < fBrake_a0[0] + 2 * fBrake_a1[0]) && (mvOccupied->Vel > 1)) { + if (( HelperState > 0 ) && (-AccDesired < fBrake_a0[0] + 2 * fBrake_a1[0]) && (mvOccupied->Vel > EU07_AI_NOMOVEMENT)) { HelperState = 0; } diff --git a/Driver.h b/Driver.h index d8f8a6fa..899d4ee4 100644 --- a/Driver.h +++ b/Driver.h @@ -18,6 +18,8 @@ http://mozilla.org/MPL/2.0/. #include "translation.h" auto const EU07_AI_NOACCELERATION = -0.05; +auto const EU07_AI_NOMOVEMENT = 0.05; // standstill velocity threshold +auto const EU07_AI_MOVEMENT = 1.0; // deliberate movement velocity threshold auto const EU07_AI_SPEEDLIMITEXTENDSBEYONDSCANRANGE = 10000.0; enum TOrders diff --git a/DynObj.cpp b/DynObj.cpp index 81f18c6d..94247d73 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -249,6 +249,9 @@ bool TDynamicObject::destination_data::deserialize_mapping( cParser &Input ) { else if( key == "parameters:" ) { parameters = Input.getToken(); } + else if( key == "background:" ) { + background = Input.getToken(); + } return true; } @@ -4648,31 +4651,47 @@ void TDynamicObject::RenderSounds() { } } // attach/detach sounds - if( ( coupler.sounds & sound::attachcoupler ) != 0 ) { - couplersounds.attach_coupler.play(); + if( ( coupler.sounds & sound::detach ) == 0 ) { + // potentially added some couplings + if( ( coupler.sounds & sound::attachcoupler ) != 0 ) { + couplersounds.attach_coupler.play(); + } + if( ( coupler.sounds & sound::attachbrakehose ) != 0 ) { + couplersounds.attach_brakehose.play(); + } + if( ( coupler.sounds & sound::attachmainhose ) != 0 ) { + couplersounds.attach_mainhose.play(); + } + if( ( coupler.sounds & sound::attachcontrol ) != 0 ) { + couplersounds.attach_control.play(); + } + if( ( coupler.sounds & sound::attachgangway ) != 0 ) { + couplersounds.attach_gangway.play(); + } + if( ( coupler.sounds & sound::attachheating ) != 0 ) { + couplersounds.attach_heating.play(); + } } - if( ( coupler.sounds & sound::attachbrakehose ) != 0 ) { - couplersounds.attach_brakehose.play(); - } - if( ( coupler.sounds & sound::attachmainhose ) != 0 ) { - couplersounds.attach_mainhose.play(); - } - if( ( coupler.sounds & sound::attachcontrol ) != 0 ) { - couplersounds.attach_control.play(); - } - if( ( coupler.sounds & sound::attachgangway ) != 0 ) { - couplersounds.attach_gangway.play(); - } - if( ( coupler.sounds & sound::attachheating ) != 0 ) { - couplersounds.attach_heating.play(); - } - if( true == TestFlag( coupler.sounds, sound::detachall ) ) { - couplersounds.detach_coupler.play(); - couplersounds.detach_brakehose.play(); - couplersounds.detach_mainhose.play(); - couplersounds.detach_control.play(); - couplersounds.detach_gangway.play(); - couplersounds.detach_heating.play(); + else { + // potentially removed some couplings + if( ( coupler.sounds & sound::attachcoupler ) != 0 ) { + couplersounds.detach_coupler.play(); + } + if( ( coupler.sounds & sound::attachbrakehose ) != 0 ) { + couplersounds.detach_brakehose.play(); + } + if( ( coupler.sounds & sound::attachmainhose ) != 0 ) { + couplersounds.detach_mainhose.play(); + } + if( ( coupler.sounds & sound::attachcontrol ) != 0 ) { + couplersounds.detach_control.play(); + } + if( ( coupler.sounds & sound::attachgangway ) != 0 ) { + couplersounds.detach_gangway.play(); + } + if( ( coupler.sounds & sound::attachheating ) != 0 ) { + couplersounds.detach_heating.play(); + } } if( true == TestFlag( coupler.sounds, sound::attachadapter ) ) { couplersounds.dsbAdapterAttach.play(); @@ -5992,12 +6011,15 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co // vehicle faces +Z in 'its' space, for motor locations negative value means ahead of centre auto const offset { std::atof( token.c_str() ) * -1.f }; // NOTE: we skip setting owner of the sounds, it'll be done during individual sound deserialization - sound_source motor { sound_placement::external }; // generally traction motor + sound_source motor { sound_placement::external }; // generic traction motor sounds + sound_source acmotor { sound_placement::external }; // inverter-specific traction motor sounds sound_source motorblower { sound_placement::engine }; // associated motor blowers // add entry to the list auto const location { glm::vec3 { 0.f, 0.f, offset } }; motor.offset( location ); m_powertrainsounds.motors.emplace_back( motor ); + acmotor.offset( location ); + m_powertrainsounds.acmotors.emplace_back( acmotor ); motorblower.offset( location ); m_powertrainsounds.motorblowers.emplace_back( motorblower ); } @@ -6344,6 +6366,7 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co DestinationSign.script = asBaseDir + DestinationSign.script; } } + // NOTE: legacy key, now expected as optional "background:" parameter in pydestinationsign: { parameter block } else if( token == "destinationsignbackground:" ) { parser.getTokens(); parser >> DestinationSign.background; diff --git a/Gauge.h b/Gauge.h index 86b08c97..0371b4d9 100644 --- a/Gauge.h +++ b/Gauge.h @@ -58,6 +58,14 @@ public: // returns offset of submodel associated with the button from the model centre glm::vec3 model_offset() const; TGaugeType type() const; + inline + bool is_push() const { + return ( static_cast( type() ) & static_cast( TGaugeType::push ) ) != 0; } + inline + bool is_toggle() const { + return ( static_cast( type() ) & static_cast( TGaugeType::toggle ) ) != 0; } + bool is_delayed() const { + return ( static_cast( type() ) & static_cast( TGaugeType::delayed ) ) != 0; } // members TSubModel *SubModel { nullptr }, // McZapkie-310302: zeby mozna bylo sprawdzac czy zainicjowany poprawnie *SubModelOn { nullptr }; // optional submodel visible when the state input is set diff --git a/Globals.cpp b/Globals.cpp index dc310c17..07799b96 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -174,6 +174,12 @@ global_settings::ConfigParse(cParser &Parser) { Parser >> EnvironmentAmbientVolume; EnvironmentAmbientVolume = clamp(EnvironmentAmbientVolume, 0.f, 1.f); } + else if( token == "sound.volume.paused" ) { + // selected device for audio renderer + Parser.getTokens(); + Parser >> PausedVolume; + EnvironmentAmbientVolume = clamp( EnvironmentAmbientVolume, 0.f, 1.f ); + } // else if (str==AnsiString("renderalpha")) //McZapkie-1312302 - dwuprzebiegowe renderowanie // bRenderAlpha=(GetNextSymbol().LowerCase()==AnsiString("yes")); else if (token == "physicslog") @@ -1035,6 +1041,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "sound.volume.vehicle", VehicleVolume ); export_as_text( Output, "sound.volume.positional", EnvironmentPositionalVolume ); export_as_text( Output, "sound.volume.ambient", EnvironmentAmbientVolume ); + export_as_text( Output, "sound.volume.paused", PausedVolume ); export_as_text( Output, "physicslog", WriteLogFlag ); export_as_text( Output, "fullphysics", FullPhysics ); export_as_text( Output, "debuglog", iWriteLogEnabled ); diff --git a/Globals.h b/Globals.h index 72be273b..c6144ced 100644 --- a/Globals.h +++ b/Globals.h @@ -159,6 +159,7 @@ struct global_settings { float VehicleVolume{ 1.0f }; float EnvironmentPositionalVolume{ 1.0f }; float EnvironmentAmbientVolume{ 1.0f }; + float PausedVolume { 0.15f }; std::string AudioRenderer; // input float fMouseXScale{ 1.5f }; diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 79c081aa..37fa05e4 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -243,7 +243,7 @@ enum sound { parallel = 1 << 4, shuntfield = 1 << 5, pneumatic = 1 << 6, - detachall = 1 << 7, + detach = 1 << 7, attachcoupler = 1 << 8, attachbrakehose = 1 << 9, attachmainhose = 1 << 10, @@ -1168,6 +1168,7 @@ public: bool UniCtrlIntegratedBrakeCtrl = false; /*zintegrowany nastawnik JH obsluguje hamowanie*/ bool UniCtrlIntegratedLocalBrakeCtrl = false; /*zintegrowany nastawnik JH obsluguje hamowanie hamulcem pomocniczym*/ int UniCtrlNoPowerPos{ 0 }; // cached highesr position not generating traction force + std::pair> PantsPreset { "", { 0, 0 } }; // pantograph preset switches; .first holds possible setups as chars, .second holds currently selected preset in each cab /*-sekcja parametrow dla lokomotywy elektrycznej*/ TSchemeTable RList; /*lista rezystorow rozruchowych i polaczen silnikow, dla dizla: napelnienia*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 0434ad35..d41b8dd2 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -511,23 +511,39 @@ bool TMoverParameters::Dettach(int ConnectNo) if( othervehicle == nullptr ) { return true; } // nie ma nic, to odczepiono - auto const i = DettachStatus(ConnectNo); // stan sprzęgu - if (i < 0) { + auto couplingchange { coupler.CouplingFlag }; // presume we'll uncouple all active flags + auto const couplingstate { DettachStatus( ConnectNo ) }; // stan sprzęgu + if (couplingstate < 0) { // gdy scisniete zderzaki, chyba ze zerwany sprzeg (wirtualnego nie odpinamy z drugiej strony) std::tie( coupler.Connected, coupler.ConnectedNr, coupler.CouplingFlag ) = std::tie( othercoupler.Connected, othercoupler.ConnectedNr, othercoupler.CouplingFlag ) = std::make_tuple( nullptr, -1, coupling::faux ); - // set sound event flag - SetFlag( coupler.sounds, sound::detachall ); - - return true; } - else if (i > 0) + else if (couplingstate > 0) { // odłączamy węże i resztę, pozostaje sprzęg fizyczny, który wymaga dociśnięcia (z wirtualnym nic) coupler.CouplingFlag &= coupling::coupler; othercoupler.CouplingFlag &= coupling::coupler; } - return false; // jeszcze nie rozłączony + // set sound event flag + couplingchange ^= coupler.CouplingFlag; // remaining bits were removed from coupling + if( couplingchange != 0 ) { + int soundflag { sound::detach }; // HACK: use detach flag to indicate removal of listed coupling + std::vector> const soundmappings = { + { coupling::coupler, sound::attachcoupler }, + { coupling::brakehose, sound::attachbrakehose }, + { coupling::mainhose, sound::attachmainhose }, + { coupling::control, sound::attachcontrol}, + { coupling::gangway, sound::attachgangway}, + { coupling::heating, sound::attachheating} }; + for( auto const &soundmapping : soundmappings ) { + if( ( couplingchange & soundmapping.first ) != 0 ) { + soundflag |= soundmapping.second; + } + } + SetFlag( coupler.sounds, soundflag ); + } + + return ( couplingstate < 0 ); }; bool TMoverParameters::DirectionForward() @@ -10800,6 +10816,14 @@ void TMoverParameters::LoadFIZ_Switches( std::string const &Input ) { extract_value( UniversalResetButtonFlag[ 0 ], "RelayResetButton1", Input, "" ); extract_value( UniversalResetButtonFlag[ 1 ], "RelayResetButton2", Input, "" ); extract_value( UniversalResetButtonFlag[ 2 ], "RelayResetButton3", Input, "" ); + // pantograph presets + { + auto &presets { PantsPreset.first }; + extract_value( presets, "PantographPresets", Input, "0|1|3|2" ); + presets.erase( + std::remove( std::begin( presets ), std::end( presets ), '|' ), + std::end( presets ) ); + } } void TMoverParameters::LoadFIZ_MotorParamTable( std::string const &Input ) { diff --git a/Train.cpp b/Train.cpp index cc98cccd..ce2856e0 100644 --- a/Train.cpp +++ b/Train.cpp @@ -31,7 +31,6 @@ http://mozilla.org/MPL/2.0/. #include "Console.h" #include "application.h" #include "renderer.h" -#include "dictionary.h" /* namespace input { @@ -72,6 +71,38 @@ control_mapper::contains( std::string const Control ) const { return ( m_names.find( Control ) != m_names.end() ); } +void TTrain::screen_entry::deserialize( cParser &Input ) { + + while( true == deserialize_mapping( Input ) ) { + ; // all work done by while() + } +} + +bool TTrain::screen_entry::deserialize_mapping( cParser &Input ) { + // token can be a key or block end + auto const key { Input.getToken( true, "\n\r\t ,;[]" ) }; + + if( ( true == key.empty() ) || ( key == "}" ) ) { return false; } + + if( key == "{" ) { + script = Input.getToken(); + } + else if( key == "target:" ) { + target = Input.getToken(); + } + else if( key == "parameters:" ) { + parameters = dictionary_source( Input.getToken() ); + } + else { + // HACK: we expect this to be true only if the screen entry doesn't start with a { which means legacy configuration format + target = key; + script = Input.getToken(); + return false; + } + + return true; +} + void TCab::Load(cParser &Parser) { // NOTE: clearing control tables here is bit of a crutch, imposed by current scheme of loading compartments anew on each cab change @@ -537,12 +568,12 @@ bool TTrain::Init(TDynamicObject *NewDynamicObject, bool e3d) return true; } -dictionary_source *TTrain::GetTrainState() { +dictionary_source *TTrain::GetTrainState( dictionary_source const &Extraparameters ) { if( ( mvOccupied == nullptr ) || ( mvControlled == nullptr ) ) { return nullptr; } - auto *dict { new dictionary_source }; + auto *dict { new dictionary_source( Extraparameters ) }; if( dict == nullptr ) { return nullptr; } dict->insert( "name", DynamicObject->asName ); @@ -612,6 +643,7 @@ dictionary_source *TTrain::GetTrainState() { dict->insert( "distance_counter", m_distancecounter ); dict->insert( "pantpress", std::abs( mvPantographUnit->PantPress ) ); dict->insert( "universal3", InstrumentLightActive ); + dict->insert( "radio", mvOccupied->Radio ); dict->insert( "radio_channel", RadioChannel() ); dict->insert( "radio_volume", Global.RadioVolume ); dict->insert( "door_lock", mvOccupied->Doors.lock_enabled ); @@ -621,6 +653,7 @@ dictionary_source *TTrain::GetTrainState() { dict->insert( "tractionforce", std::abs( mvOccupied->Ft ) ); dict->insert( "slipping_wheels", mvOccupied->SlippingWheels ); dict->insert( "sanding", mvOccupied->SandDose ); + dict->insert( "odometer", mvOccupied->DistCounter ); // electric current data dict->insert( "traction_voltage", std::abs( mvPantographUnit->PantographVoltage ) ); dict->insert( "voltage", std::abs( mvControlled->EngineVoltage ) ); @@ -1122,18 +1155,30 @@ void TTrain::OnCommand_tempomattoggle( TTrain *Train, command_data const &Comman if( Train->ggScndCtrlButton.type() == TGaugeType::push ) { // impulse switch if( Command.action == GLFW_RELEASE ) { - // just move the button back to default position + // just move the button(s) back to default position // visual feedback Train->ggScndCtrlButton.UpdateValue( 0.0, Train->dsbSwitch ); + Train->ggScndCtrlOffButton.UpdateValue( 0.0, Train->dsbSwitch ); return; } // glfw_press if( Train->mvControlled->ScndCtrlPos == 0 ) { - // turn on if needed + // turn on if it's not active Train->mvControlled->IncScndCtrl( 1 ); + // visual feedback + Train->ggScndCtrlButton.UpdateValue( 1.0, Train->dsbSwitch ); + } + else { + // otherwise turn off + Train->mvControlled->DecScndCtrl( 2 ); + // visual feedback + if( Train->m_controlmapper.contains( "tempomatoff_sw:" ) ) { + Train->ggScndCtrlOffButton.UpdateValue( 1.0, Train->dsbSwitch ); + } + else { + Train->ggScndCtrlButton.UpdateValue( 1.0, Train->dsbSwitch ); + } } - // visual feedback - Train->ggScndCtrlButton.UpdateValue( 1.0, Train->dsbSwitch ); } else { // two-state switch @@ -2194,7 +2239,7 @@ void TTrain::OnCommand_batterydisable( TTrain *Train, command_data const &Comman void TTrain::OnCommand_pantographtogglefront( TTrain *Train, command_data const &Command ) { // HACK: presence of pantograph selector prevents manual operation of the individual valves - if( Train->ggPantSelectButton.SubModel ) { return; } + if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; } if( Command.action == GLFW_PRESS ) { // only reacting to press, so the switch doesn't flip back and forth if key is held down @@ -2226,7 +2271,7 @@ void TTrain::OnCommand_pantographtogglefront( TTrain *Train, command_data const void TTrain::OnCommand_pantographtogglerear( TTrain *Train, command_data const &Command ) { // HACK: presence of pantograph selector prevents manual operation of the individual valves - if( Train->ggPantSelectButton.SubModel ) { return; } + if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; } if( Command.action == GLFW_PRESS ) { // only reacting to press, so the switch doesn't flip back and forth if key is held down @@ -2258,7 +2303,7 @@ void TTrain::OnCommand_pantographtogglerear( TTrain *Train, command_data const & void TTrain::OnCommand_pantographraisefront( TTrain *Train, command_data const &Command ) { // HACK: presence of pantograph selector prevents manual operation of the individual valves - if( Train->ggPantSelectButton.SubModel ) { return; } + if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; } // prevent operation without submodel outside of engine compartment if( ( Train->iCabn != 0 ) && ( false == Train->m_controlmapper.contains( "pantfront_sw:" ) ) ) { return; } @@ -2285,7 +2330,7 @@ void TTrain::OnCommand_pantographraisefront( TTrain *Train, command_data const & void TTrain::OnCommand_pantographraiserear( TTrain *Train, command_data const &Command ) { // HACK: presence of pantograph selector prevents manual operation of the individual valves - if( Train->ggPantSelectButton.SubModel ) { return; } + if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; } // prevent operation without submodel outside of engine compartment if( ( Train->iCabn != 0 ) && ( false == Train->m_controlmapper.contains( "pantrear_sw:" ) ) ) { return; } @@ -2312,7 +2357,7 @@ void TTrain::OnCommand_pantographraiserear( TTrain *Train, command_data const &C void TTrain::OnCommand_pantographlowerfront( TTrain *Train, command_data const &Command ) { // HACK: presence of pantograph selector prevents manual operation of the individual valves - if( Train->ggPantSelectButton.SubModel ) { return; } + if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; } // prevent operation without submodel outside of engine compartment if( ( Train->iCabn != 0 ) && ( false == Train->m_controlmapper.contains( @@ -2344,7 +2389,8 @@ void TTrain::OnCommand_pantographlowerfront( TTrain *Train, command_data const & void TTrain::OnCommand_pantographlowerrear( TTrain *Train, command_data const &Command ) { // HACK: presence of pantograph selector prevents manual operation of the individual valves - if( Train->ggPantSelectButton.SubModel ) { return; } + if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; } + if( ( Train->iCabn != 0 ) && ( false == Train->m_controlmapper.contains( Train->mvOccupied->PantSwitchType == "impulse" ? @@ -2404,7 +2450,7 @@ void TTrain::OnCommand_pantographselectnext( TTrain *Train, command_data const & if( Command.action != GLFW_PRESS ) { return; } - if( Train->ggPantSelectButton.SubModel == nullptr ) { return; } + if( false == Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; } Train->change_pantograph_selection( 1 ); } @@ -2413,7 +2459,7 @@ void TTrain::OnCommand_pantographselectprevious( TTrain *Train, command_data con if( Command.action != GLFW_PRESS ) { return; } - if( Train->ggPantSelectButton.SubModel == nullptr ) { return; } + if( false == Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; } Train->change_pantograph_selection( -1 ); } @@ -2508,19 +2554,19 @@ void TTrain::OnCommand_pantographlowerselected( TTrain *Train, command_data cons void TTrain::change_pantograph_selection( int const Change ) { - auto const initialstate { m_pantselection }; + auto const &presets { mvOccupied->PantsPreset.first }; + auto &selection { mvOccupied->PantsPreset.second[ cab_to_end() ] }; + auto const initialstate { selection }; + selection = clamp( selection + Change, 0, std::max( presets.size() - 1, 0 ) ); - m_pantselection = clamp( m_pantselection + Change, 0, 3 ); - // visual feedback - ggPantSelectButton.UpdateValue( m_pantselection ); - - if( m_pantselection == initialstate ) { return; } // no change, nothing to do + if( selection == initialstate ) { return; } // no change, nothing to do // configure pantograph valves matching the new state + auto const preset { presets[ selection ] - '0' }; auto const swapends { cab_to_end() != end::front }; // check desired states for both pantographs; value: whether the pantograph should be raised - auto const frontstate{ ( m_pantselection == 2 ) || ( m_pantselection == ( swapends ? 1 : 3 ) ) }; - auto const rearstate{ ( m_pantselection == 2 ) || ( m_pantselection == ( swapends ? 3 : 1 ) ) }; + auto const frontstate { preset & ( swapends ? 1 : 2 ) }; + auto const rearstate { preset & ( swapends ? 2 : 1 ) }; // potentially adjust pantograph valves mvOccupied->OperatePantographValve( end::front, ( frontstate ? operation_t::enable : operation_t::disable ) ); mvOccupied->OperatePantographValve( end::rear, ( rearstate ? operation_t::enable : operation_t::disable ) ); @@ -5503,9 +5549,27 @@ void TTrain::OnCommand_doorcloseall( TTrain *Train, command_data const &Command } void TTrain::OnCommand_doorsteptoggle( TTrain *Train, command_data const &Command ) { - + // TODO: move logic/visualization code to the gauge, on_command() should return hint whether it should invoke a reaction if( Command.action == GLFW_PRESS ) { - Train->mvOccupied->PermitDoorStep( false == Train->mvOccupied->Doors.step_enabled ); + // effect + if( false == Train->ggDoorStepButton.is_delayed() ) { + Train->mvOccupied->PermitDoorStep( false == Train->mvOccupied->Doors.step_enabled ); + } + // visual feedback + auto const isactive { ( + Train->ggDoorStepButton.is_push() // always press push button + || Train->mvOccupied->Doors.step_enabled ) }; // for toggle buttons indicate item state + Train->ggDoorStepButton.UpdateValue( isactive ? 1 : 0 ); + } + else if( Command.action == GLFW_RELEASE ) { + // effect + if( Train->ggDoorStepButton.is_delayed() ) { + Train->mvOccupied->PermitDoorStep( false == Train->mvOccupied->Doors.step_enabled ); + } + // visual feedback + if( Train->ggDoorStepButton.is_push() ) { + Train->ggDoorStepButton.UpdateValue( 0 ); + } } } @@ -6964,6 +7028,7 @@ bool TTrain::Update( double const Deltatime ) ggScndCtrl.Update(); } ggScndCtrlButton.Update( lowvoltagepower ); + ggScndCtrlOffButton.Update( lowvoltagepower ); ggDistanceCounterButton.Update(); if (ggDirKey.SubModel) { if (mvControlled->TrainType != dt_EZT) @@ -7047,16 +7112,17 @@ bool TTrain::Update( double const Deltatime ) // NBMX wrzesien 2003 - drzwi ggDoorLeftPermitButton.Update( lowvoltagepower ); ggDoorRightPermitButton.Update( lowvoltagepower ); - ggDoorPermitPresetButton.Update(); - ggDoorLeftButton.Update(); - ggDoorRightButton.Update(); - ggDoorLeftOnButton.Update(); - ggDoorRightOnButton.Update(); - ggDoorLeftOffButton.Update(); - ggDoorRightOffButton.Update(); - ggDoorAllOnButton.Update(); + ggDoorPermitPresetButton.Update( lowvoltagepower ); + ggDoorLeftButton.Update( lowvoltagepower ); + ggDoorRightButton.Update( lowvoltagepower ); + ggDoorLeftOnButton.Update( lowvoltagepower ); + ggDoorRightOnButton.Update( lowvoltagepower ); + ggDoorLeftOffButton.Update( lowvoltagepower ); + ggDoorRightOffButton.Update( lowvoltagepower ); + ggDoorAllOnButton.Update( lowvoltagepower ); ggDoorAllOffButton.Update( lowvoltagepower ); - ggDoorSignallingButton.Update(); + ggDoorSignallingButton.Update( lowvoltagepower ); + ggDoorStepButton.Update( lowvoltagepower ); // NBMX dzwignia sprezarki ggCompressorButton.Update(); ggCompressorLocalButton.Update(); @@ -7125,7 +7191,6 @@ bool TTrain::Update( double const Deltatime ) ggPantAllDownButton.Update(); ggPantSelectedDownButton.Update(); ggPantSelectedButton.Update(); - ggPantSelectButton.Update(); ggPantCompressorButton.Update(); ggPantCompressorValve.Update(); @@ -7237,13 +7302,13 @@ bool TTrain::Update( double const Deltatime ) && ( fScreenTimer > std::max( fScreenUpdateRate, Global.PythonScreenUpdateRate ) * 0.001f ) && ( false == FreeFlyModeFlag ) ) { // don't bother if we're outside fScreenTimer = 0.f; - for( auto const &screen : m_screens ) { - auto state_dict = GetTrainState(); + for( auto &screen : m_screens ) { + auto *state_dict = GetTrainState( screen.parameters ); /* state_dict->insert("touches", *screen.touch_list); screen.touch_list->clear(); */ - Application.request({ screen.rendererpath, state_dict, screen.rt } ); + Application.request( { screen.script, state_dict, screen.rt } ); } } // sounds @@ -8038,60 +8103,62 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) // TODO: add "pydestination:" else if (token == "pyscreen:") { - std::string submodelname, renderername; - parser.getTokens( 2 ); - parser - >> submodelname - >> renderername; + screen_entry screen; + screen.deserialize(parser); + if ((false == screen.script.empty()) && (substr_path(screen.script).empty())) + { + screen.script = DynamicObject->asBaseDir + screen.script; + } - const std::string rendererpath { - substr_path(renderername).empty() ? // supply vehicle folder as path if none is provided - DynamicObject->asBaseDir + renderername : - renderername }; - - opengl_texture *tex = nullptr; + opengl_texture *tex = nullptr; TSubModel *submodel = nullptr; - if (submodelname != "none") { - submodel = ( - DynamicObject->mdKabina ? DynamicObject->mdKabina->GetFromName( submodelname ) : - DynamicObject->mdLowPolyInt ? DynamicObject->mdLowPolyInt->GetFromName( submodelname ) : - nullptr ); - if( submodel == nullptr ) { - WriteLog( "Python Screen: submodel " + submodelname + " not found - Ignoring screen" ); - continue; - } - auto const material { submodel->GetMaterial() }; - if( material <= 0 ) { - // sub model nie posiada tekstury lub tekstura wymienna - nie obslugiwana - WriteLog( "Python Screen: invalid texture id " + std::to_string( material ) + " - Ignoring screen" ); - continue; - } + if (screen.target != "none") + { + submodel = (DynamicObject->mdKabina ? + DynamicObject->mdKabina->GetFromName(screen.target) : + DynamicObject->mdLowPolyInt ? + DynamicObject->mdLowPolyInt->GetFromName(screen.target) : + nullptr); + if (submodel == nullptr) + { + WriteLog("Python Screen: submodel " + screen.target + + " not found - Ignoring screen"); + continue; + } + auto const material{submodel->GetMaterial()}; + if (material <= 0) + { + // sub model nie posiada tekstury lub tekstura wymienna - nie obslugiwana + WriteLog("Python Screen: invalid texture id " + std::to_string(material) + + " - Ignoring screen"); + continue; + } tex = &GfxRenderer->Texture(GfxRenderer->Material(material).textures[0]); - } - else { - // TODO: fix leak - tex = new opengl_texture(); - tex->make_stub(); - } + } + else + { + // TODO: fix leak + tex = new opengl_texture(); + tex->make_stub(); + } - tex->create( true ); // make the surface static so it doesn't get destroyed by garbage collector if the user spends long time outside cab - // TBD, TODO: keep texture handles around, so we can undo the static switch when the user changes cabs? + tex->create(true); // make the surface static so it doesn't get destroyed by garbage + // collector if the user spends long time outside cab + // TBD, TODO: keep texture handles around, so we can undo the static switch when the + // user changes cabs? + auto rt = std::make_shared(); + rt->shared_tex = tex->id; - auto touch_list = std::make_shared>(); - auto rt = std::make_shared(); - rt->shared_tex = tex->id; -/* - if (submodel) - submodel->screen_touch_list = touch_list; -*/ // record renderer and material binding for future update requests - m_screens.emplace_back(); - m_screens.back().rendererpath = rendererpath; + m_screens.emplace_back(screen); m_screens.back().rt = rt; /* - m_screens.back().touch_list = touch_list; - + m_screens.back().touch_list = std::make_shared>(); + if (submodel) // guaranteed at this point + submodel->screen_touch_list = m_screens.back().touch_list; +*/ +/* if (Global.python_displaywindows) m_screens.back().viewer = std::make_unique(rt, touch_list, rendererpath); */ @@ -8280,8 +8347,14 @@ TTrain::MoveToVehicle(TDynamicObject *target) { target_train->Occupied()->LimPipePress = target_train->Occupied()->PipePress; target_train->Occupied()->CabActivisation( true ); // załączenie rozrządu (wirtualne kabiny) target_train->Dynamic()->MechInside = true; - target_train->Dynamic()->Controller = ( target_train->Dynamic()->Mechanik ? !target_train->Dynamic()->Mechanik->AIControllFlag : Humandriver ); - } else { + if( target_train->Dynamic()->Mechanik ) { + target_train->Dynamic()->Controller = target_train->Dynamic()->Mechanik->AIControllFlag; + target_train->Dynamic()->Mechanik->DirectionChange(); + } + else { + target_train->Dynamic()->Controller = Humandriver; + } + } else { target_train->Dynamic()->bDisplayCab = false; target_train->Dynamic()->ABuSetModelShake( {} ); } @@ -8327,7 +8400,13 @@ TTrain::MoveToVehicle(TDynamicObject *target) { Occupied()->LimPipePress = Occupied()->PipePress; Occupied()->CabActivisation( true ); // załączenie rozrządu (wirtualne kabiny) Dynamic()->MechInside = true; - Dynamic()->Controller = ( Dynamic()->Mechanik ? Dynamic()->Mechanik->AIControllFlag : Humandriver ); + if( Dynamic()->Mechanik ) { + Dynamic()->Controller = Dynamic()->Mechanik->AIControllFlag; + Dynamic()->Mechanik->DirectionChange(); + } + else { + Dynamic()->Controller = Humandriver; + } } else { Dynamic()->bDisplayCab = false; Dynamic()->ABuSetModelShake( {} ); @@ -8429,6 +8508,7 @@ void TTrain::clear_cab_controls() ggMainCtrlAct.Clear(); ggScndCtrl.Clear(); ggScndCtrlButton.Clear(); + ggScndCtrlOffButton.Clear(); ggDistanceCounterButton.Clear(); ggDirKey.Clear(); ggDirForwardButton.Clear(); @@ -8512,6 +8592,7 @@ void TTrain::clear_cab_controls() ggTrainHeatingButton.Clear(); ggSignallingButton.Clear(); ggDoorSignallingButton.Clear(); + ggDoorStepButton.Clear(); ggDepartureSignalButton.Clear(); ggCompressorButton.Clear(); ggCompressorLocalButton.Clear(); @@ -8528,7 +8609,6 @@ void TTrain::clear_cab_controls() ggPantAllDownButton.Clear(); ggPantSelectedButton.Clear(); ggPantSelectedDownButton.Clear(); - ggPantSelectButton.Clear(); ggPantCompressorButton.Clear(); ggPantCompressorValve.Clear(); ggI1B.Clear(); @@ -8705,13 +8785,6 @@ void TTrain::set_cab_controls( int const Cab ) { } */ // front/end pantograph selection is relative to occupied cab - m_pantselection = ( - m_pantselection == 1 ? ( cab_to_end( Cab ) == cab_to_end() ? 1 : 3 ) : - m_pantselection == 3 ? ( cab_to_end( Cab ) == cab_to_end() ? 3 : 1 ) : - m_pantselection ); // other settings affect both pantographs - if( ggPantSelectButton.SubModel ) { - ggPantSelectButton.PutValue( m_pantselection ); - } if( ggPantSelectedButton.type() == TGaugeType::toggle ) { ggPantSelectedButton.PutValue( ( mvPantographUnit->PantsValve.is_enabled ? @@ -8825,10 +8898,10 @@ void TTrain::set_cab_controls( int const Cab ) { 1.f : 0.f ) ); // doors permits - if( ggDoorLeftPermitButton.type() != TGaugeType::push ) { + if( false == ggDoorLeftPermitButton.is_push() ) { ggDoorLeftPermitButton.PutValue( mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::left : side::right ) ].open_permit ? 1.f : 0.f ); } - if( ggDoorRightPermitButton.type() != TGaugeType::push ) { + if( false == ggDoorRightPermitButton.is_push() ) { ggDoorRightPermitButton.PutValue( mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::right : side::left ) ].open_permit ? 1.f : 0.f ); } ggDoorPermitPresetButton.PutValue( mvOccupied->Doors.permit_preset ); @@ -8840,8 +8913,15 @@ void TTrain::set_cab_controls( int const Cab ) { mvOccupied->Doors.lock_enabled ? 1.f : 0.f ); + // door step + if( false == ggDoorStepButton.is_push() ) { + ggDoorStepButton.PutValue( + mvOccupied->Doors.step_enabled ? + 1.f : + 0.f ); + } // heating - if( ggTrainHeatingButton.type() != TGaugeType::push ) { + if( false == ggTrainHeatingButton.is_push() ) { ggTrainHeatingButton.PutValue( mvControlled->Heating ? 1.f : @@ -8946,7 +9026,7 @@ void TTrain::set_cab_controls( int const Cab ) { 0.f ); } // tempomat - if( ggScndCtrlButton.type() != TGaugeType::push ) { + if( false == ggScndCtrlButton.is_push() ) { ggScndCtrlButton.PutValue( ( mvControlled->ScndCtrlPos > 0 ) ? 1.f : @@ -9225,7 +9305,6 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con { "pantalloff_sw:", ggPantAllDownButton }, { "pantselected_sw:", ggPantSelectedButton }, { "pantselectedoff_sw:", ggPantSelectedDownButton }, - { "pantselect_sw:", ggPantSelectButton }, { "pantcompressor_sw:", ggPantCompressorButton }, { "pantcompressorvalve_sw:", ggPantCompressorValve }, { "trainheating_sw:", ggTrainHeatingButton }, @@ -9265,9 +9344,12 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con return true; } } - // TODO: move viable gauges to the state driven array + // dedicated gauges with state-driven optional submodel + // TODO: move viable gauges here + // TODO: convert dedicated gauges to auto-allocated ones, replace dedicated references in command handlers to mapper lookups std::unordered_map > const stategauges = { { "tempomat_sw:", { ggScndCtrlButton, &mvOccupied->SpeedCtrlUnit.IsActive } }, + { "tempomatoff_sw:", { ggScndCtrlOffButton, &mvOccupied->SpeedCtrlUnit.IsActive } }, { "speedinc_bt:", { ggSpeedControlIncreaseButton, &mvOccupied->SpeedCtrlUnit.IsActive } }, { "speeddec_bt:", { ggSpeedControlDecreaseButton, &mvOccupied->SpeedCtrlUnit.IsActive } }, { "speedctrlpowerinc_bt:", { ggSpeedControlPowerIncreaseButton, &mvOccupied->SpeedCtrlUnit.IsActive } }, @@ -9285,7 +9367,8 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con { "doorleftpermit_sw:", { ggDoorLeftPermitButton, &mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::left : side::right ) ].open_permit } }, { "doorrightpermit_sw:", { ggDoorRightPermitButton, &mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::right : side::left ) ].open_permit } }, { "dooralloff_sw:", { ggDoorAllOffButton, &m_doors } }, - { "dirforward_bt:", { ggDirForwardButton, &m_dirforward } }, + { "doorstep_sw:", { ggDoorStepButton, &mvOccupied->Doors.step_enabled } }, + { "dirforward_bt:", { ggDirForwardButton, &m_dirforward } }, { "dirneutral_bt:", { ggDirNeutralButton, &m_dirneutral } }, { "dirbackward_bt:", { ggDirBackwardButton, &m_dirbackward } }, }; @@ -9302,7 +9385,6 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con // TODO: move viable dedicated gauges to the automatic array std::unordered_map const autoboolgauges = { { "doormode_sw:", &mvOccupied->Doors.remote_only }, - { "doorstep_sw:", &mvOccupied->Doors.step_enabled }, { "coolingfans_sw:", &mvControlled->RVentForceOn }, { "pantfront_sw:", &mvPantographUnit->Pantographs[end::front].valve.is_enabled }, { "pantrear_sw:", &mvPantographUnit->Pantographs[end::rear].valve.is_enabled }, @@ -9326,6 +9408,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con // TODO: move viable dedicated gauges to the automatic array std::unordered_map const autointgauges = { { "manualbrake:", &mvOccupied->ManualBrakePos }, + { "pantselect_sw:", &mvOccupied->PantsPreset.second[cab_to_end()] }, }; { auto lookup = autointgauges.find( Label ); diff --git a/Train.h b/Train.h index dd31f515..157a0ec8 100644 --- a/Train.h +++ b/Train.h @@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/. #include "sound.h" #include "PyInt.h" #include "command.h" +#include "dictionary.h" #undef snprintf // pyint.h->python @@ -113,15 +114,21 @@ class TTrain { }; struct screen_entry { - std::string rendererpath; + + std::string script; + std::string target; std::shared_ptr rt; /* std::unique_ptr viewer; std::shared_ptr> touch_list; */ + dictionary_source parameters; // cached pre-processed optional per-screen parameters + + void deserialize( cParser &Input ); + bool deserialize_mapping( cParser &Input ); }; - typedef std::vector screen_map; + typedef std::vector screenentry_sequence; // constructors TTrain(); @@ -132,16 +139,23 @@ class TTrain { // McZapkie-010302 bool Init(TDynamicObject *NewDynamicObject, bool e3d = false); - inline Math3D::vector3 GetDirection() { return DynamicObject->VectorFront(); }; - inline Math3D::vector3 GetUp() { return DynamicObject->VectorUp(); }; - inline std::string GetLabel( TSubModel const *Control ) const { return m_controlmapper.find( Control ); } + inline + Math3D::vector3 GetDirection() const { + return DynamicObject->VectorFront(); }; + inline + Math3D::vector3 GetUp() const { + return DynamicObject->VectorUp(); }; + inline + std::string GetLabel( TSubModel const *Control ) const { + return m_controlmapper.find( Control ); } void UpdateCab(); bool Update( double const Deltatime ); void add_distance( double const Distance ); // McZapkie-310302: ladowanie parametrow z pliku bool LoadMMediaFile(std::string const &asFileName); - dictionary_source *GetTrainState(); + dictionary_source *GetTrainState( dictionary_source const &Extraparameters ); state_t get_state() const; + // basic_table interface inline std::string name() const { return Dynamic()->name(); } @@ -464,6 +478,7 @@ public: // reszta może by?publiczna TGauge ggMainCtrlAct; TGauge ggScndCtrl; TGauge ggScndCtrlButton; + TGauge ggScndCtrlOffButton; TGauge ggDirKey; TGauge ggDirForwardButton; TGauge ggDirNeutralButton; @@ -572,6 +587,7 @@ public: // reszta może by?publiczna TGauge ggDoorAllOnButton; TGauge ggDoorAllOffButton; TGauge ggDepartureSignalButton; + TGauge ggDoorStepButton; // Winger 160204 - obsluga pantografow - ZROBIC /* @@ -583,7 +599,6 @@ public: // reszta może by?publiczna TGauge ggPantAllDownButton; TGauge ggPantSelectedButton; TGauge ggPantSelectedDownButton; - TGauge ggPantSelectButton; TGauge ggPantCompressorButton; TGauge ggPantCompressorValve; // Winger 020304 - wlacznik ogrzewania @@ -790,11 +805,10 @@ private: float fPPress, fNPress; bool m_mastercontrollerinuse { false }; float m_mastercontrollerreturndelay { 0.f }; - screen_map m_screens; + screenentry_sequence m_screens; uint16_t vid { 0 }; // train network recipient id float m_distancecounter { -1.f }; // distance traveled since meter was activated or -1 if inactive double m_brakehandlecp{ 0.0 }; - int m_pantselection{ 0 }; bool m_doors{ false }; // helper, true if any door is open bool m_dirforward{ false }; // helper, true if direction set to forward bool m_dirneutral{ false }; // helper, true if direction set to neutral diff --git a/audiorenderer.cpp b/audiorenderer.cpp index 4366e80f..4a729d6e 100644 --- a/audiorenderer.cpp +++ b/audiorenderer.cpp @@ -335,7 +335,7 @@ void openal_renderer::update( double const Deltatime ) { // update listener // gain - ::alListenerf( AL_GAIN, clamp( Global.AudioVolume, 0.f, 2.f ) * ( Global.iPause == 0 ? 1.f : 0.15f ) ); + ::alListenerf( AL_GAIN, clamp( Global.AudioVolume, 0.f, 2.f ) * ( Global.iPause == 0 ? 1.f : Global.PausedVolume ) ); // orientation glm::dmat4 cameramatrix; Global.pCamera.SetMatrix( cameramatrix ); diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 34614998..fbb1dcdb 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -279,7 +279,7 @@ scenario_panel::render() { // hints if( owner != nullptr ) { auto const hintheader{ locale::strings[ locale::string::driver_hint_header ] }; - if( true == ImGui::CollapsingHeader( hintheader.c_str() ) ) { + if( true == ImGui::CollapsingHeader( hintheader.c_str(), ImGuiTreeNodeFlags_DefaultOpen ) ) { for( auto const &hint : owner->m_hints ) { auto const isdone { std::get( hint )( std::get( hint ) ) }; auto const hintcolor{ ( @@ -1254,14 +1254,15 @@ debug_panel::update_section_eventqueue( std::vector &Output ) { && ( ( false == m_eventqueueactivevehicleonly ) || ( event->m_activator == m_input.vehicle ) ) ) { + auto const label { event->m_name + ( event->m_activator ? " (by: " + event->m_activator->asName + ")" : "" ) }; + if( ( false == searchfilter.empty() ) - && ( event->m_name.find( searchfilter ) == std::string::npos ) ) { + && ( label.find( searchfilter ) == std::string::npos ) ) { event = event->m_next; continue; } auto const delay { " " + to_string( std::max( 0.0, event->m_launchtime - time ), 1 ) }; - auto const label { event->m_name + ( event->m_activator ? " (by: " + event->m_activator->asName + ")" : "" ) }; textline = delay.substr( delay.length() - 6 ) + " " diff --git a/driveruipanels.h b/driveruipanels.h index 4c8bcb97..f20c9d61 100644 --- a/driveruipanels.h +++ b/driveruipanels.h @@ -64,7 +64,8 @@ class debug_panel : public ui_panel { public: debug_panel( std::string const &Name, bool const Isopen ) - : ui_panel( Name, Isopen ) {} + : ui_panel( Name, Isopen ) { + m_eventsearch.fill( 0 ); } void update() override; void render() override; diff --git a/simulationenvironment.cpp b/simulationenvironment.cpp index dc2796bd..276adc1f 100644 --- a/simulationenvironment.cpp +++ b/simulationenvironment.cpp @@ -112,7 +112,7 @@ world_environment::update() { // twilight factor can be reset later down, so we do it here while it's still reflecting state of the sun // turbidity varies from 2-3 during the day based on overcast, 3-4 after sunset to deal with sunlight bleeding too much into the sky from below horizon m_skydome.SetTurbidity( - 2.f + 2.25f + clamp( Global.Overcast, 0.f, 1.f ) + interpolate( 0.f, 1.f, clamp( twilightfactor * 1.5f, 0.f, 1.f ) ) ); m_skydome.SetOvercastFactor( Global.Overcast ); @@ -173,8 +173,9 @@ world_environment::update() { // update the fog. setting it to match the average colour of the sky dome is cheap // but quite effective way to make the distant items blend with background better - // NOTE: base brightness calculation provides scaled up value, so we bring it back to 'real' one here - Global.FogColor = m_skydome.GetAverageHorizonColor(); + Global.FogColor = + interpolate( m_skydome.GetAverageColor(), m_skydome.GetAverageHorizonColor(), 0.33f ) + * clamp( Global.fLuminance, 0.25f, 1.f ); // weather-related simulation factors Global.FrictionWeatherFactor = ( diff --git a/skydome.cpp b/skydome.cpp index 7af63d0a..3926b710 100644 --- a/skydome.cpp +++ b/skydome.cpp @@ -264,6 +264,10 @@ void CSkyDome::RebuildColors() { colorconverter.z = 1.0f - std::exp( -m_expfactor * colorconverter.z ); } + if( colorconverter.z > 0.85f ) { + colorconverter.z = 0.85f + ( colorconverter.z - 0.85f ) * 0.35f; + } + colorconverter.y = clamp( colorconverter.y * 1.15f, 0.0f, 1.0f ); // desaturate sky colour, based on overcast level if( colorconverter.y > 0.0f ) { @@ -310,14 +314,14 @@ void CSkyDome::RebuildColors() { // save m_colours[ i ] = color; averagecolor += color; - if( ( m_vertices.size() - i ) <= ( m_tesselation * 2 ) ) { + if( ( m_vertices.size() - i ) <= ( m_tesselation * 3 + 3 ) ) { // calculate horizon colour from the bottom band of tris averagehorizoncolor += color; } } m_averagecolour = glm::max( glm::vec3(), averagecolor / static_cast( m_vertices.size() ) ); - m_averagehorizoncolour = glm::max( glm::vec3(), averagehorizoncolor / static_cast( m_tesselation * 2 ) ); + m_averagehorizoncolour = glm::max( glm::vec3(), averagehorizoncolor / static_cast( m_tesselation * 3 + 3 ) ); m_dirty = true; } diff --git a/uilayer.cpp b/uilayer.cpp index c94d01c9..5c4c0ab0 100644 --- a/uilayer.cpp +++ b/uilayer.cpp @@ -77,6 +77,7 @@ ui_layer::init( GLFWwindow *Window ) { static ImWchar const glyphranges[] = { 0x0020, 0x00FF, // ascii + extension + 0x0100, 0x017F, // latin extended-a 0x2070, 0x2079, // superscript 0x2500, 0x256C, // box drawings 0, diff --git a/version.h b/version.h index 35df6122..9f1a7029 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 305 +#define VERSION_MINOR 324 #define VERSION_REVISION 0 From 893e7e9c44fde3ac3881fad917676994036ddd2c Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Tue, 30 Mar 2021 18:06:24 +0200 Subject: [PATCH 29/63] build 210330. minor bug fixes --- Driver.cpp | 75 ++++++++++++++++++++++++++++++--------- Driver.h | 2 ++ Globals.cpp | 2 +- Globals.h | 3 ++ Train.cpp | 25 +++++++------ driverhints.cpp | 4 +-- drivermouseinput.cpp | 3 ++ driveruipanels.cpp | 11 ++++++ opengl33renderer.cpp | 15 +++++--- opengl33renderer.h | 1 + simulationenvironment.cpp | 2 +- skydome.cpp | 20 +++++------ version.h | 2 +- 13 files changed, 118 insertions(+), 47 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index fd24f51b..9108fe4d 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -2064,10 +2064,6 @@ void TController::Activation() } if (pVehicle != initialvehicle) { // jeśli zmieniony został pojazd prowadzony - auto *train { simulation::Trains.find( initialvehicle->name() ) }; - if( train ) { - train->MoveToVehicle( pVehicle ); - } ControllingSet(); // utworzenie połączenia do sterowanego pojazdu (może się zmienić) - silnikowy dla EZT if( ( mvOccupied->BrakeCtrlPosNo > 0 ) && ( ( mvOccupied->BrakeSystem == TBrakeSystem::Pneumatic ) @@ -2089,13 +2085,20 @@ void TController::Activation() mvOccupied->CabOccupied = iDirection; // aktywacja kabiny w prowadzonym pojeżdzie (silnikowy może być odwrotnie?) mvOccupied->CabActivisation(); // uruchomienie kabin w członach DirectionForward(true); // nawrotnik do przodu + mvOccupied->SpringBrakeActivate( initialspringbrakestate ); /* // NOTE: this won't restore local brake if the vehicle has integrated local brake control // TBD, TODO: fix or let the ai activate the brake again as part of its standard logic? if (initiallocalbrakelevel > 0.0) // hamowanie tylko jeśli był wcześniej zahamowany (bo możliwe, że jedzie!) mvOccupied->LocalBrakePosA = initiallocalbrakelevel; // zahamuj jak wcześniej */ - mvOccupied->SpringBrakeActivate( initialspringbrakestate ); + if( pVehicle != initialvehicle ) { + auto *train { simulation::Trains.find( initialvehicle->name() ) }; + if( train ) { + train->MoveToVehicle( pVehicle ); + } + } + CheckVehicles(); // sprawdzenie składu, AI zapali światła TableClear(); // resetowanie tabelki skanowania torów } @@ -6745,10 +6748,42 @@ TController::UpdateConnect() { } } +int +TController::unit_count( int const Threshold ) const { + + auto *vehicle { pVehicle }; + auto unitcount { 1 }; + do { + auto const decoupledend{ ( vehicle->DirectionGet() > 0 ? // numer sprzęgu od strony czoła składu + end::rear : + end::front ) }; + auto const coupling { vehicle->MoverParameters->Couplers[ decoupledend ].CouplingFlag }; + if( coupling == coupling::faux ) { + break; + } + // jeżeli sprzęg zablokowany to liczymy człony jako jeden + if( ( coupling & coupling::permanent ) == 0 ) { + ++unitcount; + } + vehicle = vehicle->Next(); + } while( unitcount < Threshold ); + + return unitcount; +} + void TController::UpdateDisconnect() { if( iVehicleCount >= 0 ) { + // early test for human drivers, who might disregard the proper procedure, or perform it before they receive the order to + if( false == AIControllFlag ) { + // iVehicleCount = 0 means the consist should be reduced to (1) leading unit, thus we increase our test values by 1 + if( unit_count( iVehicleCount + 2 ) <= iVehicleCount + 1 ) { + iVehicleCount = -2; // odczepiono, co było do odczepienia + return; // we'll wrap up the procedure on the next update beat + } + } + // regular uncoupling procedure, performed by ai and naively expected from the human drivers // 3rd stage: change direction, compress buffers and uncouple if( iDirection != iDirectionOrder ) { cue_action( locale::string::driver_hint_mastercontrollersetreverserunlock ); @@ -6814,6 +6849,10 @@ TController::UpdateDisconnect() { } // 2nd stage: apply consist brakes and change direction if( ( iDrivigFlags & movePress ) == 0 ) { + // store initial consist direction + if( !iDirectionBackup ) { + iDirectionBackup = iDirection; + } if( false == IsConsistBraked ) { WriteLog( "Uncoupling [" + mvOccupied->Name + "]: applying consist brakes..." ); cue_action( locale::string::driver_hint_trainbrakeapply ); @@ -6834,19 +6873,23 @@ TController::UpdateDisconnect() { } // odczepianie if( iVehicleCount < 0 ) { // 4th stage: restore initial direction - if( ( iDrivigFlags & movePress ) != 0 ) { - if( eStopReason == stopNone ) { // HACK: use current speed limit to discern whether we're entering this stage for the first time - WriteLog( "Uncoupling [" + mvOccupied->Name + "]: second direction change" ); - iDirectionOrder = -iDirection; - cue_action( locale::string::driver_hint_mastercontrollersetreverserunlock ); - cue_action( locale::string::driver_hint_directionother ); // zmiana kierunku jazdy na właściwy - } - if( iDirection == iDirectionOrder ) { - iDrivigFlags &= ~movePress; // koniec dociskania + if( iDirectionBackup ) { + iDirectionOrder = iDirectionBackup.value(); + iDirectionBackup.reset(); + } + if( iDirection != iDirectionOrder ) { + WriteLog( "Uncoupling [" + mvOccupied->Name + "]: second direction change" ); + cue_action( locale::string::driver_hint_mastercontrollersetreverserunlock ); + cue_action( locale::string::driver_hint_directionother ); // zmiana kierunku jazdy na właściwy + } + // 5th stage: clean up and move on to next order + if( iDirection == iDirectionOrder ) { + iDrivigFlags &= ~movePress; // koniec dociskania + while( ( OrderCurrentGet() & Disconnect ) != 0 ) { JumpToNextOrder(); // zmieni światła - TableClear(); // skanowanie od nowa - iDrivigFlags &= ~moveStartHorn; // bez trąbienia przed ruszeniem } + TableClear(); // skanowanie od nowa + iDrivigFlags &= ~moveStartHorn; // bez trąbienia przed ruszeniem } } } diff --git a/Driver.h b/Driver.h index 899d4ee4..a4de7c4a 100644 --- a/Driver.h +++ b/Driver.h @@ -317,6 +317,7 @@ private: void check_departure(); void UpdateConnect(); void UpdateDisconnect(); + int unit_count( int const Threshold ) const; void UpdateChangeDirection(); void UpdateLooseShunt(); void UpdateObeyTrain(); @@ -381,6 +382,7 @@ private: double SwitchClearDist { 0.0 }; // distance to point after farthest detected switch int iDirection = 0; // kierunek jazdy względem sprzęgów pojazdu, w którym siedzi AI (1=przód,-1=tył) int iDirectionOrder = 0; //żadany kierunek jazdy (służy do zmiany kierunku) + std::optional iDirectionBackup; // consist direction to be restored after coupling/uncoupling and similar direction-changing operations int iVehicleCount = -2; // wartość neutralna // ilość pojazdów do odłączenia albo zabrania ze składu (-1=wszystkie) int iCoupler = 0; // maska sprzęgu, jaką należy użyć przy łączeniu (po osiągnięciu trybu Connect), 0 gdy jazda bez łączenia int iDriverFailCount = 0; // licznik błędów AI diff --git a/Globals.cpp b/Globals.cpp index 07799b96..ca780f4f 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -178,7 +178,7 @@ global_settings::ConfigParse(cParser &Parser) { // selected device for audio renderer Parser.getTokens(); Parser >> PausedVolume; - EnvironmentAmbientVolume = clamp( EnvironmentAmbientVolume, 0.f, 1.f ); + PausedVolume = clamp( PausedVolume, 0.f, 1.f ); } // else if (str==AnsiString("renderalpha")) //McZapkie-1312302 - dwuprzebiegowe renderowanie // bRenderAlpha=(GetNextSymbol().LowerCase()==AnsiString("yes")); diff --git a/Globals.h b/Globals.h index c6144ced..9c0f6d3b 100644 --- a/Globals.h +++ b/Globals.h @@ -235,6 +235,9 @@ struct global_settings { std::optional> network_client; double desync = 0.0; + float m_skysaturationcorrection{ 1.65f }; + float m_skyhuecorrection{ 0.5f }; + // methods void LoadIniFile( std::string asFileName ); void ConfigParse( cParser &parser ); diff --git a/Train.cpp b/Train.cpp index ce2856e0..213a9243 100644 --- a/Train.cpp +++ b/Train.cpp @@ -1152,7 +1152,7 @@ void TTrain::OnCommand_tempomattoggle( TTrain *Train, command_data const &Comman if( Command.action == GLFW_REPEAT ) { return; } - if( Train->ggScndCtrlButton.type() == TGaugeType::push ) { + if( Train->ggScndCtrlButton.is_push() ) { // impulse switch if( Command.action == GLFW_RELEASE ) { // just move the button(s) back to default position @@ -2565,8 +2565,8 @@ void TTrain::change_pantograph_selection( int const Change ) { auto const preset { presets[ selection ] - '0' }; auto const swapends { cab_to_end() != end::front }; // check desired states for both pantographs; value: whether the pantograph should be raised - auto const frontstate { preset & ( swapends ? 1 : 2 ) }; - auto const rearstate { preset & ( swapends ? 2 : 1 ) }; + auto const frontstate { preset & ( swapends ? 2 : 1 ) }; + auto const rearstate { preset & ( swapends ? 1 : 2 ) }; // potentially adjust pantograph valves mvOccupied->OperatePantographValve( end::front, ( frontstate ? operation_t::enable : operation_t::disable ) ); mvOccupied->OperatePantographValve( end::rear, ( rearstate ? operation_t::enable : operation_t::disable ) ); @@ -7027,6 +7027,11 @@ bool TTrain::Update( double const Deltatime ) dsbNastawnikBocz ); ggScndCtrl.Update(); } + if( ggScndCtrlButton.is_toggle() ) { + ggScndCtrlButton.UpdateValue( + ( ( mvControlled->ScndCtrlPos > 0 ) ? 1.f : 0.f ), + dsbSwitch ); + } ggScndCtrlButton.Update( lowvoltagepower ); ggScndCtrlOffButton.Update( lowvoltagepower ); ggDistanceCounterButton.Update(); @@ -8393,20 +8398,18 @@ TTrain::MoveToVehicle(TDynamicObject *target) { if( Dynamic()->Mechanik ) { Dynamic()->Mechanik->MoveTo( target ); - } - - DynamicSet(target); - - Occupied()->LimPipePress = Occupied()->PipePress; - Occupied()->CabActivisation( true ); // załączenie rozrządu (wirtualne kabiny) - Dynamic()->MechInside = true; - if( Dynamic()->Mechanik ) { Dynamic()->Controller = Dynamic()->Mechanik->AIControllFlag; Dynamic()->Mechanik->DirectionChange(); } else { Dynamic()->Controller = Humandriver; } + Dynamic()->MechInside = true; + + DynamicSet(target); + + Occupied()->LimPipePress = Occupied()->PipePress; + Occupied()->CabActivisation( true ); // załączenie rozrządu (wirtualne kabiny) } else { Dynamic()->bDisplayCab = false; Dynamic()->ABuSetModelShake( {} ); diff --git a/driverhints.cpp b/driverhints.cpp index 92946ff9..d673ba88 100644 --- a/driverhints.cpp +++ b/driverhints.cpp @@ -1010,7 +1010,7 @@ TController::cue_action( locale::string const Action, float const Actionparamete hint( Action, [this](float const Parameter) -> bool { - return ( ( mvOccupied->Doors.has_warning == false ) || ( mvOccupied->DepartureSignal == true ) || mvOccupied->Vel > 5.0 ); } ); + return ( ( mvOccupied->Doors.has_warning == false ) || ( mvOccupied->Doors.has_autowarning == true ) || ( mvOccupied->DepartureSignal == true ) || mvOccupied->Vel > 5.0 ); } ); break; } case locale::string::driver_hint_departuresignaloff: { @@ -1021,7 +1021,7 @@ TController::cue_action( locale::string const Action, float const Actionparamete hint( Action, [this](float const Parameter) -> bool { - return ( ( mvOccupied->Doors.has_warning == false ) || ( mvOccupied->DepartureSignal == false ) ); } ); + return ( ( mvOccupied->Doors.has_warning == false ) || ( mvOccupied->Doors.has_autowarning == true ) || ( mvOccupied->DepartureSignal == false ) ); } ); break; } // consist doors diff --git a/drivermouseinput.cpp b/drivermouseinput.cpp index eaa61d28..0df33342 100644 --- a/drivermouseinput.cpp +++ b/drivermouseinput.cpp @@ -527,6 +527,9 @@ drivermouse_input::default_bindings() { { "tempomat_sw:", { user_command::tempomattoggle, user_command::none } }, + { "tempomatoff_sw:", { + user_command::tempomattoggle, + user_command::none } }, { "dirkey:", { user_command::reverserincrease, user_command::reverserdecrease } }, diff --git a/driveruipanels.cpp b/driveruipanels.cpp index fbb1dcdb..64b02135 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -1420,6 +1420,17 @@ debug_panel::render_section_settings() { ImGui::PopStyleColor(); // reflection fidelity ImGui::SliderInt( ( to_string( Global.reflectiontune.fidelity ) + "###reflectionfidelity" ).c_str(), &Global.reflectiontune.fidelity, 0, 2, "Reflection fidelity" ); + if( DebugModeFlag ) { + // sky sliders + { + ImGui::SliderFloat( + ( to_string( Global.m_skysaturationcorrection, 2, 5 ) + "###skysaturation" ).c_str(), &Global.m_skysaturationcorrection, 0.0f, 3.0f, "Sky saturation" ); + } + { + ImGui::SliderFloat( + ( to_string( Global.m_skyhuecorrection, 2, 5 ) + "###skyhue" ).c_str(), &Global.m_skyhuecorrection, 0.0f, 1.0f, "Sky hue correction" ); + } + } ImGui::PushStyleColor( ImGuiCol_Text, { Global.UITextColor.r, Global.UITextColor.g, Global.UITextColor.b, Global.UITextColor.a } ); ImGui::TextUnformatted( "Sound" ); diff --git a/opengl33renderer.cpp b/opengl33renderer.cpp index 8b9e7ef6..5f00b30f 100644 --- a/opengl33renderer.cpp +++ b/opengl33renderer.cpp @@ -401,7 +401,12 @@ bool opengl33_renderer::init_viewport(viewport_config &vp) vp.main_texv = std::make_unique(); vp.main_texv->alloc_rendertarget(Global.gfx_postfx_motionblur_format, GL_RG, vp.width, vp.height); vp.main_fb->attach(*vp.main_texv, GL_COLOR_ATTACHMENT1); - vp.main_fb->setup_drawing(2); + + vp.main_texd = std::make_unique(); + vp.main_texd->alloc_rendertarget( Global.gfx_format_depth, GL_DEPTH_COMPONENT, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE ); + vp.main_fb->attach( *vp.main_texd, GL_DEPTH_ATTACHMENT ); + + vp.main_fb->setup_drawing(1); WriteLog("motion blur enabled"); } @@ -782,15 +787,18 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) if (Global.gfx_postfx_motionblur_enabled) { gl::program::unbind(); + vp.main_fb->clear(GL_COLOR_BUFFER_BIT); vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0); vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT1); + vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_DEPTH_BUFFER_BIT, GL_DEPTH_ATTACHMENT); + + vp.main_fb->setup_drawing(1); // restore draw buffers after blit operation model_ubs.param[0].x = m_framerate / (1.0 / Global.gfx_postfx_motionblur_shutter); model_ubo->update(model_ubs); - m_pfx_motionblur->apply({vp.main_tex.get(), vp.main_texv.get()}, vp.main2_fb.get()); - vp.main_fb->setup_drawing(1); // restore draw buffers after blit operation + m_pfx_motionblur->apply({vp.main_tex.get(), vp.main_texv.get(), vp.main_texd.get()}, vp.main2_fb.get()); } else { @@ -804,7 +812,6 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) glViewport(0, 0, target_size.x, target_size.y); if( Global.gfx_postfx_chromaticaberration_enabled ) { - // NOTE: for some unexplained reason need this setup_drawing() call here for the tonemapping effects to show up on the main_tex? m_pfx_tonemapping->apply( *vp.main2_tex, vp.main_fb.get() ); m_pfx_chromaticaberration->apply( *vp.main_tex, nullptr ); } diff --git a/opengl33renderer.h b/opengl33renderer.h index 28545ba6..1a05c4b9 100644 --- a/opengl33renderer.h +++ b/opengl33renderer.h @@ -208,6 +208,7 @@ class opengl33_renderer : public gfx_renderer { std::unique_ptr main_fb; std::unique_ptr main_texv; std::unique_ptr main_tex; + std::unique_ptr main_texd; std::unique_ptr main2_fb; std::unique_ptr main2_tex; diff --git a/simulationenvironment.cpp b/simulationenvironment.cpp index 276adc1f..6969663e 100644 --- a/simulationenvironment.cpp +++ b/simulationenvironment.cpp @@ -174,7 +174,7 @@ world_environment::update() { // update the fog. setting it to match the average colour of the sky dome is cheap // but quite effective way to make the distant items blend with background better Global.FogColor = - interpolate( m_skydome.GetAverageColor(), m_skydome.GetAverageHorizonColor(), 0.33f ) + interpolate( m_skydome.GetAverageColor(), m_skydome.GetAverageHorizonColor(), 0.25f ) * clamp( Global.fLuminance, 0.25f, 1.f ); // weather-related simulation factors diff --git a/skydome.cpp b/skydome.cpp index 3926b710..446bcf7f 100644 --- a/skydome.cpp +++ b/skydome.cpp @@ -268,10 +268,10 @@ void CSkyDome::RebuildColors() { colorconverter.z = 0.85f + ( colorconverter.z - 0.85f ) * 0.35f; } - colorconverter.y = clamp( colorconverter.y * 1.15f, 0.0f, 1.0f ); + colorconverter.y = clamp( colorconverter.y * Global.m_skysaturationcorrection, 0.0f, 1.0f ); // desaturate sky colour, based on overcast level if( colorconverter.y > 0.0f ) { - colorconverter.y *= ( 1.0f - m_overcast ); + colorconverter.y *= ( 1.0f - 0.5f * m_overcast ); } // override the hue, based on sun height above the horizon. crude way to deal with model shortcomings @@ -288,13 +288,8 @@ void CSkyDome::RebuildColors() { color = colors::HSVtoRGB(colorconverter); - color = interpolate( color, shiftedcolor, shiftfactor ); -/* - // gamma control - color.x = std::pow( color.x, m_gammacorrection ); - color.x = std::pow( color.y, m_gammacorrection ); - color.x = std::pow( color.z, m_gammacorrection ); -*/ + color = interpolate( color, shiftedcolor, shiftfactor * Global.m_skyhuecorrection ); + // crude correction for the times where the model breaks (late night) // TODO: use proper night sky calculation for these times instead if( ( color.x <= 0.05f ) @@ -304,14 +299,17 @@ void CSkyDome::RebuildColors() { color.z = 0.75f * std::max( color.z + m_sundirection.y, 0.075f ); color.x = 0.20f * color.z; color.y = 0.65f * color.z; - color = color * ( 1.15f - vertex.y ); // simple gradient, darkening towards the top } // gamma correction color = glm::pow( color, gammacorrection ); +/* if( Global.GfxFramebufferSRGB ) { color = glm::pow( color, glm::vec3( 2.2f ) - ( gammacorrection * 0.5f ) ); } - // save +*/ + // simple gradient, darkening towards the top + color *= ( 1.15f - vertex.y ); + // save m_colours[ i ] = color; averagecolor += color; if( ( m_vertices.size() - i ) <= ( m_tesselation * 3 + 3 ) ) { diff --git a/version.h b/version.h index 9f1a7029..a20c0567 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 324 +#define VERSION_MINOR 330 #define VERSION_REVISION 0 From 716aefc2edeea9b3895430328ea4edf2aba31220 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Thu, 1 Apr 2021 17:23:20 +0200 Subject: [PATCH 30/63] pantograph valves state update cab control, skydome tweaks, minor bug fixes --- McZapkie/MOVER.h | 2 +- Train.cpp | 77 ++++++++++++++++++++++++++++++++++------- Train.h | 5 +++ command.cpp | 2 ++ command.h | 2 ++ driverkeyboardinput.cpp | 2 ++ drivermouseinput.cpp | 3 ++ mtable.cpp | 36 +++++++++---------- mtable.h | 1 + skydome.cpp | 5 +-- translation.cpp | 6 ++++ translation.h | 2 ++ version.h | 2 +- 13 files changed, 109 insertions(+), 36 deletions(-) diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 37fa05e4..3dc86817 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1168,7 +1168,7 @@ public: bool UniCtrlIntegratedBrakeCtrl = false; /*zintegrowany nastawnik JH obsluguje hamowanie*/ bool UniCtrlIntegratedLocalBrakeCtrl = false; /*zintegrowany nastawnik JH obsluguje hamowanie hamulcem pomocniczym*/ int UniCtrlNoPowerPos{ 0 }; // cached highesr position not generating traction force - std::pair> PantsPreset { "", { 0, 0 } }; // pantograph preset switches; .first holds possible setups as chars, .second holds currently selected preset in each cab + std::pair> PantsPreset { "0132", { 0, 0 } }; // pantograph preset switches; .first holds possible setups as chars, .second holds currently selected preset in each cab /*-sekcja parametrow dla lokomotywy elektrycznej*/ TSchemeTable RList; /*lista rezystorow rozruchowych i polaczen silnikow, dla dizla: napelnienia*/ diff --git a/Train.cpp b/Train.cpp index 213a9243..bfade70a 100644 --- a/Train.cpp +++ b/Train.cpp @@ -285,6 +285,8 @@ TTrain::commandhandler_map const TTrain::m_commandhandlers = { { user_command::pantographtoggleselected, &TTrain::OnCommand_pantographtoggleselected }, { user_command::pantographraiseselected, &TTrain::OnCommand_pantographraiseselected }, { user_command::pantographlowerselected, &TTrain::OnCommand_pantographlowerselected }, + { user_command::pantographvalvesupdate, &TTrain::OnCommand_pantographvalvesupdate }, + { user_command::pantographvalvesoff, &TTrain::OnCommand_pantographvalvesoff }, { user_command::linebreakertoggle, &TTrain::OnCommand_linebreakertoggle }, { user_command::linebreakeropen, &TTrain::OnCommand_linebreakeropen }, { user_command::linebreakerclose, &TTrain::OnCommand_linebreakerclose }, @@ -2552,6 +2554,20 @@ void TTrain::OnCommand_pantographlowerselected( TTrain *Train, command_data cons } } +void TTrain::update_pantograph_valves() { + + auto const &presets { mvOccupied->PantsPreset.first }; + auto &selection { mvOccupied->PantsPreset.second[ cab_to_end() ] }; + + auto const preset { presets[ selection ] - '0' }; + auto const swapends { cab_to_end() != end::front }; + // check desired states for both pantographs; value: whether the pantograph should be raised + auto const frontstate { preset & ( swapends ? 2 : 1 ) }; + auto const rearstate { preset & ( swapends ? 1 : 2 ) }; + mvOccupied->OperatePantographValve( end::front, ( frontstate ? operation_t::enable : operation_t::disable ) ); + mvOccupied->OperatePantographValve( end::rear, ( rearstate ? operation_t::enable : operation_t::disable ) ); +} + void TTrain::change_pantograph_selection( int const Change ) { auto const &presets { mvOccupied->PantsPreset.first }; @@ -2561,15 +2577,45 @@ void TTrain::change_pantograph_selection( int const Change ) { if( selection == initialstate ) { return; } // no change, nothing to do - // configure pantograph valves matching the new state - auto const preset { presets[ selection ] - '0' }; - auto const swapends { cab_to_end() != end::front }; - // check desired states for both pantographs; value: whether the pantograph should be raised - auto const frontstate { preset & ( swapends ? 2 : 1 ) }; - auto const rearstate { preset & ( swapends ? 1 : 2 ) }; - // potentially adjust pantograph valves - mvOccupied->OperatePantographValve( end::front, ( frontstate ? operation_t::enable : operation_t::disable ) ); - mvOccupied->OperatePantographValve( end::rear, ( rearstate ? operation_t::enable : operation_t::disable ) ); + // potentially adjust pantograph valves to match the new state + if( false == m_controlmapper.contains( "pantvalves_sw:" ) ) { + update_pantograph_valves(); + } +} + +void TTrain::OnCommand_pantographvalvesupdate( TTrain *Train, command_data const &Command ) { + + if( Command.action == GLFW_REPEAT ) { return; } + + if( Command.action == GLFW_PRESS ) { + // implement action + Train->update_pantograph_valves(); + // visual feedback + Train->ggPantValvesButton.UpdateValue( 1.0, Train->dsbSwitch ); + } + else if( Command.action == GLFW_RELEASE ) { + // visual feedback + // NOTE: pantvalves_sw: is a specialized button, with no toggle behavior support + Train->ggPantValvesButton.UpdateValue( 0.5, Train->dsbSwitch ); + } +} + +void TTrain::OnCommand_pantographvalvesoff( TTrain *Train, command_data const &Command ) { + + if( Command.action == GLFW_REPEAT ) { return; } + + if( Command.action == GLFW_PRESS ) { + // implement action + Train->mvOccupied->OperatePantographValve( end::front, operation_t::disable ); + Train->mvOccupied->OperatePantographValve( end::rear, operation_t::disable ); + // visual feedback + Train->ggPantValvesButton.UpdateValue( 0.0, Train->dsbSwitch ); + } + else if( Command.action == GLFW_RELEASE ) { + // visual feedback + // NOTE: pantvalves_sw: is a specialized button, with no toggle behavior support + Train->ggPantValvesButton.UpdateValue( 0.5, Train->dsbSwitch ); + } } void TTrain::OnCommand_pantographcompressorvalvetoggle( TTrain *Train, command_data const &Command ) { @@ -7196,6 +7242,7 @@ bool TTrain::Update( double const Deltatime ) ggPantAllDownButton.Update(); ggPantSelectedDownButton.Update(); ggPantSelectedButton.Update(); + ggPantValvesButton.Update(); ggPantCompressorButton.Update(); ggPantCompressorValve.Update(); @@ -8398,15 +8445,18 @@ TTrain::MoveToVehicle(TDynamicObject *target) { if( Dynamic()->Mechanik ) { Dynamic()->Mechanik->MoveTo( target ); + } + + DynamicSet( target ); + + Dynamic()->MechInside = true; + if( Dynamic()->Mechanik ) { Dynamic()->Controller = Dynamic()->Mechanik->AIControllFlag; Dynamic()->Mechanik->DirectionChange(); } else { Dynamic()->Controller = Humandriver; } - Dynamic()->MechInside = true; - - DynamicSet(target); Occupied()->LimPipePress = Occupied()->PipePress; Occupied()->CabActivisation( true ); // załączenie rozrządu (wirtualne kabiny) @@ -8612,6 +8662,7 @@ void TTrain::clear_cab_controls() ggPantAllDownButton.Clear(); ggPantSelectedButton.Clear(); ggPantSelectedDownButton.Clear(); + ggPantValvesButton.Clear(); ggPantCompressorButton.Clear(); ggPantCompressorValve.Clear(); ggI1B.Clear(); @@ -8806,6 +8857,7 @@ void TTrain::set_cab_controls( int const Cab ) { 1.f : 0.f ) ); } + ggPantValvesButton.PutValue( 0.5f ); // auxiliary compressor ggPantCompressorValve.PutValue( mvControlled->bPantKurek3 ? @@ -9308,6 +9360,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con { "pantalloff_sw:", ggPantAllDownButton }, { "pantselected_sw:", ggPantSelectedButton }, { "pantselectedoff_sw:", ggPantSelectedDownButton }, + { "pantvalves_sw:", ggPantValvesButton }, { "pantcompressor_sw:", ggPantCompressorButton }, { "pantcompressorvalve_sw:", ggPantCompressorValve }, { "trainheating_sw:", ggTrainHeatingButton }, diff --git a/Train.h b/Train.h index 157a0ec8..35cc593c 100644 --- a/Train.h +++ b/Train.h @@ -190,6 +190,8 @@ class TTrain { void set_paired_open_motor_connectors_button( bool const State ); // helper, common part of pantograph selection methods void change_pantograph_selection( int const Change ); + // helper, common part of pantograh valves state update methods + void update_pantograph_valves(); // update function subroutines void update_sounds( double const Deltatime ); void update_sounds_runningnoise( sound_source &Sound ); @@ -291,6 +293,8 @@ class TTrain { 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_pantographvalvesupdate( TTrain *Train, command_data const &Command ); + static void OnCommand_pantographvalvesoff( TTrain *Train, command_data const &Command ); static void OnCommand_linebreakertoggle( TTrain *Train, command_data const &Command ); static void OnCommand_linebreakeropen( TTrain *Train, command_data const &Command ); static void OnCommand_linebreakerclose( TTrain *Train, command_data const &Command ); @@ -599,6 +603,7 @@ public: // reszta może by?publiczna TGauge ggPantAllDownButton; TGauge ggPantSelectedButton; TGauge ggPantSelectedDownButton; + TGauge ggPantValvesButton; TGauge ggPantCompressorButton; TGauge ggPantCompressorValve; // Winger 020304 - wlacznik ogrzewania diff --git a/command.cpp b/command.cpp index 6c79b2dc..fecd991f 100644 --- a/command.cpp +++ b/command.cpp @@ -193,6 +193,8 @@ commanddescription_sequence Commands_descriptions = { { "pantographtoggleselected", command_target::vehicle }, { "pantographraiseselected", command_target::vehicle }, { "pantographlowerselected", command_target::vehicle }, + { "pantographvalvesupdate", command_target::vehicle }, + { "pantographvalvesoff", command_target::vehicle }, { "heatingtoggle", command_target::vehicle }, { "heatingenable", command_target::vehicle }, { "heatingdisable", command_target::vehicle }, diff --git a/command.h b/command.h index af9e5864..b8380f22 100644 --- a/command.h +++ b/command.h @@ -184,6 +184,8 @@ enum class user_command { pantographtoggleselected, pantographraiseselected, pantographlowerselected, + pantographvalvesupdate, + pantographvalvesoff, heatingtoggle, heatingenable, heatingdisable, diff --git a/driverkeyboardinput.cpp b/driverkeyboardinput.cpp index 008f6806..70b2016c 100644 --- a/driverkeyboardinput.cpp +++ b/driverkeyboardinput.cpp @@ -193,6 +193,8 @@ driverkeyboard_input::default_bindings() { { user_command::pantographtoggleselected, GLFW_KEY_O | keymodifier::shift | keymodifier::control }, // pantographraiseselected, // pantographlowerselected, + // pantographvalvesupdate, + // pantographvalvesoff, { user_command::heatingtoggle, GLFW_KEY_H }, // heatingenable, // heatingdisable, diff --git a/drivermouseinput.cpp b/drivermouseinput.cpp index 0df33342..e37b433e 100644 --- a/drivermouseinput.cpp +++ b/drivermouseinput.cpp @@ -822,6 +822,9 @@ drivermouse_input::default_bindings() { { "pantselect_sw:", { user_command::pantographselectnext, user_command::pantographselectprevious } }, + { "pantvalves_sw:", { + user_command::pantographvalvesupdate, + user_command::pantographvalvesoff } }, { "pantcompressor_sw:", { user_command::pantographcompressoractivate, user_command::none } }, diff --git a/mtable.cpp b/mtable.cpp index fafc852e..19078968 100644 --- a/mtable.cpp +++ b/mtable.cpp @@ -310,7 +310,6 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) // pliku} // ConversionError:=-7 {błąd niezgodności} TrainName = s; // nadanie nazwy z pliku TXT (bez ścieżki do pliku) - bool isCategory = false; while (fin >> s || fin.bad()) { if (s.find("_______|") != std::string::npos) @@ -319,29 +318,25 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) } if (s == "Kategoria") { - isCategory = true; - break; + do + { + fin >> s; + } while (!((s == "|") || (fin.bad()))); + fin >> TrainCategory; + continue; } + if (s == "Nazwa") + { + do + { + fin >> s; + } while (!((s == "|") || (fin.bad()))); + fin >> TrainLabel; + continue; + } } // while (!(s == "Seria")); - if (isCategory) - { - do - { - fin >> s; - } while (!((s == "|") || (fin.bad()))); - fin >> TrainCategory; - } // else { /*czytaj naglowek*/ - if (isCategory) - { - while (fin >> s || !fin.bad()) - { - if (s.find("_______|") != std::string::npos) - break; - // fin >> s; - } // while (!(s.find("_______|") != std::string::npos) || fin.eof()); - } while (fin >> s || !fin.bad()) { if (s == "[") @@ -647,6 +642,7 @@ void TTrainParameters::serialize( dictionary_source *Output ) const { Output->insert( "trainnumber", TrainName ); Output->insert( "traincategory", TrainCategory ); + Output->insert( "trainname", TrainLabel ); Output->insert( "train_brakingmassratio", BrakeRatio ); Output->insert( "train_enginetype", LocSeries ); Output->insert( "train_engineload", LocLoad ); diff --git a/mtable.h b/mtable.h index a30757b7..d5aea177 100644 --- a/mtable.h +++ b/mtable.h @@ -51,6 +51,7 @@ class TTrainParameters public: std::string TrainName; std::string TrainCategory; + std::string TrainLabel; double TTVmax; std::string Relation1; std::string Relation2; // nazwy stacji danego odcinka diff --git a/skydome.cpp b/skydome.cpp index 446bcf7f..b10629ee 100644 --- a/skydome.cpp +++ b/skydome.cpp @@ -300,6 +300,9 @@ void CSkyDome::RebuildColors() { color.x = 0.20f * color.z; color.y = 0.65f * color.z; } + // simple gradient, darkening towards the top + color *= clamp( ( 1.25f - vertex.y ), 0.f, 1.f ); +// color *= ( 1.25f - vertex.y ); // gamma correction color = glm::pow( color, gammacorrection ); /* @@ -307,8 +310,6 @@ void CSkyDome::RebuildColors() { color = glm::pow( color, glm::vec3( 2.2f ) - ( gammacorrection * 0.5f ) ); } */ - // simple gradient, darkening towards the top - color *= ( 1.15f - vertex.y ); // save m_colours[ i ] = color; averagecolor += color; diff --git a/translation.cpp b/translation.cpp index 5df19be4..1de6db36 100644 --- a/translation.cpp +++ b/translation.cpp @@ -191,6 +191,7 @@ init() { "second controller", "shunt mode power", "tempomat", + "tempomat", "tempomat (speed)", "tempomat (speed)", "tempomat (power)", @@ -303,6 +304,7 @@ init() { "selected pantograph", "selected pantograph", "selected pantograph", + "selected pantograph", "pantograph compressor", "pantograph 3-way valve", "heating", @@ -502,6 +504,7 @@ init() { "nastawnik dodatkowy", "sterowanie analogowe", "tempomat", + "tempomat", "tempomat (predkosc)", "tempomat (predkosc)", "tempomat (moc)", @@ -614,6 +617,7 @@ init() { "wybrany pantograf", "wybrany pantograf", "wybrany pantograf", + "wybrany pantograf", "sprezarka pantografow", "kurek trojdrogowy pantografow", "ogrzewanie pociagu", @@ -660,6 +664,7 @@ init() { "scndctrl:", "shuntmodepower:", "tempomat_sw:", + "tempomatoff_sw:", "speedinc_bt:", "speeddec_bt:", "speedctrlpowerinc_bt:", @@ -772,6 +777,7 @@ init() { "pantselected_sw:", "pantselectedoff_sw:", "pantselect_sw:", + "pantvalves_sw:", "pantcompressor_sw:", "pantcompressorvalve_sw:", "trainheating_sw:", diff --git a/translation.h b/translation.h index 3dae8018..bb57b8ac 100644 --- a/translation.h +++ b/translation.h @@ -180,6 +180,7 @@ enum string { cab_scndctrl, cab_shuntmodepower, cab_tempomat, + cab_tempomatoff, cab_speedinc, cab_speeddec, cab_speedctrlpowerinc, @@ -289,6 +290,7 @@ enum string { cab_pantselected_sw, cab_pantselectedoff_sw, cab_pantselect_sw, + cab_pantvalves_sw, cab_pantcompressor_sw, cab_pantcompressorvalve_sw, cab_trainheating_sw, diff --git a/version.h b/version.h index a20c0567..d72439c9 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 330 +#define VERSION_MINOR 331 #define VERSION_REVISION 0 From 66bd63336b38668394a482d38ac77609e5a9c8a3 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Fri, 9 Apr 2021 13:36:43 +0200 Subject: [PATCH 31/63] build 210408. door opening logic enhancements, door permit sound, door permit indicator cab control --- DynObj.cpp | 45 ++++++++++++++++++++------ DynObj.h | 1 + McZapkie/MOVER.h | 5 +++ McZapkie/Mover.cpp | 31 ++++++++++-------- Train.cpp | 78 ++++++++++++++++++++++++++++++++++++---------- Train.h | 2 ++ version.h | 2 +- 7 files changed, 124 insertions(+), 40 deletions(-) diff --git a/DynObj.cpp b/DynObj.cpp index 94247d73..7037412f 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -4347,13 +4347,15 @@ void TDynamicObject::RenderSounds() { if( true == door.is_opening ) { // door sounds - // due to potential wait for the doorstep we play the sound only during actual animation - if( door.position > 0.f ) { - for( auto &doorsounds : m_doorsounds ) { - if( doorsounds.placement == side ) { - // determine left side doors from their offset - doorsounds.rsDoorOpen.play( sound_flags::exclusive ); - doorsounds.rsDoorClose.stop(); + if( ( false == door.step_unfolding ) // no wait if no doorstep + || ( MoverParameters->Doors.step_type == 2 ) ) { // no wait for rotating doorstep + if( door.position < 0.5f ) { // safety measure, to keep slightly too short sounds from repeating + for( auto &doorsounds : m_doorsounds ) { + if( doorsounds.placement == side ) { + // determine left side doors from their offset + doorsounds.rsDoorOpen.play( sound_flags::exclusive ); + doorsounds.rsDoorClose.stop(); + } } } } @@ -4579,10 +4581,9 @@ void TDynamicObject::RenderSounds() { } } - // McZapkie! - to wazne - SoundFlag wystawiane jest przez moje moduly - // gdy zachodza pewne wydarzenia komentowane dzwiekiem. + // McZapkie! - to wazne - SoundFlag wystawiane jest przez moje moduly gdy zachodza pewne wydarzenia komentowane dzwiekiem. + // pneumatic relay if( TestFlag( MoverParameters->SoundFlag, sound::pneumatic ) ) { - // pneumatic relay dsbPneumaticRelay .gain( true == TestFlag( MoverParameters->SoundFlag, sound::loud ) ? @@ -4590,6 +4591,16 @@ void TDynamicObject::RenderSounds() { 0.8f ) .play(); } + // door permit + if( TestFlag( MoverParameters->SoundFlag, sound::doorpermit ) ) { + // NOTE: current implementation doesn't discern between permit for left/right side, + // which may be undesired in weird setups with doors only on one side + // TBD, TODO: rework into dedicated sound event flag for each door location instance? + for( auto &door : m_doorsounds ) { + door.permit_granted.play( sound_flags::exclusive ); + } + } + // couplers int couplerindex { 0 }; for( auto &couplersounds : m_couplersounds ) { @@ -5848,6 +5859,18 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co } } + else if( token == "doorpermit:" ) { + sound_source soundtemplate { sound_placement::general }; + soundtemplate.deserialize( parser, sound_type::single ); + soundtemplate.owner( this ); + for( auto &door : m_doorsounds ) { + // apply configuration to all defined doors, but preserve their individual offsets + auto const dooroffset { door.permit_granted.offset() }; + door.permit_granted = soundtemplate; + door.permit_granted.offset( dooroffset ); + } + } + else if( token == "unloading:" ) { m_exchangesounds.unloading.range( MoverParameters->Dim.L * 0.5f * -1 ); m_exchangesounds.unloading.deserialize( parser, sound_type::single ); @@ -5982,6 +6005,7 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co door.unlock.offset( location ); door.step_close.offset( location ); door.step_open.offset( location ); + door.permit_granted.offset( location ); m_doorsounds.emplace_back( door ); } if( ( sides == "both" ) @@ -5995,6 +6019,7 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co door.unlock.offset( location ); door.step_close.offset( location ); door.step_open.offset( location ); + door.permit_granted.offset( location ); m_doorsounds.emplace_back( door ); } m_doorspeakers.emplace_back( diff --git a/DynObj.h b/DynObj.h index 12d831b5..017d54f4 100644 --- a/DynObj.h +++ b/DynObj.h @@ -356,6 +356,7 @@ private: sound_source unlock { sound_placement::general }; sound_source step_open { sound_placement::general }; sound_source step_close { sound_placement::general }; + sound_source permit_granted { sound_placement::general }; side placement {}; }; diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 3dc86817..35cebad9 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -252,6 +252,7 @@ enum sound { attachheating = 1 << 13, attachadapter = 1 << 14, removeadapter = 1 << 15, + doorpermit = 1 << 16, }; // customizable reset button @@ -882,6 +883,7 @@ private: // internal data float auto_timer { -1.f }; // delay between activation of open state and closing state for automatic doors float close_delay { 0.f }; // delay between activation of closing state and actual closing + float open_delay { 0.f }; // delay between activation of opening state and actual opening float position { 0.f }; // current shift of the door from the closed position float step_position { 0.f }; // current shift of the movable step from the retracted position // ld outputs @@ -897,6 +899,7 @@ private: // config control_t open_control { control_t::passenger }; float open_rate { 1.f }; + float open_delay { 0.f }; control_t close_control { control_t::passenger }; float close_rate { 1.f }; float close_delay { 0.f }; @@ -1432,6 +1435,7 @@ public: heat_data dizel_heat; std::array MotorBlowers; door_data Doors; + float DoorsOpenWithPermitAfter { -1.f }; // remote open if permit button is held for specified time. NOTE: separate from door data as its cab control thing int BrakeCtrlPos = -2; /*nastawa hamulca zespolonego*/ double BrakeCtrlPosR = 0.0; /*nastawa hamulca zespolonego - plynna dla FV4a*/ @@ -1868,6 +1872,7 @@ public: bool AssignLoad( std::string const &Name, float const Amount = 0.f ); bool LoadingDone(double LSpeed, std::string const &Loadname); bool PermitDoors( side const Door, bool const State = true, range_t const Notify = range_t::consist ); + void PermitDoors_( side const Door, bool const State = true ); bool ChangeDoorPermitPreset( int const Change, range_t const Notify = range_t::consist ); bool PermitDoorStep( bool const State, range_t const Notify = range_t::consist ); bool ChangeDoorControlMode( bool const State, range_t const Notify = range_t::consist ); diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index d41b8dd2..e7bdb15d 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -8236,7 +8236,7 @@ bool TMoverParameters::PermitDoors( side const Door, bool const State, range_t c bool const initialstate { Doors.instances[Door].open_permit }; - Doors.instances[ Door ].open_permit = State; + PermitDoors_( Door, State ); if( Notify != range_t::local ) { @@ -8255,6 +8255,14 @@ bool TMoverParameters::PermitDoors( side const Door, bool const State, range_t c return ( Doors.instances[ Door ].open_permit != initialstate ); } +void TMoverParameters::PermitDoors_( side const Door, bool const State ) { + + if( ( State ) && ( State != Doors.instances[ Door ].open_permit ) ) { + SetFlag( SoundFlag, sound::doorpermit ); + } + Doors.instances[ Door ].open_permit = State; +} + bool TMoverParameters::ChangeDoorControlMode( bool const State, range_t const Notify ) { auto const initialstate { Doors.remote_only }; @@ -8483,21 +8491,15 @@ TMoverParameters::update_doors( double const Deltatime ) { // doors if( true == door.is_opening ) { // open door - if( ( TrainType == dt_EZT ) - || ( TrainType == dt_DMU ) ) { - // multi-unit vehicles typically open door only after unfolding the doorstep - if( ( false == door.step_unfolding ) // no wait if no doorstep - || ( Doors.step_type == 2 ) ) { // no wait for rotating doorstep + if( ( false == door.step_unfolding ) // no wait if no doorstep + || ( Doors.step_type == 2 ) ) { // no wait for rotating doorstep + door.open_delay += Deltatime; + if( door.open_delay > Doors.open_delay ) { door.position = std::min( Doors.range, door.position + Doors.open_rate * Deltatime ); } } - else { - door.position = std::min( - Doors.range, - door.position + Doors.open_rate * Deltatime ); - } door.close_delay = 0.f; } if( true == door.is_closing ) { @@ -8508,6 +8510,7 @@ TMoverParameters::update_doors( double const Deltatime ) { 0.f, door.position - Doors.close_rate * Deltatime ); } + door.open_delay = 0.f; } // doorsteps if( door.step_unfolding ) { @@ -9973,6 +9976,7 @@ void TMoverParameters::LoadFIZ_Doors( std::string const &line ) { } extract_value( Doors.open_rate, "OpenSpeed", line, "" ); + extract_value( Doors.open_delay, "DoorOpenDelay", line, "" ); extract_value( Doors.close_rate, "CloseSpeed", line, "" ); extract_value( Doors.close_delay, "DoorCloseDelay", line, "" ); extract_value( Doors.range, "DoorMaxShiftL", line, "" ); @@ -10016,6 +10020,7 @@ void TMoverParameters::LoadFIZ_Doors( std::string const &line ) { extract_value( MirrorMaxShift, "MirrorMaxShift", line, "" ); extract_value( MirrorVelClose, "MirrorVelClose", line, ""); + extract_value( DoorsOpenWithPermitAfter, "DoorOpenWithPermit", line, "" ); } void TMoverParameters::LoadFIZ_BuffCoupl( std::string const &line, int const Index ) { @@ -11841,10 +11846,10 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C auto const right { 3 - left }; if( std::abs( static_cast( CValue1 ) ) & right ) { - Doors.instances[ side::right ].open_permit = (CValue1 > 0); + PermitDoors_( side::right, ( CValue1 > 0 ) ); } if( std::abs( static_cast( CValue1 ) ) & left ) { - Doors.instances[ side::left ].open_permit = (CValue1 > 0); + PermitDoors_( side::left, ( CValue1 > 0 ) ); } OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); diff --git a/Train.cpp b/Train.cpp index bfade70a..f3f1b2fe 100644 --- a/Train.cpp +++ b/Train.cpp @@ -1100,7 +1100,7 @@ void TTrain::OnCommand_secondcontrollerincrease( TTrain *Train, command_data con } } else { - if( Train->ggScndCtrl.type() == TGaugeType::push ) { + if( Train->ggScndCtrl.is_push() ) { // two-state control, active while the button is down if( Command.action == GLFW_PRESS ) { // activate on press @@ -1118,6 +1118,13 @@ void TTrain::OnCommand_secondcontrollerincrease( TTrain *Train, command_data con Train->mvControlled->IncScndCtrl( 1 ); } } + // potentially animate tempomat button + if( ( Train->ggScndCtrlButton.is_push() ) + && ( Train->mvControlled->ScndCtrlPos <= 1 ) ) { + Train->ggScndCtrlButton.UpdateValue( + ( ( Command.action == GLFW_RELEASE ) ? 0.f : 1.f ), + Train->dsbSwitch ); + } } } @@ -1252,6 +1259,20 @@ void TTrain::OnCommand_secondcontrollerdecrease( TTrain *Train, command_data con Train->mvControlled->DecScndCtrl( 1 ); } } + // potentially animate tempomat button + if( ( Train->ggScndCtrlButton.is_push() ) + && ( Train->mvControlled->ScndCtrlPos <= 1 ) ) { + if( Train->m_controlmapper.contains( "tempomatoff_sw:" ) ) { + Train->ggScndCtrlOffButton.UpdateValue( + ( ( Command.action == GLFW_RELEASE ) ? 0.f : 1.f ), + Train->dsbSwitch ); + } + else { + Train->ggScndCtrlButton.UpdateValue( + ( ( Command.action == GLFW_RELEASE ) ? 0.f : 1.f ), + Train->dsbSwitch ); + } + } } void TTrain::OnCommand_secondcontrollerdecreasefast( TTrain *Train, command_data const &Command ) { @@ -5168,18 +5189,20 @@ void TTrain::OnCommand_doorpermitleft( TTrain *Train, command_data const &Comman if( Command.action == GLFW_REPEAT ) { return; } if( false == Train->mvOccupied->Doors.permit_presets.empty() ) { return; } + auto const side { ( + Train->cab_to_end() == end::front ? + side::left : + side::right ) }; + if( Command.action == GLFW_PRESS ) { - auto const side { ( - Train->cab_to_end() == end::front ? - side::left : - side::right ) }; - - if( Train->ggDoorLeftPermitButton.type() == TGaugeType::push ) { + if( Train->ggDoorLeftPermitButton.is_push() ) { // impulse switch Train->mvOccupied->PermitDoors( side ); // visual feedback Train->ggDoorLeftPermitButton.UpdateValue( 1.0, Train->dsbSwitch ); + // start potential timer for remote door control + Train->m_doorpermittimers[ side ] = Train->mvOccupied->DoorsOpenWithPermitAfter; } else { // two-state switch @@ -5192,10 +5215,12 @@ void TTrain::OnCommand_doorpermitleft( TTrain *Train, command_data const &Comman } else if( Command.action == GLFW_RELEASE ) { - if( Train->ggDoorLeftPermitButton.type() == TGaugeType::push ) { + if( Train->ggDoorLeftPermitButton.is_push() ) { // impulse switch // visual feedback Train->ggDoorLeftPermitButton.UpdateValue( 0.0, Train->dsbSwitch ); + // reset potential remote door control timer + Train->m_doorpermittimers[ side ] = -1.f; } } } @@ -5205,18 +5230,20 @@ void TTrain::OnCommand_doorpermitright( TTrain *Train, command_data const &Comma if( Command.action == GLFW_REPEAT ) { return; } if( false == Train->mvOccupied->Doors.permit_presets.empty() ) { return; } - if( Command.action == GLFW_PRESS ) { + auto const side { ( + Train->cab_to_end() == end::front ? + side::right : + side::left ) }; - auto const side { ( - Train->cab_to_end() == end::front ? - side::right : - side::left ) }; + if( Command.action == GLFW_PRESS ) { if( Train->ggDoorRightPermitButton.type() == TGaugeType::push ) { // impulse switch Train->mvOccupied->PermitDoors( side ); // visual feedback Train->ggDoorRightPermitButton.UpdateValue( 1.0, Train->dsbSwitch ); + // start potential timer for remote door control + Train->m_doorpermittimers[ side ] = Train->mvOccupied->DoorsOpenWithPermitAfter; } else { // two-state switch @@ -5233,6 +5260,8 @@ void TTrain::OnCommand_doorpermitright( TTrain *Train, command_data const &Comma // impulse switch // visual feedback Train->ggDoorRightPermitButton.UpdateValue( 0.0, Train->dsbSwitch ); + // reset potential remote door control timer + Train->m_doorpermittimers[ side ] = -1.f; } } } @@ -6118,7 +6147,7 @@ void TTrain::UpdateCab() { bool TTrain::Update( double const Deltatime ) { - // train state verification + // train state update // line breaker: if( m_linebreakerstate == 0 ) { if( true == mvControlled->Mains ) { @@ -6169,13 +6198,29 @@ bool TTrain::Update( double const Deltatime ) 0 ); } } + // door permits + for( auto idx = 0; idx < 2; ++idx ) { + auto &doorpermittimer { m_doorpermittimers[ idx ] }; + if( doorpermittimer < 0.f ) { + continue; + } + doorpermittimer -= Deltatime; + if( doorpermittimer < 0.f ) { + mvOccupied->OperateDoors( static_cast( idx ), true ); + } + } // helper variables if( DynamicObject->Mechanik != nullptr ) { - m_doors = ( DynamicObject->Mechanik->IsAnyDoorOpen[ side::right ] || DynamicObject->Mechanik->IsAnyDoorOpen[ side::left ] ); + m_doors = ( + DynamicObject->Mechanik->IsAnyDoorOpen[ side::right ] + || DynamicObject->Mechanik->IsAnyDoorOpen[ side::left ] ); + m_doorpermits = ( + DynamicObject->Mechanik->IsAnyDoorPermitActive[ side::right ] + || DynamicObject->Mechanik->IsAnyDoorPermitActive[ side::left ] ); } m_dirforward = ( mvControlled->DirActive > 0 ); m_dirneutral = ( mvControlled->DirActive == 0 ); - m_dirbackward = ( mvControlled->DirActive <0 ); + m_dirbackward = ( mvControlled->DirActive < 0 ); // check for received user commands // NOTE: this is a temporary arrangement, for the transition period from old command setup to the new one @@ -9213,6 +9258,7 @@ bool TTrain::initialize_button(cParser &Parser, std::string const &Label, int co { "i-doors:", &m_doors }, { "i-doorpermit_left:", &mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::left : side::right ) ].open_permit }, { "i-doorpermit_right:", &mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::right : side::left ) ].open_permit }, + { "i-doorpermit_any:", &m_doorpermits }, { "i-doorstep:", &mvOccupied->Doors.step_enabled }, { "i-mainpipelock:", &mvOccupied->LockPipe }, { "i-battery:", &mvOccupied->Power24vIsAvailable }, diff --git a/Train.h b/Train.h index 35cc593c..2202fb1f 100644 --- a/Train.h +++ b/Train.h @@ -818,6 +818,8 @@ private: bool m_dirforward{ false }; // helper, true if direction set to forward bool m_dirneutral{ false }; // helper, true if direction set to neutral bool m_dirbackward{ false }; // helper, true if direction set to backward + bool m_doorpermits{ false }; // helper, true if any door permit is active + float m_doorpermittimers[2] = { -1.f, -1.f }; // ld substitute bool m_couplingdisconnect { false }; diff --git a/version.h b/version.h index d72439c9..bfc43be6 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 331 +#define VERSION_MINOR 408 #define VERSION_REVISION 0 From 3775d20e0c4ba8f8d2ff148dcd9d9fa21905e418 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sat, 24 Apr 2021 19:37:16 +0200 Subject: [PATCH 32/63] build 210424. soundproofing enhancement, battery sound, heating command propagation, memory cell command retrieval logic enhancement, coupling mode logic enhancement, motor overload relay threshold cab control animation fix, cab control fallback sound activation fixes, --- Driver.cpp | 117 +++++++++++++++++++++++++++++---------------- Driver.h | 3 +- DynObj.cpp | 16 ++++++- DynObj.h | 1 + Event.cpp | 13 ++--- Gauge.cpp | 59 +++++++++++++++++++---- Gauge.h | 8 +++- McZapkie/MOVER.h | 6 ++- McZapkie/Mover.cpp | 44 +++++++++++++++-- Train.cpp | 48 +++++++++++-------- driverhints.cpp | 8 ++-- driveruipanels.cpp | 2 +- sound.cpp | 39 ++++++++++----- sound.h | 10 ++++ translation.cpp | 10 ++-- uilayer.cpp | 2 +- version.h | 2 +- 17 files changed, 276 insertions(+), 112 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 9108fe4d..cc82fa2b 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -1676,14 +1676,19 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo else if( Point.evEvent->is_command() ) { // jeśli prędkość jest zerowa, a komórka zawiera komendę eSignNext = Point.evEvent; // dla informacji - if( true == TestFlag( iDrivigFlags, moveStopHere ) ) { - // jeśli ma stać, dostaje komendę od razu - Command = TCommandType::cm_Command; // komenda z komórki, do wykonania po zatrzymaniu - } - else if( Point.fDist <= fMaxProximityDist ) { - // jeśli ma dociągnąć, to niech dociąga - // (moveStopCloser dotyczy dociągania do W4, nie semafora) - Command = TCommandType::cm_Command; // komenda z komórki, do wykonania po zatrzymaniu + // make sure the command isn't for someone else + // TBD, TODO: revise this check for bank/loose_shunt; we might want to ignore obstacles with no owner in these modes + if( Point.fDist < Obstacle.distance ) { + if( true == TestFlag( iDrivigFlags, moveStopHere ) ) { + // jeśli ma stać, dostaje komendę od razu + Command = TCommandType::cm_Command; // komenda z komórki, do wykonania po zatrzymaniu + } + // NOTE: human drivers are likely to stop wherever and expect things to still work, so we give them more leeway + else if( Point.fDist <= ( AIControllFlag ? fMaxProximityDist : std::max( 250.0, fMaxProximityDist ) ) ) { + // jeśli ma dociągnąć, to niech dociąga + // (moveStopCloser dotyczy dociągania do W4, nie semafora) + Command = TCommandType::cm_Command; // komenda z komórki, do wykonania po zatrzymaniu + } } } } @@ -1928,9 +1933,13 @@ TController::~TController() // zamiana kodu rozkazu na opis std::string TController::Order2Str(TOrders Order) const { - if( Order & Change_direction ) { + if( ( ( Order & Change_direction ) != 0 ) + && ( Order != Change_direction ) ) { // może być nałożona na inną i wtedy ma priorytet - return "Change_direction"; + return + Order2Str( Change_direction ) + + " + " + + Order2Str( static_cast( Order & ~Change_direction ) ); } switch( Order ) { case Wait_for_orders: return "Wait_for_orders"; @@ -1953,11 +1962,15 @@ std::array orderbuffer; std::string TController::OrderCurrent() const { // pobranie aktualnego rozkazu celem wyświetlenia auto const order { OrderCurrentGet() }; + // generally prioritize direction change... if( order & Change_direction ) { - return locale::strings[ locale::string::driver_scenario_changedirection ]; + // ...but not in the middle of coupling operation + if( false == TestFlag( iDrivigFlags, moveConnect ) ) { + return locale::strings[ locale::string::driver_scenario_changedirection ]; + } } - switch( OrderList[ OrderPos ] ) { + switch( static_cast( order & ~Change_direction ) ) { case Wait_for_orders: { return locale::strings[ locale::string::driver_scenario_waitfororders ]; } case Prepare_engine: { return locale::strings[ locale::string::driver_scenario_prepareengine ]; } case Release_engine: { return locale::strings[ locale::string::driver_scenario_releaseengine ]; } @@ -4927,17 +4940,20 @@ TController::PrepareDirection() { locale::string::driver_hint_directionbackward ); } -void TController::JumpToNextOrder() +void TController::JumpToNextOrder( bool const Ignoremergedchangedirection ) { // wykonanie kolejnej komendy z tablicy rozkazów if (OrderList[OrderPos] != Wait_for_orders) { - if (OrderList[OrderPos] & Change_direction) // jeśli zmiana kierunku - if (OrderList[OrderPos] != Change_direction) // ale nałożona na coś - { - OrderList[OrderPos] = TOrders(OrderList[OrderPos] & ~Change_direction); // usunięcie zmiany kierunku z innej komendy + if( ( ( OrderList[ OrderPos ] & Change_direction ) != 0 ) // jeśli zmiana kierunku + && ( OrderList[ OrderPos ] != Change_direction ) ) { // ale nałożona na coś + + if( false == Ignoremergedchangedirection ) { + // usunięcie zmiany kierunku z innej komendy + OrderList[ OrderPos ] = TOrders( OrderList[ OrderPos ] & ~Change_direction ); OrderCheck(); return; } + } if (OrderPos < maxorders - 1) ++OrderPos; else @@ -6701,9 +6717,9 @@ TController::UpdateConnect() { // podłączanie do składu if (iDrivigFlags & moveConnect) { // sprzęgi sprawdzamy w pierwszej kolejności, bo jak połączony, to koniec - auto *vehicle { pVehicles[ end::front ] }; + auto *vehicle { iCouplingVehicle.value().first }; + auto const couplingend { iCouplingVehicle.value().second }; auto *vehicleparameters { vehicle->MoverParameters }; - auto const couplingend { ( vehicle->DirectionGet() > 0 ? end::front : end::rear ) }; if( vehicleparameters->Couplers[ couplingend ].CouplingFlag != iCoupler ) { auto const &neighbour { vehicleparameters->Neighbours[ couplingend ] }; if( neighbour.vehicle != nullptr ) { @@ -6736,14 +6752,24 @@ TController::UpdateConnect() { CheckVehicles( Connect ); // sprawdzić światła nowego składu iCoupler = 0; // dalsza jazda manewrowa już bez łączenia + iCouplingVehicle.reset(); iDrivigFlags &= ~moveConnect; // zdjęcie flagi doczepiania - JumpToNextOrder(); // wykonanie następnej komendy + JumpToNextOrder( true ); // wykonanie następnej komendy } } // moveConnect else { + // początek podczepiania, z wyłączeniem sprawdzania fTrackBlock if( Obstacle.distance <= 20.0 ) { - // początek podczepiania, z wyłączeniem sprawdzania fTrackBlock - iDrivigFlags |= moveConnect; + if( !iCouplingVehicle ) { + // write down which vehicle should be coupled with the target consist, + // so we don't lose track of it if the user does something unexpected + iCouplingVehicle = { + pVehicles[ end::front ], + ( pVehicles[ end::front ]->DirectionGet() > 0 ? + end::front : + end::rear ) }; + iDrivigFlags |= moveConnect; + } } } } @@ -6885,9 +6911,7 @@ TController::UpdateDisconnect() { // 5th stage: clean up and move on to next order if( iDirection == iDirectionOrder ) { iDrivigFlags &= ~movePress; // koniec dociskania - while( ( OrderCurrentGet() & Disconnect ) != 0 ) { - JumpToNextOrder(); // zmieni światła - } + JumpToNextOrder( true ); // zmieni światła TableClear(); // skanowanie od nowa iDrivigFlags &= ~moveStartHorn; // bez trąbienia przed ruszeniem } @@ -6957,6 +6981,11 @@ TController::handle_orders() { break; } default: { + // special case, change_direction potentially received during partially completed coupling operation + // this is unlikely to happen for the AI, but can happen for a (slower) human user + if( TestFlag( iDrivigFlags, moveConnect ) ) { + UpdateConnect(); + } break; } } // switch OrderList[OrderPos] @@ -7949,27 +7978,31 @@ TController::check_route_ahead( double const Range ) { void TController::check_route_behind( double const Range ) { - if( VelNext != 0.0 ) { return; } + if( VelNext != 0.0 ) { return; } // not if we can move ahead + if( TestFlag( iDrivigFlags, moveConnect ) ) { return; } // not if we're in middle of coupling operation - if( !( OrderCurrentGet() & ~( Shunt | Loose_shunt | Connect ) ) ) { + if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Connect ) ) == 0 ) { // jedzie w Shunt albo Connect, albo Wait_for_orders // w trybie Connect skanować do tyłu tylko jeśli przed kolejnym sygnałem nie ma taboru do podłączenia - if( ( ( OrderCurrentGet() & Connect ) == 0 ) - || ( Obstacle.distance > std::min( 2000.0, FirstSemaphorDist ) ) ) { - auto const comm { BackwardScan( Range ) }; - if( comm != TCommandType::cm_Unknown ) { - // jeśli w drugą można jechać - // należy sprawdzać odległość od znalezionego sygnalizatora, - // aby w przypadku prędkości 0.1 wyciągnąć najpierw skład za sygnalizator - // i dopiero wtedy zmienić kierunek jazdy, oczekując podania prędkości >0.5 - if( comm == TCommandType::cm_Command ) { - // jeśli komenda Shunt to ją odbierz bez przemieszczania się (np. odczep wagony po dopchnięciu do końca toru) - iDrivigFlags |= moveStopHere; - } - iDirectionOrder = -iDirection; // zmiana kierunku jazdy - // zmiana kierunku bez psucia kolejnych komend - OrderList[ OrderPos ] = TOrders( OrderCurrentGet() | Change_direction ); + auto const couplinginprogress { + ( TestFlag( OrderCurrentGet(), Connect ) ) + && ( ( Obstacle.distance < std::min( 2000.0, FirstSemaphorDist ) ) + || ( TestFlag( iDrivigFlags, moveConnect ) ) ) }; + if( couplinginprogress ) { return; } + + auto const comm { BackwardScan( Range ) }; + if( comm != TCommandType::cm_Unknown ) { + // jeśli w drugą można jechać + // należy sprawdzać odległość od znalezionego sygnalizatora, + // aby w przypadku prędkości 0.1 wyciągnąć najpierw skład za sygnalizator + // i dopiero wtedy zmienić kierunek jazdy, oczekując podania prędkości >0.5 + if( comm == TCommandType::cm_Command ) { + // jeśli komenda Shunt to ją odbierz bez przemieszczania się (np. odczep wagony po dopchnięciu do końca toru) + iDrivigFlags |= moveStopHere; } + iDirectionOrder = -iDirection; // zmiana kierunku jazdy + // zmiana kierunku bez psucia kolejnych komend + OrderList[ OrderPos ] = TOrders( OrderCurrentGet() | Change_direction ); } } } diff --git a/Driver.h b/Driver.h index a4de7c4a..cba21eec 100644 --- a/Driver.h +++ b/Driver.h @@ -383,6 +383,7 @@ private: int iDirection = 0; // kierunek jazdy względem sprzęgów pojazdu, w którym siedzi AI (1=przód,-1=tył) int iDirectionOrder = 0; //żadany kierunek jazdy (służy do zmiany kierunku) std::optional iDirectionBackup; // consist direction to be restored after coupling/uncoupling and similar direction-changing operations + std::optional> iCouplingVehicle; // to be coupled with another consist in current coupling operation int iVehicleCount = -2; // wartość neutralna // ilość pojazdów do odłączenia albo zabrania ze składu (-1=wszystkie) int iCoupler = 0; // maska sprzęgu, jaką należy użyć przy łączeniu (po osiągnięciu trybu Connect), 0 gdy jazda bez łączenia int iDriverFailCount = 0; // licznik błędów AI @@ -446,7 +447,7 @@ public: std::string OrderCurrent() const; private: void RecognizeCommand(); // odczytuje komende przekazana lokomotywie - void JumpToNextOrder(); + void JumpToNextOrder( bool const Skipmergedchangedirection = false ); void JumpToFirstOrder(); void OrderPush( TOrders NewOrder ); void OrderNext( TOrders NewOrder ); diff --git a/DynObj.cpp b/DynObj.cpp index 7037412f..af41a8f1 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -4122,6 +4122,14 @@ void TDynamicObject::RenderSounds() { sHeater.stop(); } + // battery sound + if( MoverParameters->Battery ) { + m_batterysound.play( sound_flags::exclusive | sound_flags::looping ); + } + else { + m_batterysound.stop(); + } + // brake system and braking sounds: // brake cylinder piston @@ -5763,6 +5771,12 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co sHeater.owner( this ); } + else if( token == "battery:" ) { + // train heating device + m_batterysound.deserialize( parser, sound_type::single ); + m_batterysound.owner( this ); + } + else if( token == "turbo:" ) { // pliki z turbogeneratorem m_powertrainsounds.engine_turbo.deserialize( parser, sound_type::multipart, sound_parameters::range ); @@ -6484,7 +6498,7 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co // other engine compartment sounds auto const nullvector { glm::vec3() }; std::vector enginesounds = { - &sConverter, &sCompressor, &sCompressorIdle, &sSmallCompressor, &sHeater + &sConverter, &sCompressor, &sCompressorIdle, &sSmallCompressor, &sHeater, &m_batterysound }; for( auto sound : enginesounds ) { if( sound->offset() == nullvector ) { diff --git a/DynObj.h b/DynObj.h index 017d54f4..cf614561 100644 --- a/DynObj.h +++ b/DynObj.h @@ -480,6 +480,7 @@ private: sound_source sCompressorIdle { sound_placement::engine }; sound_source sSmallCompressor { sound_placement::engine }; sound_source sHeater { sound_placement::engine }; + sound_source m_batterysound { sound_placement::engine }; // braking sounds sound_source dsbPneumaticRelay { sound_placement::external }; sound_source rsBrake { sound_placement::external, EU07_SOUND_BRAKINGCUTOFFRANGE }; // moved from cab diff --git a/Event.cpp b/Event.cpp index aceb1645..f2c7cd8a 100644 --- a/Event.cpp +++ b/Event.cpp @@ -538,10 +538,10 @@ updatevalues_event::run_() { auto const location { targetcell->location() }; for( auto vehicle : targetcell->Track->Dynamics ) { if( vehicle->Mechanik ) { + WriteLog( " Vehicle: [" + vehicle->name() + "]" ); targetcell->PutCommand( vehicle->Mechanik, &location ); - WriteLog( " Vehicle: [" + vehicle->name() + "]" ); } } } @@ -775,10 +775,9 @@ putvalues_event::run_() { m_input.location.z, m_input.location.y }; - std::string vehiclename; if( m_activator->Mechanik ) { // przekazanie rozkazu do AI - vehiclename = m_activator->Mechanik->Vehicle()->name(); + WriteLog( " Vehicle: [" + m_activator->Mechanik->Vehicle()->name() + "]" ); m_activator->Mechanik->PutCommand( m_input.data_text, m_input.data_value_1, @@ -790,7 +789,7 @@ putvalues_event::run_() { // 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 - vehiclename = m_activator->ctOwner->Vehicle()->name(); + WriteLog( " Vehicle: [" + m_activator->ctOwner->Vehicle()->name() + "]" ); m_activator->ctOwner->PutCommand( m_input.data_text, m_input.data_value_1, @@ -799,15 +798,13 @@ putvalues_event::run_() { } else { // przekazanie do pojazdu - vehiclename = m_activator->name(); + WriteLog( " Vehicle: [" + m_activator->name() +"]" ); m_activator->MoverParameters->PutCommand( m_input.data_text, m_input.data_value_1, m_input.data_value_2, loc ); } - - WriteLog( " Vehicle: [" + vehiclename + "]" ); } // export_as_text() subclass details @@ -944,10 +941,10 @@ copyvalues_event::run_() { auto const location { targetcell->location() }; for( auto vehicle : targetcell->Track->Dynamics ) { if( vehicle->Mechanik ) { + WriteLog( " Vehicle: [" + vehicle->name() + "]" ); targetcell->PutCommand( vehicle->Mechanik, &location ); - WriteLog( " Vehicle: [" + vehicle->name() + "]" ); } } } diff --git a/Gauge.cpp b/Gauge.cpp index 2788a4b2..e6853a17 100644 --- a/Gauge.cpp +++ b/Gauge.cpp @@ -141,8 +141,32 @@ void TGauge::Load( cParser &Parser, TDynamicObject const *Owner, double const mu >> endscale; } // new, variable length section - while( true == Load_mapping( Parser ) ) { - ; // all work done by while() + { + scratch_data scratchpad; + while( true == Load_mapping( Parser, scratchpad ) ) { + ; // all work done by while() + } + // post-deserialization cleanup + // set provided custom soundproofing to assigned sounds (for sounds without their own custom soundproofing) + if( scratchpad.soundproofing ) { + if( !m_soundfxincrease.soundproofing() ) { + m_soundfxincrease.soundproofing() = scratchpad.soundproofing; + } + if( !m_soundfxdecrease.soundproofing() ) { + m_soundfxdecrease.soundproofing() = scratchpad.soundproofing; + } + if( !m_soundfxon.soundproofing() ) { + m_soundfxon.soundproofing() = scratchpad.soundproofing; + } + if( !m_soundfxoff.soundproofing() ) { + m_soundfxoff.soundproofing() = scratchpad.soundproofing; + } + for( auto &soundfxrecord : m_soundfxvalues ) { + if( !soundfxrecord.second.soundproofing() ) { + soundfxrecord.second.soundproofing() = scratchpad.soundproofing; + } + } + } } } @@ -207,7 +231,7 @@ void TGauge::Load( cParser &Parser, TDynamicObject const *Owner, double const mu }; bool -TGauge::Load_mapping( cParser &Input ) { +TGauge::Load_mapping( cParser &Input, TGauge::scratch_data &Scratchpad ) { // token can be a key or block end auto const key { Input.getToken( true, "\n\r\t ,;" ) }; @@ -236,8 +260,21 @@ TGauge::Load_mapping( cParser &Input ) { else if( key == "soundoff:" ) { m_soundfxoff.deserialize( Input, sound_type::single ); } - else if( key.compare( 0, std::min( key.size(), 5 ), "sound" ) == 0 ) { - // sounds assigned to specific gauge values, defined by key soundFoo: where Foo = value + else if( key == "soundproofing:" ) { + // custom soundproofing in format [ p1, p2, p3, p4, p5, p6 ] + Input.getTokens( 6, false, "\n\r\t ,;[]" ); + std::array soundproofing; + Input + >> soundproofing[ 0 ] + >> soundproofing[ 1 ] + >> soundproofing[ 2 ] + >> soundproofing[ 3 ] + >> soundproofing[ 4 ] + >> soundproofing[ 5 ]; + Scratchpad.soundproofing = soundproofing; + } + else if( starts_with( key, "sound" ) ) { + // sounds assigned to specific gauge values, defined by key soundX: where X = value auto const indexstart { key.find_first_of( "-1234567890" ) }; auto const indexend { key.find_first_not_of( "-1234567890", indexstart ) }; if( indexstart != std::string::npos ) { @@ -290,13 +327,19 @@ TGauge::UpdateValue( float fNewDesired, sound_source *Fallbacksound ) { // HACK: crude way to discern controls with continuous and quantized value range if( currentvalue < fNewDesired ) { // shift up - m_soundfxincrease.play( m_soundtype ); + if( false == m_soundfxincrease.empty() ) { + m_soundfxincrease.play( m_soundtype ); + return; + } } else if( currentvalue > fNewDesired ) { // shift down - m_soundfxdecrease.play( m_soundtype ); + if( false == m_soundfxdecrease.empty() ) { + m_soundfxdecrease.play( m_soundtype ); + return; + } } - else if( Fallbacksound != nullptr ) { + if( Fallbacksound != nullptr ) { // ...and if that fails too, try the provided fallback sound from legacy system Fallbacksound->play( m_soundtype ); } diff --git a/Gauge.h b/Gauge.h index 0371b4d9..d2e40b0b 100644 --- a/Gauge.h +++ b/Gauge.h @@ -71,10 +71,14 @@ public: *SubModelOn { nullptr }; // optional submodel visible when the state input is set private: +// types + struct scratch_data { + std::optional< std::array > soundproofing; + }; // methods -// imports member data pair from the config file + // imports member data pair from the config file bool - Load_mapping( cParser &Input ); + Load_mapping( cParser &Input, TGauge::scratch_data &Scratchpad ); void UpdateValue( float fNewDesired, sound_source *Fallbacksound ); float diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 35cebad9..f6b16f6c 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1046,7 +1046,7 @@ public: double EngineHeatingRPM { 0.0 }; // guaranteed engine revolutions with heating enabled double LightPower = 0.0; /*moc pobierana na ogrzewanie/oswietlenie*/ double BatteryVoltage = 0.0; /*Winger - baterie w elektrykach*/ - bool Battery = false; /*Czy sa zalavzone baterie*/ + bool Battery = false; /*Czy sa zalaczone baterie*/ start_t BatteryStart = start_t::manual; bool EpFuse = true; /*Czy sa zalavzone baterie*/ bool Signalling = false; /*Czy jest zalaczona sygnalizacja hamowania ostatniego wagonu*/ @@ -1805,8 +1805,10 @@ public: bool ConverterSwitch( bool State, range_t const Notify = range_t::consist );/*! wl/wyl przetwornicy*/ bool CompressorSwitch( bool State, range_t const Notify = range_t::consist );/*! wl/wyl sprezarki*/ bool ChangeCompressorPreset( int const Change, range_t const Notify = range_t::consist ); + bool HeatingSwitch( bool const State, range_t const Notify = range_t::consist ); + void HeatingSwitch_( bool const State ); - /*-funkcje typowe dla lokomotywy elektrycznej*/ + /*-funkcje typowe dla lokomotywy elektrycznej*/ void LowVoltagePowerCheck( double const Deltatime ); void MainsCheck( double const Deltatime ); void PowerCouplersCheck( double const Deltatime, coupling const Coupling ); diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index e7bdb15d..eb29e1e2 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -647,10 +647,12 @@ bool TMoverParameters::ChangeCab(int direction) // rozruch wysoki (true) albo niski (false) bool TMoverParameters::CurrentSwitch(bool const State) { - // Ra: przeniosłem z Train.cpp, nie wiem czy ma to sens - if (MaxCurrentSwitch(State)) { - if (TrainType != dt_EZT) - return (MinCurrentSwitch(State)); + + if( MaxCurrentSwitch( State ) ) { + if( TrainType != dt_EZT ) { + ( MinCurrentSwitch( State ) ); + } + return true; } // TBD, TODO: split off shunt mode toggle into a separate command? It doesn't make much sense to have these two together like that // dla 2Ls150 @@ -3641,6 +3643,33 @@ bool TMoverParameters::ChangeCompressorPreset( int const State, range_t const No return ( CompressorListPos != initialstate ); } +bool TMoverParameters::HeatingSwitch( bool const State, range_t const Notify ) { + + bool const initialstate { HeatingAllow }; + + HeatingSwitch_( State ); + + if( Notify != range_t::local ) { + // pass the command to other vehicles + // TBD: pass the requested state, or the actual state? + SendCtrlToNext( + "HeatingSwitch", + ( State ? 1 : 0 ), + CabActive, + ( Notify == range_t::unit ? + coupling::control | coupling::permanent : + coupling::control ) ); + } + + return ( HeatingAllow != initialstate ); +} + +void TMoverParameters::HeatingSwitch_( bool const State ) { + + // TBD, TODO: activation dependencies? + HeatingAllow = State; +} + // ************************************************************************************************* // Q: 20160711 // zwiększenie nastawy hamulca @@ -6550,12 +6579,14 @@ bool TMoverParameters::MaxCurrentSwitch(bool State) if (State && (Imax == ImaxLo) && (RList[MainCtrlPos].Bn < 2) && !((TrainType == dt_ET42) && (MainCtrlPos > 0))) { + Imax = ImaxHi; MCS = true; if (CabActive != 0) SendCtrlToNext("MaxCurrentSwitch", 1, CabActive); } if (!State) + if (Imax == ImaxHi) if (!((TrainType == dt_ET42) && (MainCtrlPos > 0))) { @@ -11771,6 +11802,11 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C MainSwitch_( CValue1 > 0.0 ); OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); } + else if (Command == "HeatingSwitch") + { + HeatingSwitch_( CValue1 > 0.0 ); + OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); + } else if (Command == "Direction") { DirActive = static_cast(floor(CValue1)); diff --git a/Train.cpp b/Train.cpp index f3f1b2fe..1fd2b13c 100644 --- a/Train.cpp +++ b/Train.cpp @@ -4859,7 +4859,7 @@ void TTrain::OnCommand_heatingenable( TTrain *Train, command_data const &Command if( Command.action == GLFW_PRESS ) { - Train->mvControlled->HeatingAllow = true; + Train->mvOccupied->HeatingSwitch( true ); // visual feedback Train->ggTrainHeatingButton.UpdateValue( 1.0, Train->dsbSwitch ); } @@ -4869,7 +4869,7 @@ void TTrain::OnCommand_heatingdisable( TTrain *Train, command_data const &Comman if( Command.action == GLFW_PRESS ) { - Train->mvControlled->HeatingAllow = false; + Train->mvOccupied->HeatingSwitch( false ); // visual feedback Train->ggTrainHeatingButton.UpdateValue( ( Train->ggTrainHeatingButton.type() == TGaugeType::push ? @@ -7101,7 +7101,7 @@ bool TTrain::Update( double const Deltatime ) } ggMainCtrl.Update(); } - if (ggMainCtrlAct.SubModel) + if (ggMainCtrlAct.SubModel != nullptr ) { if (mvControlled->CoupledCtrl) ggMainCtrlAct.UpdateValue( @@ -7110,7 +7110,7 @@ bool TTrain::Update( double const Deltatime ) ggMainCtrlAct.UpdateValue(double(mvControlled->MainCtrlActualPos)); ggMainCtrlAct.Update(); } - if (ggScndCtrl.SubModel) { + if (ggScndCtrl.SubModel != nullptr ) { // Ra: od byte odejmowane boolean i konwertowane potem na double? ggScndCtrl.UpdateValue( double( mvControlled->ScndCtrlPos @@ -7118,26 +7118,34 @@ bool TTrain::Update( double const Deltatime ) dsbNastawnikBocz ); ggScndCtrl.Update(); } - if( ggScndCtrlButton.is_toggle() ) { - ggScndCtrlButton.UpdateValue( - ( ( mvControlled->ScndCtrlPos > 0 ) ? 1.f : 0.f ), - dsbSwitch ); + if( ggScndCtrlButton.SubModel != nullptr ) { + if( ggScndCtrlButton.is_toggle() ) { + ggScndCtrlButton.UpdateValue( + ( ( mvControlled->ScndCtrlPos > 0 ) ? 1.f : 0.f ), + dsbSwitch ); + } + ggScndCtrlButton.Update( lowvoltagepower ); } - ggScndCtrlButton.Update( lowvoltagepower ); - ggScndCtrlOffButton.Update( lowvoltagepower ); - ggDistanceCounterButton.Update(); - if (ggDirKey.SubModel) { - if (mvControlled->TrainType != dt_EZT) + if( ggScndCtrlOffButton.SubModel != nullptr ) { + ggScndCtrlOffButton.Update( lowvoltagepower ); + } + if( ggDistanceCounterButton.SubModel != nullptr ) { + ggDistanceCounterButton.Update(); + } + if (ggDirKey.SubModel != nullptr ) { + if( mvControlled->TrainType != dt_EZT ) { ggDirKey.UpdateValue( - double(mvControlled->DirActive), - dsbReverserKey); - else + double( mvControlled->DirActive ), + dsbReverserKey ); + } + else { ggDirKey.UpdateValue( - double(mvControlled->DirActive) + double(mvControlled->Imin == mvControlled->IminHi), - dsbReverserKey); + double( mvControlled->DirActive ) + double( mvControlled->Imin == mvControlled->IminHi ), + dsbReverserKey ); + } ggDirKey.Update(); } - if (ggBrakeCtrl.SubModel) + if (ggBrakeCtrl.SubModel != nullptr ) { #ifdef _WIN32 if (DynamicObject->Mechanik ? @@ -9437,7 +9445,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con { "universal7:", ggUniversals[ 7 ] }, { "universal8:", ggUniversals[ 8 ] }, { "universal9:", ggUniversals[ 9 ] } - }; + }; { auto const lookup { gauges.find( Label ) }; if( lookup != gauges.end() ) { diff --git a/driverhints.cpp b/driverhints.cpp index d673ba88..90c5f92f 100644 --- a/driverhints.cpp +++ b/driverhints.cpp @@ -1147,24 +1147,24 @@ TController::cue_action( locale::string const Action, float const Actionparamete // consist heating case locale::string::driver_hint_consistheatingon: { if( AIControllFlag ) { - mvControlling->HeatingAllow = true; + mvOccupied->HeatingSwitch( true ); } remove_hint( locale::string::driver_hint_consistheatingoff ); hint( Action, [this](float const Parameter) -> bool { - return ( mvControlling->HeatingAllow == true ); } ); + return ( mvOccupied->HeatingAllow == true ); } ); break; } case locale::string::driver_hint_consistheatingoff: { if( AIControllFlag ) { - mvControlling->HeatingAllow = false; + mvOccupied->HeatingSwitch( false ); } remove_hint( locale::string::driver_hint_consistheatingon ); hint( Action, [this](float const Parameter) -> bool { - return ( mvControlling->HeatingAllow == false ); } ); + return ( mvOccupied->HeatingAllow == false ); } ); break; } diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 64b02135..0ee313a3 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -1088,7 +1088,7 @@ debug_panel::update_section_ai( std::vector &Output ) { // biezaca komenda dla AI auto textline = "Current order: [" + std::to_string( mechanik.OrderPos ) + "] " - + mechanik.OrderCurrent(); + + mechanik.Order2Str( mechanik.OrderCurrentGet() ); if( mechanik.fStopTime < 0.0 ) { textline += "\n stop time: " + to_string( std::abs( mechanik.fStopTime ), 1 ); diff --git a/sound.cpp b/sound.cpp index 79acf8e5..62123844 100644 --- a/sound.cpp +++ b/sound.cpp @@ -171,10 +171,23 @@ sound_source::deserialize_mapping( cParser &Input ) { else if( key == "soundend:" ) { sound( sound_id::end ).buffer = audio::renderer.fetch_buffer( deserialize_random_set( Input, "\n\r\t ,;" ) ); } - else if( key.compare( 0, std::min( key.size(), 5 ), "sound" ) == 0 ) { + else if( key == "soundproofing:" ) { + // custom soundproofing in format [ p1, p2, p3, p4, p5, p6 ] + Input.getTokens( 6, false, "\n\r\t ,;[]" ); + std::array soundproofing; + Input + >> soundproofing[ 0 ] + >> soundproofing[ 1 ] + >> soundproofing[ 2 ] + >> soundproofing[ 3 ] + >> soundproofing[ 4 ] + >> soundproofing[ 5 ]; + m_soundproofing = soundproofing; + } + else if( starts_with( key, "sound" ) ) { // sound chunks, defined with key soundX where X = activation threshold - auto const indexstart { key.find_first_of( "1234567890" ) }; - auto const indexend { key.find_first_not_of( "1234567890", indexstart ) }; + auto const indexstart { key.find_first_of( "-1234567890" ) }; + auto const indexend { key.find_first_not_of( "-1234567890", indexstart ) }; if( indexstart != std::string::npos ) { // NOTE: we'll sort the chunks at the end of deserialization m_soundchunks.emplace_back( @@ -202,10 +215,10 @@ sound_source::deserialize_mapping( cParser &Input ) { Input.getToken( false, "\n\r\t ,;" ), 0.0f, 1.0f ); } - else if( key.compare( 0, std::min( key.size(), 5 ), "pitch" ) == 0 ) { + else if( starts_with( key, "pitch" ) ) { // sound chunk pitch, defined with key pitchX where X = activation threshold - auto const indexstart { key.find_first_of( "1234567890" ) }; - auto const indexend { key.find_first_not_of( "1234567890", indexstart ) }; + auto const indexstart { key.find_first_of( "-1234567890" ) }; + auto const indexend { key.find_first_not_of( "-1234567890", indexstart ) }; if( indexstart != std::string::npos ) { auto const index { std::stoi( key.substr( indexstart, indexend - indexstart ) ) }; auto const pitch { Input.getToken( false, "\n\r\t ,;" ) }; @@ -962,12 +975,14 @@ sound_source::update_soundproofing() { auto const isambient { ( ( m_placement == sound_placement::external ) && ( m_owner == nullptr ) && ( m_range < -1 ) ? 1 : 0 ) }; auto const placement { ( isambient ? sound_placement::external_ambient : m_placement ) }; if( m_owner != nullptr ) { - m_properties.soundproofing = - m_owner->soundproofing( - static_cast( placement ), - ( m_owner != listenervehicle ? - 4 : // part of two-stage calculation owner->outside->listener, or single stage owner->outside one - occupiedcab ) ); + auto const listenerlocation { ( + m_owner != listenervehicle ? + 4 : // part of two-stage calculation owner->outside->listener, or single stage owner->outside one + occupiedcab ) }; + m_properties.soundproofing = ( + m_soundproofing ? // custom soundproofing has higher priority than that of the owner + m_soundproofing.value()[ listenerlocation + 1 ] : // cab indices start from -1 so we have to account for this + m_owner->soundproofing( static_cast( placement ), listenerlocation ) ); } if( ( listenervehicle ) && ( listenervehicle != m_owner ) ) { // if the listener is located in another vehicle, calculate additional proofing of the sound coming from outside diff --git a/sound.h b/sound.h index 9a1f01fa..944cd600 100644 --- a/sound.h +++ b/sound.h @@ -116,6 +116,11 @@ public: start( float const Offset ); float const & start() const; + // custom soundproofing setter/getter + auto & + soundproofing(); + auto const & + soundproofing() const; // returns true if there isn't any sound buffer associated with the object, false otherwise bool empty() const; @@ -231,6 +236,7 @@ private: std::vector m_soundchunks; // table of samples activated when associated variable is within certain range bool m_soundchunksempty { true }; // helper, cached check whether sample table is linked with any actual samples int m_crossfaderange {}; // range of transition from one chunk to another + std::optional< std::array > m_soundproofing; // custom soundproofing parameters }; // owner setter/getter @@ -245,6 +251,10 @@ inline std::string const & sound_source::name() const { return m_name; } // playback starting point shift setter/getter inline void sound_source::start( float const Offset ) { m_startoffset = Offset; } inline float const & sound_source::start() const { return m_startoffset; } +// custom soundproofing setter/getter +inline auto & sound_source::soundproofing() { return m_soundproofing; } +inline auto const & sound_source::soundproofing() const { return m_soundproofing; } + // collection of sound sources present in the scene class sound_table : public basic_table { diff --git a/translation.cpp b/translation.cpp index 1de6db36..0bb68458 100644 --- a/translation.cpp +++ b/translation.cpp @@ -393,15 +393,15 @@ init() { "Wylaczyc przetwornice", "Odblokowac przekaznik nadmiarowy przetwornicy", "Odblokowac przekaznik roznicowy obwodu glownego", - "Odblokowac przekaznik nadmiarowy motorow trakcyjnych", + "Odblokowac przekaznik nadmiarowy silnikow trakcyjnych", "Zamknac wylacznik szybki", "Otworzyc wylacznik szybki", "Zalaczyc sprezarke", "Wylaczyc sprezarke", - "Zalaczyc wentylatory przednich motorow trakcyjnych", - "Wylaczyc wentylatory przednich motorow trakcyjnych", - "Zalaczyc wentylatory tylnych motorow trakcyjnych", - "Wylaczyc wentylatory tylnych motorow trakcyjnych", + "Zalaczyc wentylatory przednich silnikow trakcyjnych", + "Wylaczyc wentylatory przednich silnikow trakcyjnych", + "Zalaczyc wentylatory tylnych silnikow trakcyjnych", + "Wylaczyc wentylatory tylnych silnikow trakcyjnych", "Zalaczyc hamulec sprezynowy", "Zwolnic hamules sprezynowy", "Zalaczyc hamulec reczny", diff --git a/uilayer.cpp b/uilayer.cpp index 5c4c0ab0..5f15cc4b 100644 --- a/uilayer.cpp +++ b/uilayer.cpp @@ -77,7 +77,7 @@ ui_layer::init( GLFWwindow *Window ) { static ImWchar const glyphranges[] = { 0x0020, 0x00FF, // ascii + extension - 0x0100, 0x017F, // latin extended-a +// 0x0100, 0x017F, // latin extended-a 0x2070, 0x2079, // superscript 0x2500, 0x256C, // box drawings 0, diff --git a/version.h b/version.h index bfc43be6..c7002bc9 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 408 +#define VERSION_MINOR 424 #define VERSION_REVISION 0 From 87aa760c2028df500800a923679d8b2580dcc481 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Tue, 27 Apr 2021 16:18:12 +0200 Subject: [PATCH 33/63] build 210426. announcements soundproofing, releaser control logic tweak, minor bug fixes --- Driver.cpp | 15 +++++++------ DynObj.cpp | 60 ++++++++++++++++++++++++++++++++++++++----------- DynObj.h | 1 + driverhints.cpp | 2 +- version.h | 2 +- 5 files changed, 58 insertions(+), 22 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index cc82fa2b..be287014 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -7622,7 +7622,7 @@ void TController::control_tractive_force() { auto const velocity { DirectionalVel() }; // jeśli przyspieszenie pojazdu jest mniejsze niż żądane oraz... if( ( AccDesired > EU07_AI_NOACCELERATION ) // don't add power if not asked for actual speed-up - && ( AccDesired - AbsAccS > 0.05 ) + && ( AbsAccS < AccDesired /* - 0.05 */ ) && ( false == TestFlag( iDrivigFlags, movePress ) ) ) { // ...jeśli prędkość w kierunku czoła jest mniejsza od dozwolonej o margines... if( velocity < ( @@ -7652,7 +7652,7 @@ void TController::control_tractive_force() { else if( ( velocity > VelDesired + SpeedCtrlMargin) || ( fAccGravity < -0.01 ? AccDesired < 0.0 : - (AbsAccS > AccDesired + 0.05) ) + ( AbsAccS > AccDesired + 0.05 ) ) || ( IsAnyCouplerStretched ) ) { // jak za bardzo przyspiesza albo prędkość przekroczona // dodany wyjatek na "pelna w przod" @@ -7817,11 +7817,12 @@ void TController::control_releaser() { if( mvOccupied->BrakeSystem != TBrakeSystem::Pneumatic ) { return; } auto const hasreleaser { - ( mvOccupied->BrakeHandle == TBrakeHandle::FV4a ) - || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_6P ) - || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K5P ) - || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P ) - || ( mvOccupied->BrakeHandle == TBrakeHandle::M394 ) }; + ( false == ( is_dmu() || is_emu() ) ) // TODO: a more refined test than rejecting these types wholesale + && ( ( mvOccupied->BrakeHandle == TBrakeHandle::FV4a ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_6P ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K5P ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::M394 ) ) }; if( false == hasreleaser ) { return; } diff --git a/DynObj.cpp b/DynObj.cpp index af41a8f1..ca094e5c 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -4109,17 +4109,20 @@ void TDynamicObject::RenderSounds() { } // heater sound - if( ( true == MoverParameters->Heating ) - && ( std::abs( MoverParameters->enrot ) > 0.01 ) ) { - // TBD: check whether heating should depend on 'engine rotations' for electric vehicles - sHeater - .pitch( true == sHeater.is_combined() ? + { + auto const isdieselenginepowered { ( MoverParameters->EngineType == TEngineType::DieselElectric ) || ( MoverParameters->EngineType == TEngineType::DieselEngine ) }; + if( ( true == MoverParameters->Heating ) + && ( ( false == isdieselenginepowered ) + || ( std::abs( MoverParameters->enrot ) > 0.01 ) ) ) { + sHeater + .pitch( true == sHeater.is_combined() ? std::abs( MoverParameters->enrot ) * 60.f * 0.01f : 1.f ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - sHeater.stop(); + .play( sound_flags::exclusive | sound_flags::looping ); + } + else { + sHeater.stop(); + } } // battery sound @@ -4338,6 +4341,11 @@ void TDynamicObject::RenderSounds() { m_pasystem.announcement = m_pasystem.announcement_queue.front(); m_pasystem.announcement.owner( this ); m_pasystem.announcement.range( 0.5 * MoverParameters->Dim.L * -1 ); + if( m_pasystem.soundproofing ) { + if( !m_pasystem.announcement.soundproofing() ) { + m_pasystem.announcement.soundproofing() = m_pasystem.soundproofing; + } + } m_pasystem.announcement.play(); m_pasystem.announcement_queue.pop_front(); } @@ -5958,7 +5966,6 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co // announcement sounds // content provided as "key: value" pairs together enclosed in "{}" // value can be optionally set of values enclosed in "[]" in which case one value will be picked randomly - std::array( announcement_t::end )> announcementsounds; std::unordered_map const announcements = { { "near_stop:", announcement_t::approaching }, { "stop:", announcement_t::current }, @@ -5968,23 +5975,50 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co while( ( ( token = parser.getToken() ) != "" ) && ( token != "}" ) ) { if( token.back() == ':' ) { + + if( token == "soundproofing:" ) { + // custom soundproofing in format [ p1, p2, p3, p4, p5, p6 ] + parser.getTokens( 6, false, "\n\r\t ,;[]" ); + std::array soundproofing; + parser + >> soundproofing[ 0 ] + >> soundproofing[ 1 ] + >> soundproofing[ 2 ] + >> soundproofing[ 3 ] + >> soundproofing[ 4 ] + >> soundproofing[ 5 ]; + m_pasystem.soundproofing = soundproofing; + continue; + } + auto const lookup { announcements.find( token ) }; auto const announcementtype { ( lookup != announcements.end() ? lookup->second : announcement_t::idle ) }; // NOTE: we retrieve key value for all keys, not just recognized ones - auto announcementsound{ deserialize_random_set( parser ) }; - replace_slashes( announcementsound ); if( announcementtype == announcement_t::idle ) { + token = parser.getToken(); continue; } +/* + auto announcementsound { deserialize_random_set( parser ) }; + replace_slashes( announcementsound ); +*/ sound_source soundtemplate { sound_placement::engine }; // NOTE: sound range gets filled by pa system - soundtemplate.deserialize( announcementsound, sound_type::single ); + soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); m_pasystem.announcements[ static_cast( announcementtype ) ] = soundtemplate; } } + // set provided custom soundproofing to assigned sounds (for sounds without their own custom soundproofing) + if( m_pasystem.soundproofing ) { + for( auto &announcement : m_pasystem.announcements ) { + if( !announcement.soundproofing() ) { + announcement.soundproofing() = m_pasystem.soundproofing; + } + } + } } } while( ( token != "" ) diff --git a/DynObj.h b/DynObj.h index cf614561..aa60fb21 100644 --- a/DynObj.h +++ b/DynObj.h @@ -417,6 +417,7 @@ private: // single source per vehicle struct pasystem_sounds { std::array( announcement_t::end )> announcements; + std::optional< std::array > soundproofing; sound_source announcement; std::deque announcement_queue; // fifo queue }; diff --git a/driverhints.cpp b/driverhints.cpp index 90c5f92f..fb0af11d 100644 --- a/driverhints.cpp +++ b/driverhints.cpp @@ -938,7 +938,7 @@ TController::cue_action( locale::string const Action, float const Actionparamete hint( Action, [this](float const Parameter) -> bool { - return ( ( mvControlling->Sand == 0 ) || ( mvControlling->SandDose == true ) ); } ); + return ( ( mvControlling->Sand == 0 ) || ( mvControlling->SandDose == true ) || ( mvControlling->SlippingWheels == false ) ); } ); break; } case locale::string::driver_hint_sandingoff: { diff --git a/version.h b/version.h index c7002bc9..db807e11 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 424 +#define VERSION_MINOR 426 #define VERSION_REVISION 0 From 8f02ae26c4058561edb9a99d01fe8462ea46b381 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sat, 1 May 2021 23:04:25 +0200 Subject: [PATCH 34/63] backward scan ai logic tweak, heating control ai logic tweak, autogenerated power station recognition and filtering, pantograph tank pressure exposed to uart interface, editor mode camera initialization fix --- Driver.cpp | 309 +++++++++++++++++++++++---------------------- Driver.h | 2 +- Globals.cpp | 30 +++-- Traction.cpp | 1 + TractionPower.h | 1 + Train.cpp | 1 + Train.h | 1 + driveruipanels.cpp | 5 +- editormode.cpp | 20 ++- uart.cpp | 9 +- uart.h | 2 + 11 files changed, 214 insertions(+), 167 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index be287014..2ed0019c 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -2502,10 +2502,14 @@ bool TController::CheckVehicles(TOrders user) { // HACK: to account for su-45/46 shortcomings diesel-powered engines only activate heating in cold conditions // TODO: take instead into account presence of converters in attached cars, once said presence is possible to specify - auto const isheatingneeded{ ( - IsCargoTrain ? false : - has_diesel_engine() ? ( Global.AirTemperature < 10 ) : - true ) }; + auto const ispassengertrain { ( IsPassengerTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) }; + auto const isheatingcouplingactive { pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::heating ) }; + auto const isheatingneeded { + (is_emu() || is_dmu() ? true : +// false == isheatingcouplingactive ? false : + (OrderCurrentGet() & (Obey_train | Bank)) == 0 ? false : + ispassengertrain ? (has_diesel_engine() ? (Global.AirTemperature < 10) : true) : + false)}; if( mvControlling->HeatingAllow != isheatingneeded ) { cue_action( isheatingneeded ? @@ -5317,161 +5321,164 @@ TCommandType TController::BackwardScan( double const Range ) } // szukamy od pierwszej osi w wybranym kierunku double scandir = startdir * pVehicles[0]->RaDirectionGet(); - if (scandir != 0.0) { - // skanowanie toru w poszukiwaniu eventów GetValues (PutValues nie są przydatne) - // Ra: przy wstecznym skanowaniu prędkość nie ma znaczenia - double scandist = Range; // zmodyfikuje na rzeczywiście przeskanowane - basic_event *e = NULL; // event potencjalnie od semafora - // opcjonalnie może być skanowanie od "wskaźnika" z przodu, np. W5, Tm=Ms1, koniec toru wg drugiej osi w kierunku ruchu - TTrack *scantrack = BackwardTraceRoute(scandist, scandir, pVehicles[end::front], e); - auto const dir = startdir * pVehicles[end::front]->VectorFront(); // wektor w kierunku jazdy/szukania - if( !scantrack ) { - // jeśli wstecz wykryto koniec toru to raczej nic się nie da w takiej sytuacji zrobić - return TCommandType::cm_Unknown; + + if( scandir == 0.0 ) { return TCommandType::cm_Unknown; } + + // skanowanie toru w poszukiwaniu eventów GetValues (PutValues nie są przydatne) + // Ra: przy wstecznym skanowaniu prędkość nie ma znaczenia + double scandist = Range; // zmodyfikuje na rzeczywiście przeskanowane + basic_event *e = nullptr; // event potencjalnie od semafora + // opcjonalnie może być skanowanie od "wskaźnika" z przodu, np. W5, Tm=Ms1, koniec toru wg + // drugiej osi w kierunku ruchu + auto const *scantrack{BackwardTraceRoute(scandist, scandir, pVehicles[end::front], e)}; + auto const dir{startdir * + pVehicles[end::front]->VectorFront()}; // wektor w kierunku jazdy/szukania + + // jeśli wstecz wykryto koniec toru to raczej nic się nie da w takiej sytuacji zrobić + if (e == nullptr) + { + return TCommandType::cm_Unknown; + } + if (typeid(*e) != typeid(getvalues_event)) + { + return TCommandType::cm_Unknown; + } + + // jeśli jest jakiś sygnał na widoku + double scanvel; // prędkość ustawiona semaforem +#if LOGBACKSCAN + std::string edir{"Backward scan by " + pVehicle->asName + " - " + + ((scandir > 0) ? "Event2 " : "Event1 ")}; + edir += "(" + (e->name()) + ")"; +#endif + { + // najpierw sprawdzamy, czy semafor czy inny znak został przejechany + auto const sl{e->input_location()}; // położenie komórki pamięci + auto const pos{pVehicles[end::rear]->RearPosition()}; // pozycja tyłu + auto const sem{sl - pos}; // wektor do komórki pamięci od końca składu + if (dir.x * sem.x + dir.z * sem.z < 0) + { + // jeśli został minięty + // iloczyn skalarny jest ujemny, gdy sygnał stoi z tyłu +#if LOGBACKSCAN + WriteLog(edir + " - ignored as not passed yet"); +#endif + return TCommandType::cm_Unknown; // nic } - else { - // a jeśli są dalej tory - double vmechmax; // prędkość ustawiona semaforem - if( e != nullptr ) { - // jeśli jest jakiś sygnał na widoku + scanvel = e->input_value(1); // prędkość przy tym semaforze + // przeliczamy odległość od semafora - potrzebne by były współrzędne początku składu + scandist = sem.Length() - 2; // 2m luzu przy manewrach wystarczy + if (scandist < 0) + { + // ujemnych nie ma po co wysyłać + scandist = 0; + } + } + + auto move{false}; // czy AI w trybie manewerowym ma dociągnąć pod S1 + if (e->input_command() == TCommandType::cm_SetVelocity) + { + if ((scanvel == 0.0) ? (OrderCurrentGet() & (Shunt | Loose_shunt | Connect)) : + (OrderCurrentGet() & Connect)) + { // przy podczepianiu ignorować wyjazd? + move = true; // AI w trybie manewerowym ma dociągnąć pod S1 + } + else + { + if ((scandist > fMinProximityDist) && + ((mvOccupied->Vel > EU07_AI_NOMOVEMENT) && + ((OrderCurrentGet() & (Shunt | Loose_shunt)) == 0))) + { + // jeśli semafor jest daleko, a pojazd jedzie, to informujemy o zmianie prędkości + // jeśli jedzie manewrowo, musi dostać SetVelocity, żeby sie na pociągowy przełączył #if LOGBACKSCAN - std::string edir { - "Backward scan by " - + pVehicle->asName + " - " - + ( ( scandir > 0 ) ? - "Event2 " : - "Event1 " ) }; + // WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist) + + // AnsiString(scanvel)); + WriteLog(edir); #endif - // najpierw sprawdzamy, czy semafor czy inny znak został przejechany - auto pos = pVehicles[1]->RearPosition(); // pozycja tyłu - if( typeid( *e ) == typeid( getvalues_event ) ) - { // przesłać info o zbliżającym się semaforze + // SetProximityVelocity(scandist,scanvel,&sl); + return (scanvel > 0 ? TCommandType::cm_SetVelocity : TCommandType::cm_Unknown); + } + else + { + // ustawiamy prędkość tylko wtedy, gdy ma ruszyć, stanąć albo ma stać + // if ((MoverParameters->Vel==0.0)||(scanvel==0.0)) //jeśli stoi lub ma stanąć/stać + // semafor na tym torze albo lokomtywa stoi, a ma ruszyć, albo ma stanąć, albo nie + // ruszać stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam + // PutCommand("SetVelocity",scanvel,e->Params[9].asMemCell->Value2(),&sl,stopSem); #if LOGBACKSCAN - edir += "(" + ( e->name() ) + ")"; + WriteLog(edir + " - [SetVelocity] [" + to_string(scanvel, 2) + "] [" + + to_string(e->input_value(2), 2) + "]"); #endif - auto sl = e->input_location(); // położenie komórki pamięci - auto sem = sl - pos; // wektor do komórki pamięci od końca składu - if (dir.x * sem.x + dir.z * sem.z < 0) { - // jeśli został minięty - // iloczyn skalarny jest ujemny, gdy sygnał stoi z tyłu + return (scanvel > 0 ? TCommandType::cm_SetVelocity : TCommandType::cm_Unknown); + } + } + } + // reakcja AI w trybie manewrowym dodatkowo na sygnały manewrowe + if (OrderCurrentGet() ? OrderCurrentGet() & (Shunt | Loose_shunt | Connect) : + true) // w Wait_for_orders też widzi tarcze + { + if (move || e->input_command() == TCommandType::cm_ShuntVelocity) + { // jeśli powyżej było SetVelocity 0 0, to dociągamy pod S1 +/* + if ((scandist > fMinProximityDist) && + ((mvOccupied->Vel > EU07_AI_NOMOVEMENT) || (scanvel == 0.0))) + { + // jeśli tarcza jest daleko, to: + //- jesli pojazd jedzie, to informujemy o zmianie prędkości + //- jeśli stoi, to z własnej inicjatywy może podjechać pod zamkniętą tarczę + if (mvOccupied->Vel > EU07_AI_NOMOVEMENT) // tylko jeśli jedzie + { // Mechanik->PutCommand("SetProximityVelocity",scandist,scanvel,sl); #if LOGBACKSCAN - WriteLog(edir + " - ignored as not passed yet"); + // WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist)+" + // "+AnsiString(scanvel)); + WriteLog(edir); #endif - return TCommandType::cm_Unknown; // nic - } - vmechmax = e->input_value(1); // prędkość przy tym semaforze - // przeliczamy odległość od semafora - potrzebne by były współrzędne początku składu - scandist = sem.Length() - 2; // 2m luzu przy manewrach wystarczy - if( scandist < 0 ) { - // ujemnych nie ma po co wysyłać - scandist = 0; - } - bool move = false; // czy AI w trybie manewerowym ma dociągnąć pod S1 - if( e->input_command() == TCommandType::cm_SetVelocity ) { - if( ( vmechmax == 0.0 ) ? - ( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect ) ) : - ( OrderCurrentGet() & Connect ) ) { // przy podczepianiu ignorować wyjazd? - move = true; // AI w trybie manewerowym ma dociągnąć pod S1 - } - else { - if( ( scandist > fMinProximityDist ) - && ( ( mvOccupied->Vel > EU07_AI_NOMOVEMENT ) - && ( ( OrderCurrentGet() & ( Shunt | Loose_shunt ) ) == 0 ) ) ) { - // jeśli semafor jest daleko, a pojazd jedzie, to informujemy o zmianie prędkości - // jeśli jedzie manewrowo, musi dostać SetVelocity, żeby sie na pociągowy przełączył + // SetProximityVelocity(scandist,scanvel,&sl); + // jeśli jedzie na W5 albo koniec toru, to można zmienić kierunek + return (iDrivigFlags & moveTrackEnd) ? TCommandType::cm_ChangeDirection : + TCommandType::cm_Unknown; + // TBD: request direction change also if VelNext in current direction is 0, + // while signal behind requests movement? + } + } + else + { + // ustawiamy prędkość tylko wtedy, gdy ma ruszyć, albo stanąć albo ma stać pod + // tarczą stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam if + // ((MoverParameters->Vel==0.0)||(scanvel==0.0)) //jeśli jedzie lub ma stanąć/stać + { // nie dostanie komendy jeśli jedzie i ma jechać + // PutCommand("ShuntVelocity",scanvel,e->Params[9].asMemCell->Value2(),&sl,stopSem); #if LOGBACKSCAN - // WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist) + AnsiString(vmechmax)); - WriteLog(edir); + WriteLog(edir + " - [ShuntVelocity] [" + to_string(scanvel, 2) + "] [" + + to_string(e->input_value(2), 2) + "]"); #endif - // SetProximityVelocity(scandist,vmechmax,&sl); - return ( - vmechmax > 0 ? - TCommandType::cm_SetVelocity : - TCommandType::cm_Unknown ); - } - else { - // ustawiamy prędkość tylko wtedy, gdy ma ruszyć, stanąć albo ma stać - // if ((MoverParameters->Vel==0.0)||(vmechmax==0.0)) //jeśli stoi lub ma stanąć/stać - // semafor na tym torze albo lokomtywa stoi, a ma ruszyć, albo ma stanąć, albo nie ruszać - // stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam - // PutCommand("SetVelocity",vmechmax,e->Params[9].asMemCell->Value2(),&sl,stopSem); + return (scanvel > 0 ? TCommandType::cm_ShuntVelocity : + TCommandType::cm_Unknown); + } + } + // NOTE: dead code due to returns in branching above + if ((scanvel != 0.0) && (scandist < 100.0)) + { + // jeśli Tm w odległości do 100m podaje zezwolenie na jazdę, to od razu ją + // ignorujemy, aby móc szukać kolejnej eSignSkip=e; //wtedy uznajemy ignorowaną przy + // poszukiwaniu nowej #if LOGBACKSCAN - WriteLog( - edir + " - [SetVelocity] [" - + to_string( vmechmax, 2 ) + "] [" - + to_string( e->input_value( 2 ), 2 ) + "]" ); + WriteLog(edir + " - will be ignored due to Ms2"); #endif - return ( - vmechmax > 0 ? - TCommandType::cm_SetVelocity : - TCommandType::cm_Unknown ); - } - } - } - if (OrderCurrentGet() ? OrderCurrentGet() & (Shunt | Loose_shunt | Connect) : - true) // w Wait_for_orders też widzi tarcze - { // reakcja AI w trybie manewrowym dodatkowo na sygnały manewrowe - if (move ? true : e->input_command() == TCommandType::cm_ShuntVelocity) - { // jeśli powyżej było SetVelocity 0 0, to dociągamy pod S1 - if ((scandist > fMinProximityDist) && - (mvOccupied->Vel > EU07_AI_NOMOVEMENT) || (vmechmax == 0.0) ) - { // jeśli tarcza jest daleko, to: - //- jesli pojazd jedzie, to informujemy o zmianie prędkości - //- jeśli stoi, to z własnej inicjatywy może podjechać pod zamkniętą - // tarczę - if (mvOccupied->Vel > EU07_AI_NOMOVEMENT) // tylko jeśli jedzie - { // Mechanik->PutCommand("SetProximityVelocity",scandist,vmechmax,sl); -#if LOGBACKSCAN - // WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist)+" - // "+AnsiString(vmechmax)); - WriteLog(edir); -#endif - // SetProximityVelocity(scandist,vmechmax,&sl); - return (iDrivigFlags & moveTrackEnd) ? - TCommandType::cm_ChangeDirection : - TCommandType::cm_Unknown; // jeśli jedzie na W5 albo koniec toru, - // to można zmienić kierunek - } - } - else { - // ustawiamy prędkość tylko wtedy, gdy ma ruszyć, albo stanąć albo ma stać pod tarczą - // stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam - // if ((MoverParameters->Vel==0.0)||(vmechmax==0.0)) //jeśli jedzie lub ma stanąć/stać - { // nie dostanie komendy jeśli jedzie i ma jechać - // PutCommand("ShuntVelocity",vmechmax,e->Params[9].asMemCell->Value2(),&sl,stopSem); -#if LOGBACKSCAN - WriteLog( - edir + " - [ShuntVelocity] [" - + to_string( vmechmax, 2 ) + "] [" - + to_string( e->input_value( 2 ), 2 ) + "]" ); -#endif - return ( - vmechmax > 0 ? - TCommandType::cm_ShuntVelocity : - TCommandType::cm_Unknown ); - } - } - if ((vmechmax != 0.0) && (scandist < 100.0)) { - // jeśli Tm w odległości do 100m podaje zezwolenie na jazdę, to od razu ją ignorujemy, aby móc szukać kolejnej - // eSignSkip=e; //wtedy uznajemy ignorowaną przy poszukiwaniu nowej -#if LOGBACKSCAN - WriteLog(edir + " - will be ignored due to Ms2"); -#endif - return ( - vmechmax > 0 ? - TCommandType::cm_ShuntVelocity : - TCommandType::cm_Unknown ); - } - } // if (move?... - } // if (OrderCurrentGet()==Shunt) - if (e->m_passive) // jeśli skanowany - if (e->is_command()) // a podłączona komórka ma komendę - return TCommandType::cm_Command; // to też się obrócić - } // if (e->Type==tp_GetValues) - } // if (e) - } // if (scantrack) - } // if (scandir!=0.0) - return TCommandType::cm_Unknown; // nic + return (scanvel > 0 ? TCommandType::cm_ShuntVelocity : TCommandType::cm_Unknown); + } +*/ + return ((VelNext > 0) ? + TCommandType::cm_Unknown : // no need to bother if we can continue in current direction + (scanvel == 0) ? + TCommandType::cm_Unknown : // no need to bother with a stop signal + TCommandType::cm_ShuntVelocity); // otherwise report a relevant shunt signal behind + } // if (move?... + } // if (OrderCurrentGet()==Shunt) + + return (((e->m_passive) && (e->is_command())) ? TCommandType::cm_Command : + TCommandType::cm_Unknown); }; std::string TController::NextStop() const @@ -7991,13 +7998,13 @@ TController::check_route_behind( double const Range ) { || ( TestFlag( iDrivigFlags, moveConnect ) ) ) }; if( couplinginprogress ) { return; } - auto const comm { BackwardScan( Range ) }; - if( comm != TCommandType::cm_Unknown ) { + auto const commandbehind { BackwardScan( Range ) }; + if( commandbehind != TCommandType::cm_Unknown ) { // jeśli w drugą można jechać // należy sprawdzać odległość od znalezionego sygnalizatora, // aby w przypadku prędkości 0.1 wyciągnąć najpierw skład za sygnalizator // i dopiero wtedy zmienić kierunek jazdy, oczekując podania prędkości >0.5 - if( comm == TCommandType::cm_Command ) { + if( commandbehind == TCommandType::cm_Command ) { // jeśli komenda Shunt to ją odbierz bez przemieszczania się (np. odczep wagony po dopchnięciu do końca toru) iDrivigFlags |= moveStopHere; } diff --git a/Driver.h b/Driver.h index cba21eec..28b88a4d 100644 --- a/Driver.h +++ b/Driver.h @@ -407,7 +407,7 @@ private: double IdleTime{}; // keeps track of time spent at a stop double fStopTime = 0.0; // czas postoju przed dalszą jazdą (np. na przystanku) float ExchangeTime{ 0.0 }; // time needed to finish current load exchange - double fShuntVelocity = 25.0; // prędkość manewrowania, zależy m.in. od składu + double fShuntVelocity = 40.0; // prędkość manewrowania, zależy m.in. od składu int iDrivigFlags = // flagi bitowe ruchu moveStopPoint | // podjedź do W4 możliwie blisko moveStopHere | // nie podjeżdżaj do semafora, jeśli droga nie jest wolna diff --git a/Globals.cpp b/Globals.cpp index ca780f4f..d62fab48 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -695,17 +695,24 @@ global_settings::ConfigParse(cParser &Parser) { { uart_conf.enable = true; Parser.getTokens(3, false); - Parser >> uart_conf.port >> uart_conf.baud >> uart_conf.updatetime; + Parser + >> uart_conf.port + >> uart_conf.baud + >> uart_conf.updatetime; } else if (token == "uarttune") { Parser.getTokens(16); - Parser >> uart_conf.mainbrakemin >> uart_conf.mainbrakemax >> uart_conf.localbrakemin >> - uart_conf.localbrakemax >> uart_conf.tankmax >> uart_conf.tankuart >> - uart_conf.pipemax >> uart_conf.pipeuart >> uart_conf.brakemax >> - uart_conf.brakeuart >> uart_conf.hvmax >> uart_conf.hvuart >> - uart_conf.currentmax >> uart_conf.currentuart >> uart_conf.lvmax >> - uart_conf.lvuart; + Parser + >> uart_conf.mainbrakemin >> uart_conf.mainbrakemax + >> uart_conf.localbrakemin >> uart_conf.localbrakemax + >> uart_conf.tankmax >> uart_conf.tankuart + >> uart_conf.pipemax >> uart_conf.pipeuart + >> uart_conf.brakemax >> uart_conf.brakeuart + >> uart_conf.pantographmax >> uart_conf.pantographuart + >> uart_conf.hvmax >> uart_conf.hvuart + >> uart_conf.currentmax >> uart_conf.currentuart + >> uart_conf.lvmax >> uart_conf.lvuart; } else if (token == "uarttachoscale") { @@ -715,8 +722,11 @@ global_settings::ConfigParse(cParser &Parser) { else if (token == "uartfeature") { Parser.getTokens(4); - Parser >> uart_conf.mainenable >> uart_conf.scndenable >> uart_conf.trainenable >> - uart_conf.localenable; + Parser + >> uart_conf.mainenable + >> uart_conf.scndenable + >> uart_conf.trainenable + >> uart_conf.localenable; } else if (token == "uartdebug") { @@ -1177,6 +1187,8 @@ global_settings::export_as_text( std::ostream &Output ) const { << uart_conf.pipeuart << " " << uart_conf.brakemax << " " << uart_conf.brakeuart << " " + << uart_conf.pantographmax << " " + << uart_conf.pipemax << " " << uart_conf.hvmax << " " << uart_conf.hvuart << " " << uart_conf.currentmax << " " diff --git a/Traction.cpp b/Traction.cpp index 14be7909..e28767c9 100644 --- a/Traction.cpp +++ b/Traction.cpp @@ -767,6 +767,7 @@ traction_table::InitTraction() { scene::node_data nodedata; nodedata.name = traction->asPowerSupplyName; powersource = new TTractionPowerSource( nodedata ); + powersource->IsAutogenerated = true; powersource->Init( traction->NominalVoltage, traction->MaxCurrent ); simulation::Powergrid.insert( powersource ); } diff --git a/TractionPower.h b/TractionPower.h index d96f401d..3bdeacf1 100644 --- a/TractionPower.h +++ b/TractionPower.h @@ -33,6 +33,7 @@ public: // members TTractionPowerSource *psNode[ 2 ] = { nullptr, nullptr }; // zasilanie na końcach dla sekcji bool bSection = false; // czy jest sekcją + bool IsAutogenerated { false }; // indicates autogenerated source for individual traction piece private: // methods diff --git a/Train.cpp b/Train.cpp index 1fd2b13c..b808d850 100644 --- a/Train.cpp +++ b/Train.cpp @@ -799,6 +799,7 @@ TTrain::get_state() const { static_cast( mvOccupied->Compressor ), static_cast( mvOccupied->PipePress ), static_cast( mvOccupied->BrakePress ), + static_cast( mvPantographUnit->PantPress ), fHVoltage, { fHCurrent[ ( mvControlled->TrainType & dt_EZT ) ? 0 : 1 ], fHCurrent[ 2 ], fHCurrent[ 3 ] }, ggLVoltage.GetValue(), diff --git a/Train.h b/Train.h index 2202fb1f..3fe5e4b2 100644 --- a/Train.h +++ b/Train.h @@ -104,6 +104,7 @@ class TTrain { float reservoir_pressure; float pipe_pressure; float brake_pressure; + float pantograph_pressure; float hv_voltage; std::array hv_current; float lv_voltage; diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 0ee313a3..771053fa 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -938,8 +938,8 @@ debug_panel::update_section_vehicle( std::vector &Output ) { } } } + Output.emplace_back( textline, Global.UITextColor ); } - Output.emplace_back( textline, Global.UITextColor ); } std::string @@ -1293,7 +1293,8 @@ debug_panel::update_section_powergrid( std::vector &Output ) { for( auto const *powerstation : simulation::Powergrid.sequence() ) { - if( true == powerstation->bSection ) { continue; } + if( true == powerstation->IsAutogenerated ) { continue; } + if( true == powerstation->bSection ) { continue; } auto const name { ( powerstation->m_name.empty() ? diff --git a/editormode.cpp b/editormode.cpp index 71631eca..9143c47c 100644 --- a/editormode.cpp +++ b/editormode.cpp @@ -106,7 +106,25 @@ editor_mode::enter() { m_statebackup = { Global.pCamera, FreeFlyModeFlag, Global.ControlPicking }; Camera = Global.pCamera; - FreeFlyModeFlag = true; + // NOTE: camera placement is effectively a copy of drivermode DistantView( true ) + // TBD, TODO: refactor into a common vehicle method? + if( false == FreeFlyModeFlag ) { + + auto const *vehicle { Camera.m_owner }; + auto const cab { + ( vehicle->MoverParameters->CabOccupied == 0 ? + 1 : + vehicle->MoverParameters->CabOccupied ) }; + auto const left { vehicle->VectorLeft() * cab }; + Camera.Pos = + Math3D::vector3( Camera.Pos.x, vehicle->GetPosition().y, Camera.Pos.z ) + + left * vehicle->GetWidth() + + Math3D::vector3( 1.25 * left.x, 1.6, 1.25 * left.z ); + Camera.m_owner = nullptr; + Camera.LookAt = vehicle->GetPosition(); + Camera.RaLook(); // jednorazowe przestawienie kamery + FreeFlyModeFlag = true; + } Global.ControlPicking = true; EditorModeFlag = true; diff --git a/uart.cpp b/uart.cpp index 9be1e202..d2900aef 100644 --- a/uart.cpp +++ b/uart.cpp @@ -336,7 +336,8 @@ void uart_input::poll() uint16_t tank_press = (uint16_t)std::min(conf.tankuart, trainstate.reservoir_pressure * 0.1f / conf.tankmax * conf.tankuart); uint16_t pipe_press = (uint16_t)std::min(conf.pipeuart, trainstate.pipe_pressure * 0.1f / conf.pipemax * conf.pipeuart); uint16_t brake_press = (uint16_t)std::min(conf.brakeuart, trainstate.brake_pressure * 0.1f / conf.brakemax * conf.brakeuart); - uint16_t hv_voltage = (uint16_t)std::min(conf.hvuart, trainstate.hv_voltage / conf.hvmax * conf.hvuart); + uint16_t pantograph_press = (uint16_t)std::min(conf.pantographuart, trainstate.pantograph_pressure * 0.1f / conf.pantographmax * conf.pantographuart ); + uint16_t hv_voltage = (uint16_t)std::min(conf.hvuart, trainstate.hv_voltage / conf.hvmax * conf.hvuart); uint16_t current1 = (uint16_t)std::min(conf.currentuart, trainstate.hv_current[0] / conf.currentmax * conf.currentuart); uint16_t current2 = (uint16_t)std::min(conf.currentuart, trainstate.hv_current[1] / conf.currentmax * conf.currentuart); uint16_t current3 = (uint16_t)std::min(conf.currentuart, trainstate.hv_current[2] / conf.currentmax * conf.currentuart); @@ -409,8 +410,10 @@ void uart_input::poll() SPLIT_INT16(lv_voltage), //byte 33 (uint8_t)trainstate.radio_channel, - //byte 34-48 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + //byte 34-35 + SPLIT_INT16(pantograph_press), + //byte 36-48 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (conf.debug) diff --git a/uart.h b/uart.h index 3d864740..649c29ec 100644 --- a/uart.h +++ b/uart.h @@ -23,6 +23,8 @@ public: float pipeuart = 65535.0f; float brakemax = 10.0f; float brakeuart = 65535.0f; + float pantographmax = 10.0f; + float pantographuart = 65535.0f; float hvmax = 100000.0f; float hvuart = 65535.0f; float currentmax = 10000.0f; From 3401f3e176b212667a425218d3c3c2cedc835a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 1 May 2021 23:28:48 +0200 Subject: [PATCH 35/63] Small fix for Driver's calculation of med braking --- Driver.cpp | 4 ++-- Driver.h | 1 + DynObj.cpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index be287014..f02a864f 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -3105,8 +3105,8 @@ bool TController::IncBrakeEIM() case 0: { if( mvOccupied->MED_amax != 9.81 ) { auto const maxpos{mvOccupied->EIMCtrlEmergency ? 0.9 : 1.0 }; - auto const brakelimit{ -2.2 * AccDesired / mvOccupied->MED_amax - 1.0}; //additional limit when hinted is too low - auto const brakehinted{ -1.0 * mvOccupied->AIHintLocalBrakeAccFactor * AccDesired / mvOccupied->MED_amax }; //preffered by AI + auto const brakelimit{ -2.2 * AccDesired / fMedAmax - 1.0}; //additional limit when hinted is too low + auto const brakehinted{ -1.0 * mvOccupied->AIHintLocalBrakeAccFactor * AccDesired / fMedAmax }; //preffered by AI auto const brakeposition{ maxpos * clamp(std::max(brakelimit, brakehinted), 0.0, 1.0)}; OK = ( brakeposition != mvOccupied->LocalBrakePosA ); mvOccupied->LocalBrakePosA = brakeposition; diff --git a/Driver.h b/Driver.h index cba21eec..6ab2c61d 100644 --- a/Driver.h +++ b/Driver.h @@ -354,6 +354,7 @@ public: int UniversalBrakeButtons = 0.0; // flag of which universal buttons need to be pressed int DizelPercentage = 0; // oczekiwane procenty jazdy/hamowania szynobusem int DizelPercentage_Speed = 0; // oczekiwane procenty jazdy/hamowania szynobusem w związku z osiąganiem VelDesired + double fMedAmax = 0.8; //maximum decceleration when using ep/med brake private: bool Psyche = false; int HelperState = 0; //stan pomocnika maszynisty diff --git a/DynObj.cpp b/DynObj.cpp index ca094e5c..1dff2eab 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3114,6 +3114,7 @@ bool TDynamicObject::Update(double dt, double dt1) RapidMult = MoverParameters->RapidMult; auto const amax = RapidMult * std::min(FmaxPN / masamax, MoverParameters->MED_amax); + Mechanik->fMedAmax = amax; auto doorisopen { ( false == MoverParameters->Doors.instances[ side::left ].is_closed ) || ( false == MoverParameters->Doors.instances[ side::right ].is_closed ) From 939408ec489de77e41d8489736d16dcc78511a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sun, 2 May 2021 23:19:47 +0200 Subject: [PATCH 36/63] Repair of St133 drivers' valve for both player and AI --- Driver.cpp | 16 ++++++++-------- McZapkie/hamulce.cpp | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 108079ab..fc971b99 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -3215,15 +3215,15 @@ bool TController::DecBrake() { clamp( -AccDesired / AccMax * mvOccupied->AIHintLocalBrakeAccFactor, 0.0, 1.0 ) ) ); OK = ( mvOccupied->fBrakeCtrlPos != initialbrakeposition ); } - else if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_EPR ) ) { - mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_EPR)); - if( mvOccupied->Handle->GetPos( bh_EPR ) - mvOccupied->Handle->GetPos( bh_EPN ) < 0.1 ) { - mvOccupied->SwitchEPBrake( 1 ); - } - OK = true; - } else { - OK = false; + OK = false; + if (mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos(bh_EPR)) { + mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_EPR)); + OK = true; + } + if (mvOccupied->Handle->GetPos(bh_EPR) - mvOccupied->Handle->GetPos(bh_EPN) < 0.1) { + OK = OK || mvOccupied->SwitchEPBrake(1); + } } if( !OK ) { OK = mvOccupied->DecLocalBrakeLevel( 2 ); diff --git a/McZapkie/hamulce.cpp b/McZapkie/hamulce.cpp index dc7fcfb8..75b498b3 100644 --- a/McZapkie/hamulce.cpp +++ b/McZapkie/hamulce.cpp @@ -3135,7 +3135,7 @@ double TSt113::GetPF(double i_bcp, double PP, double HP, double dt, double ep) BCP = lround(i_bcp); - EPS = BEP_K[BCP]; + EPS = BEP_K[BCP+1]; if (BCP > 0) BCP = BCP - 1; From 750e9a21a67d42943167fd346ae8bb5cc64a6eb1 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Mon, 3 May 2021 02:02:16 +0200 Subject: [PATCH 37/63] load exchange ai logic tweaks, minor bug fixes --- Driver.cpp | 82 +++++++++++++++++++++++++++------------------- Driver.h | 1 + MemCell.cpp | 2 +- driverhints.cpp | 2 +- editoruipanels.cpp | 8 ++--- utilities.h | 2 +- 6 files changed, 56 insertions(+), 41 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 2ed0019c..dab7085f 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -1285,25 +1285,6 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub } } - if( pVehicle->DirectionGet() != m_lastexchangedirection ) { - // generally means the ai driver moved to the opposite end of the consist - // TODO: investigate whether user playing with the reverser can mess this up - auto const left { ( m_lastexchangedirection > 0 ) ? 1 : 2 }; - auto const right { 3 - left }; - m_lastexchangeplatforms = - ( ( m_lastexchangeplatforms & left ) != 0 ? right : 0 ) - + ( ( m_lastexchangeplatforms & right ) != 0 ? left : 0 ); - m_lastexchangedirection = pVehicle->DirectionGet(); - } - if( ( false == TrainParams.IsMaintenance() ) - && ( ( false == TestFlag( iDrivigFlags, moveDoorOpened ) ) - || ( true == DoesAnyDoorNeedOpening ) ) ) { - iDrivigFlags |= moveDoorOpened; // nie wykonywać drugi raz - remove_hint( locale::string::driver_hint_doorleftopen ); - remove_hint( locale::string::driver_hint_doorrightopen ); - Doors( true, m_lastexchangeplatforms ); - } - if (OrderCurrentGet() & ( Shunt | Loose_shunt )) { OrderNext(Obey_train); // uruchomić jazdę pociągową CheckVehicles(); // zmienić światła @@ -2748,6 +2729,7 @@ bool TController::PrepareEngine() if( IsAnyConverterOverloadRelayOpen ) { // wywalił bezpiecznik nadmiarowy przetwornicy + cue_action( locale::string::driver_hint_compressoroff ); // TODO: discern whether compressor needs converter to operate cue_action( locale::string::driver_hint_converteroff ); cue_action( locale::string::driver_hint_primaryconverteroverloadreset ); // reset nadmiarowego } @@ -6625,6 +6607,44 @@ TController::check_departure() { } } +// verify progress of load exchange +void +TController::check_load_exchange() { + + ExchangeTime = 0.f; + DoesAnyDoorNeedOpening = false; + + if( fStopTime > 0 ) { return; } + + // czas postoju przed dalszą jazdą (np. na przystanku) + auto *vehicle { pVehicles[ end::front ] }; + while( vehicle != nullptr ) { + auto const vehicleexchangetime { vehicle->LoadExchangeTime() }; + DoesAnyDoorNeedOpening |= ( ( vehicleexchangetime > 0 ) && ( vehicle->LoadExchangeSpeed() == 0 ) ); + ExchangeTime = std::max( ExchangeTime, vehicleexchangetime ); + vehicle = vehicle->Next(); + } + + if( pVehicle->DirectionGet() != m_lastexchangedirection ) { + // generally means the ai driver moved to the opposite end of the consist + // TODO: investigate whether user playing with the reverser can mess this up + auto const left { ( m_lastexchangedirection > 0 ) ? 1 : 2 }; + auto const right { 3 - left }; + m_lastexchangeplatforms = + ( ( m_lastexchangeplatforms & left ) != 0 ? right : 0 ) + + ( ( m_lastexchangeplatforms & right ) != 0 ? left : 0 ); + m_lastexchangedirection = pVehicle->DirectionGet(); + } + if( ( false == TrainParams.IsMaintenance() ) + && ( ( false == TestFlag( iDrivigFlags, moveDoorOpened ) ) + || ( true == DoesAnyDoorNeedOpening ) ) ) { + iDrivigFlags |= moveDoorOpened; // nie wykonywać drugi raz + remove_hint( locale::string::driver_hint_doorleftopen ); + remove_hint( locale::string::driver_hint_doorrightopen ); + Doors( true, m_lastexchangeplatforms ); + } +} + void TController::UpdateChangeDirection() { // TODO: rework into driver mode independent routine @@ -6640,6 +6660,10 @@ TController::UpdateChangeDirection() { // shared part of the routine, implement if direction matches what was requested if( ( mvOccupied->Vel < EU07_AI_NOMOVEMENT ) && ( iDirection == iDirectionOrder ) ) { + // NOTE: we can't be sure there's a visible signal within scan range after direction change + // which would normally overwrite the old limit, so we reset signal value manually here + VelSignal = -1; + VelSignalNext = -1; PrepareEngine(); JumpToNextOrder(); // następnie robimy, co jest do zrobienia (Shunt albo Obey_train) if( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect ) ) { @@ -7249,22 +7273,12 @@ TController::adjust_desired_speed_for_limits() { } // recalculate potential load exchange duration - DoesAnyDoorNeedOpening = false; - ExchangeTime = 0.f; + check_load_exchange(); + if( ( ExchangeTime > 0 ) + || ( mvOccupied->Vel > 2.0 ) ) { // HACK: force timer reset if the load exchange is cancelled due to departure + WaitingSet( ExchangeTime ); + } if( fStopTime < 0 ) { - // czas postoju przed dalszą jazdą (np. na przystanku) - // verify progress of load exchange - auto *vehicle { pVehicles[ end::front ] }; - while( vehicle != nullptr ) { - auto const vehicleexchangetime { vehicle->LoadExchangeTime() }; - DoesAnyDoorNeedOpening |= ( ( vehicleexchangetime > 0 ) && ( vehicle->LoadExchangeSpeed() == 0 ) ); - ExchangeTime = std::max( ExchangeTime, vehicleexchangetime ); - vehicle = vehicle->Next(); - } - if( ( ExchangeTime > 0 ) - || ( mvOccupied->Vel > 2.0 ) ) { // HACK: force timer reset if the load exchange is cancelled due to departure - WaitingSet( ExchangeTime ); - } VelDesired = 0.0; // jak ma czekać, to nie ma jazdy cue_action( locale::string::driver_hint_waitloadexchange ); return; // speed limit can't get any lower diff --git a/Driver.h b/Driver.h index 28b88a4d..9431c1e7 100644 --- a/Driver.h +++ b/Driver.h @@ -314,6 +314,7 @@ private: void handle_engine(); void handle_orders(); void UpdateNextStop(); + void check_load_exchange(); // returns: estimated remaining time of load exchange, in seconds void check_departure(); void UpdateConnect(); void UpdateDisconnect(); diff --git a/MemCell.cpp b/MemCell.cpp index 950f5dee..f0cd8bca 100644 --- a/MemCell.cpp +++ b/MemCell.cpp @@ -71,7 +71,7 @@ TCommandType TMemCell::CommandCheck() eCommand = TCommandType::cm_OutsideStation; bCommand = false; // tego nie powinno być w komórce } - else if( szText.compare( 0, 19, "PassengerStopPoint:" ) == 0 ) // porównanie początków + else if( starts_with( szText, "PassengerStopPoint:" ) ) // porównanie początków { eCommand = TCommandType::cm_PassengerStopPoint; bCommand = false; // tego nie powinno być w komórce diff --git a/driverhints.cpp b/driverhints.cpp index fb0af11d..e9e16ce0 100644 --- a/driverhints.cpp +++ b/driverhints.cpp @@ -723,7 +723,7 @@ TController::cue_action( locale::string const Action, float const Actionparamete hint( Action, [this](float const Parameter) -> bool { - return ( mvPantographUnit->PantPress >= 4.2 ); } ); + return ( mvPantographUnit->PantPress >= ( is_emu() ? ( mvPantographUnit->PantPressLockActive ? 4.6 : 2.6 ) : 4.2 ) ); } ); break; } case locale::string::driver_hint_waitloadexchange: { diff --git a/editoruipanels.cpp b/editoruipanels.cpp index ab20397d..66f58283 100644 --- a/editoruipanels.cpp +++ b/editoruipanels.cpp @@ -58,7 +58,7 @@ itemproperties_panel::update( scene::basic_node const *Node ) { } */ textline = - "name: " + ( node->name().empty() ? "(none)" : node->name() ) + "name: " + ( node->name().empty() ? "(none)" : Bezogonkow( node->name() ) ) + "\nlocation: [" + to_string( node->location().x, 2 ) + ", " + to_string( node->location().y, 2 ) + ", " + to_string( node->location().z, 2 ) + "]" + " (distance: " + to_string( glm::length( glm::dvec3{ node->location().x, 0.0, node->location().z } -glm::dvec3{ camera.Pos.x, 0.0, camera.Pos.z } ), 1 ) + " m)"; text_lines.emplace_back( textline, Global.UITextColor ); @@ -112,7 +112,7 @@ itemproperties_panel::update( scene::basic_node const *Node ) { auto const *subnode = static_cast( node ); // basic attributes textline = - "isolated: " + ( ( subnode->pIsolated != nullptr ) ? subnode->pIsolated->asName : "(none)" ) + "isolated: " + ( ( subnode->pIsolated != nullptr ) ? Bezogonkow( subnode->pIsolated->asName ) : "(none)" ) + "\nvelocity: " + to_string( subnode->SwitchExtension ? subnode->SwitchExtension->fVelocity : subnode->fVelocity ) + "\nwidth: " + to_string( subnode->fTrackWidth ) + " m" + "\nfriction: " + to_string( subnode->fFriction, 2 ) @@ -168,7 +168,7 @@ itemproperties_panel::update( scene::basic_node const *Node ) { } textline += ( event.second != nullptr ? - event.second->m_name : + Bezogonkow( event.second->m_name ) : event.first + " (missing)" ); } textline += "] "; @@ -184,7 +184,7 @@ itemproperties_panel::update( scene::basic_node const *Node ) { + " [" + to_string( subnode->Value1(), 2 ) + "]" + " [" + to_string( subnode->Value2(), 2 ) + "]"; text_lines.emplace_back( textline, Global.UITextColor ); - textline = "track: " + ( subnode->asTrackName.empty() ? "(none)" : subnode->asTrackName ); + textline = "track: " + ( subnode->asTrackName.empty() ? "(none)" : Bezogonkow( subnode->asTrackName ) ); text_lines.emplace_back( textline, Global.UITextColor ); } diff --git a/utilities.h b/utilities.h index 6d280ab0..c1f1d3c9 100644 --- a/utilities.h +++ b/utilities.h @@ -215,7 +215,7 @@ std::ptrdiff_t len_common_prefix( std::string const &Left, std::string const &Ri // returns true if provided string ends with another provided string bool ends_with( std::string_view String, std::string_view Suffix ); // returns true if provided string begins with another provided string -bool starts_with( std::string_view String, std::string_view Suffix ); +bool starts_with( std::string_view String, std::string_view Prefix ); template void SafeDelete( Type_ &Pointer ) { From fa9136e3333bb6fd7a2560fb55959c3d7d3d502e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sun, 2 May 2021 23:19:47 +0200 Subject: [PATCH 38/63] Repair of St133 drivers' valve for both player and AI --- Driver.cpp | 16 ++++++++-------- McZapkie/hamulce.cpp | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index dab7085f..13b19068 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -3197,15 +3197,15 @@ bool TController::DecBrake() { clamp( -AccDesired / AccMax * mvOccupied->AIHintLocalBrakeAccFactor, 0.0, 1.0 ) ) ); OK = ( mvOccupied->fBrakeCtrlPos != initialbrakeposition ); } - else if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_EPR ) ) { - mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_EPR)); - if( mvOccupied->Handle->GetPos( bh_EPR ) - mvOccupied->Handle->GetPos( bh_EPN ) < 0.1 ) { - mvOccupied->SwitchEPBrake( 1 ); - } - OK = true; - } else { - OK = false; + OK = false; + if (mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos(bh_EPR)) { + mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_EPR)); + OK = true; + } + if (mvOccupied->Handle->GetPos(bh_EPR) - mvOccupied->Handle->GetPos(bh_EPN) < 0.1) { + OK = OK || mvOccupied->SwitchEPBrake(1); + } } if( !OK ) { OK = mvOccupied->DecLocalBrakeLevel( 2 ); diff --git a/McZapkie/hamulce.cpp b/McZapkie/hamulce.cpp index dc7fcfb8..75b498b3 100644 --- a/McZapkie/hamulce.cpp +++ b/McZapkie/hamulce.cpp @@ -3135,7 +3135,7 @@ double TSt113::GetPF(double i_bcp, double PP, double HP, double dt, double ep) BCP = lround(i_bcp); - EPS = BEP_K[BCP]; + EPS = BEP_K[BCP+1]; if (BCP > 0) BCP = BCP - 1; From 51415ef71fd3ee18d9a2fbd3b455a938312412d7 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sun, 9 May 2021 02:01:16 +0200 Subject: [PATCH 39/63] build 210508. spring brake sound support, internaldata sounds update fix, minor bug fixes --- Driver.cpp | 5 +- DynObj.cpp | 24 +- DynObj.h | 7 + Gauge.cpp | 41 ++- Gauge.h | 6 +- Train.cpp | 900 ++++++++++++++++++++++++----------------------------- Train.h | 52 ++-- version.h | 2 +- 8 files changed, 494 insertions(+), 543 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 13b19068..e968e086 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -2709,10 +2709,11 @@ bool TController::PrepareEngine() cue_action( locale::string::driver_hint_pantographcompressoroff ); // sprężarkę pantografów można już wyłączyć } } + // TODO: make pantograph setup a part of control_pantographs() and call it here instead if( ( fOverhead2 == -1.0 ) && ( iOverheadDown == 0 ) ) { cue_action( locale::string::driver_hint_pantographsvalveon ); - cue_action( locale::string::driver_hint_frontpantographvalveon ); - cue_action( locale::string::driver_hint_rearpantographvalveon ); + cue_action( locale::string::driver_hint_frontpantographvalveon, ( iDirection >= 0 ? 5 : 0 ) ); + cue_action( locale::string::driver_hint_rearpantographvalveon, ( iDirection >= 0 ? 0 : 5 ) ); } } diff --git a/DynObj.cpp b/DynObj.cpp index ca094e5c..928deef6 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -4209,7 +4209,7 @@ void TDynamicObject::RenderSounds() { } // Dzwiek odluzniacza - if( MoverParameters->Hamulec->GetStatus() & b_rls ) { + if( MoverParameters->Hamulec->Releaser() ) { sReleaser .gain( clamp( @@ -4294,6 +4294,19 @@ void TDynamicObject::RenderSounds() { rsPisk.stop(); } + // spring brake + if( m_springbrakesounds.state != MoverParameters->SpringBrake.Activate ) { + m_springbrakesounds.state = MoverParameters->SpringBrake.Activate; + if( m_springbrakesounds.state ) { + m_springbrakesounds.activate.play( sound_flags::exclusive ); + m_springbrakesounds.release.stop(); + } + else { + m_springbrakesounds.activate.stop(); + m_springbrakesounds.release.play( sound_flags::exclusive ); + } + } + // other sounds // load exchange if( MoverParameters->LoadStatus & 1 ) { @@ -5702,6 +5715,15 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co rsUnbrake.deserialize( parser, sound_type::single, sound_parameters::range ); rsUnbrake.owner( this ); } + // spring brake sounds + else if( token == "springbrake:" ) { + m_springbrakesounds.activate.deserialize( parser, sound_type::single ); + m_springbrakesounds.activate.owner( this ); + } + else if( token == "springbrakeoff:" ) { + m_springbrakesounds.release.deserialize( parser, sound_type::single ); + m_springbrakesounds.release.owner( this ); + } else if( token == "derail:" ) { // dzwiek przy wykolejeniu diff --git a/DynObj.h b/DynObj.h index aa60fb21..7c5283ed 100644 --- a/DynObj.h +++ b/DynObj.h @@ -421,6 +421,12 @@ private: sound_source announcement; std::deque announcement_queue; // fifo queue }; + struct springbrake_sounds { + sound_source activate { sound_placement::external }; + sound_source release { sound_placement::external }; + bool state { false }; + }; + // methods void ABuLittleUpdate(double ObjSqrDist); @@ -496,6 +502,7 @@ private: sound_source m_emergencybrake { sound_placement::engine }; double m_emergencybrakeflow{ 0.f }; sound_source sReleaser { sound_placement::external }; + springbrake_sounds m_springbrakesounds; sound_source rsSlippery { sound_placement::external, EU07_SOUND_BRAKINGCUTOFFRANGE }; // moved from cab sound_source sSand { sound_placement::external }; // moving part and other external sounds diff --git a/Gauge.cpp b/Gauge.cpp index e6853a17..71f78d32 100644 --- a/Gauge.cpp +++ b/Gauge.cpp @@ -286,25 +286,25 @@ TGauge::Load_mapping( cParser &Input, TGauge::scratch_data &Scratchpad ) { return true; // return value marks a key: value pair was extracted, nothing about whether it's recognized } -void -TGauge::UpdateValue( float fNewDesired ) { - - return UpdateValue( fNewDesired, nullptr ); -} - -void -TGauge::UpdateValue( float fNewDesired, sound_source &Fallbacksound ) { - - return UpdateValue( fNewDesired, &Fallbacksound ); -} - // ustawienie wartości docelowej. plays provided fallback sound, if no sound was defined in the control itself -void -TGauge::UpdateValue( float fNewDesired, sound_source *Fallbacksound ) { +bool +TGauge::UpdateValue( float fNewDesired, std::optional &Fallbacksound ) { + + if( false == UpdateValue( fNewDesired ) ) { + if( Fallbacksound ) { + Fallbacksound->play( m_soundtype ); + return true; + } + } + return false; +} + +bool +TGauge::UpdateValue( float fNewDesired ) { auto const desiredtimes100 = static_cast( std::round( 100.0 * fNewDesired ) ); if( desiredtimes100 == static_cast( std::round( 100.0 * m_targetvalue ) ) ) { - return; + return true; } m_targetvalue = fNewDesired; // if there's any sound associated with new requested value, play it @@ -315,7 +315,7 @@ TGauge::UpdateValue( float fNewDesired, sound_source *Fallbacksound ) { auto const lookup = m_soundfxvalues.find( desiredtimes100 / 100 ); if( lookup != m_soundfxvalues.end() ) { lookup->second.play(); - return; + return true; } } else { @@ -329,20 +329,17 @@ TGauge::UpdateValue( float fNewDesired, sound_source *Fallbacksound ) { // shift up if( false == m_soundfxincrease.empty() ) { m_soundfxincrease.play( m_soundtype ); - return; + return true; } } else if( currentvalue > fNewDesired ) { // shift down if( false == m_soundfxdecrease.empty() ) { m_soundfxdecrease.play( m_soundtype ); - return; + return true; } } - if( Fallbacksound != nullptr ) { - // ...and if that fails too, try the provided fallback sound from legacy system - Fallbacksound->play( m_soundtype ); - } + return false; // no suitable sound was found }; void TGauge::PutValue(float fNewDesired) diff --git a/Gauge.h b/Gauge.h index d2e40b0b..1bc3fdf8 100644 --- a/Gauge.h +++ b/Gauge.h @@ -43,8 +43,8 @@ public: *this = TGauge(); } void Init(TSubModel *Submodel, TSubModel *Submodelon, TGaugeAnimation Type, float Scale = 1, float Offset = 0, float Friction = 0, float Value = 0, float const Endvalue = -1.0, float const Endscale = -1.0, bool const Interpolate = false ); void Load(cParser &Parser, TDynamicObject const *Owner, double const mul = 1.0); - void UpdateValue( float fNewDesired ); - void UpdateValue( float fNewDesired, sound_source &Fallbacksound ); + bool UpdateValue( float fNewDesired ); + bool UpdateValue( float fNewDesired, std::optional &Fallbacksound ); void PutValue(float fNewDesired); float GetValue() const; float GetDesiredValue() const; @@ -79,8 +79,6 @@ private: // imports member data pair from the config file bool Load_mapping( cParser &Input, TGauge::scratch_data &Scratchpad ); - void - UpdateValue( float fNewDesired, sound_source *Fallbacksound ); float GetScaledValue() const; void diff --git a/Train.cpp b/Train.cpp index b808d850..fcd0d114 100644 --- a/Train.cpp +++ b/Train.cpp @@ -671,32 +671,33 @@ dictionary_source *TTrain::GetTrainState( dictionary_source const &Extraparamete for( int j = 0; j < 10; ++j ) dict->insert( ( "eimp_t_" + std::string( TXTT[ j ] ) ), fEIMParams[ 0 ][ j ] ); for( int i = 0; i < 8; ++i ) { + auto const idx { std::to_string( i + 1 ) }; for( int j = 0; j < 10; ++j ) - dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_" + std::string( TXTC[ j ] ) ), fEIMParams[ i + 1 ][ j ] ); + dict->insert( ( "eimp_c" + idx + "_" + std::string( TXTC[ j ] ) ), fEIMParams[ i + 1 ][ j ] ); for (int j = 0; j < 10; ++j) - dict->insert(("diesel_param_" + std::to_string(i + 1) + "_" + std::string(TXTD[j])), fDieselParams[i + 1][j]); + dict->insert(("diesel_param_" + idx + "_" + std::string(TXTD[j])), fDieselParams[i + 1][j]); - dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_ms" ), bMains[ i ] ); - dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_cv" ), fCntVol[ i ] ); - dict->insert( ( "eimp_u" + std::to_string( i + 1 ) + "_pf" ), bPants[ i ][ 0 ] ); - dict->insert( ( "eimp_u" + std::to_string( i + 1 ) + "_pr" ), bPants[ i ][ 1 ] ); - dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_fuse" ), bFuse[ i ] ); - dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_batt" ), bBatt[ i ] ); - dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_conv" ), bConv[ i ] ); - dict->insert( ( "eimp_u" + std::to_string( i + 1 ) + "_comp_a" ), bComp[ i ][ 0 ] ); - dict->insert( ( "eimp_u" + std::to_string( i + 1 ) + "_comp_w" ), bComp[ i ][ 1 ] ); - //dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_comp_a" ), bComp[ i ][ 2 ]); - //dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_comp_w" ), bComp[ i ][ 3 ]); - dict->insert( ( "eimp_c" + std::to_string( i + 1 ) + "_heat" ), bHeat[ i ] ); + dict->insert( ( "eimp_c" + idx + "_ms" ), bMains[ i ] ); + dict->insert( ( "eimp_c" + idx + "_cv" ), fCntVol[ i ] ); + dict->insert( ( "eimp_c" + idx + "_fuse" ), bFuse[ i ] ); + dict->insert( ( "eimp_c" + idx + "_batt" ), bBatt[ i ] ); + dict->insert( ( "eimp_c" + idx + "_conv" ), bConv[ i ] ); + dict->insert( ( "eimp_c" + idx + "_heat" ), bHeat[ i ] ); + + dict->insert( ( "eimp_u" + idx + "_pf" ), bPants[ i ][ 0 ] ); + dict->insert( ( "eimp_u" + idx + "_pr" ), bPants[ i ][ 1 ] ); + dict->insert( ( "eimp_u" + idx + "_comp_a" ), bComp[ i ][ 0 ] ); + dict->insert( ( "eimp_u" + idx + "_comp_w" ), bComp[ i ][ 1 ] ); } dict->insert( "compressors_no", (int)bCompressors.size() ); for (int i = 0; i < bCompressors.size(); i++) { - dict->insert("compressors_" + std::to_string(i + 1) + "_allow", std::get<0>(bCompressors[i])); - dict->insert("compressors_" + std::to_string(i + 1) + "_work", std::get<1>(bCompressors[i])); - dict->insert("compressors_" + std::to_string(i + 1) + "_car_no", std::get<2>(bCompressors[i])); + auto const idx { std::to_string( i + 1 ) }; + dict->insert("compressors_" + idx + "_allow", std::get<0>(bCompressors[i])); + dict->insert("compressors_" + idx + "_work", std::get<1>(bCompressors[i])); + dict->insert("compressors_" + idx + "_car_no", std::get<2>(bCompressors[i])); } @@ -856,7 +857,9 @@ void TTrain::set_train_brake( double const Position ) { ( ( originalbrakeposition / 100 == 0 ) || ( originalbrakeposition / 100 >= 5 ) ) && ( ( mvOccupied->BrakeCtrlPos == 0 ) || ( mvOccupied->BrakeCtrlPos >= 5 ) ) ) ) ) { // sound feedback if the lever movement activates one of the switches - dsbPneumaticSwitch.play(); + if( dsbPneumaticSwitch ) { + dsbPneumaticSwitch->play(); + } } } @@ -1839,7 +1842,9 @@ void TTrain::OnCommand_epbrakecontroltoggle( TTrain *Train, command_data const & // turn on if( Train->mvOccupied->EpFuseSwitch( true ) ) { // audio feedback - Train->dsbPneumaticSwitch.play(); + if( Train->dsbPneumaticSwitch ) { + Train->dsbPneumaticSwitch->play(); + } }; } else { @@ -1851,7 +1856,9 @@ void TTrain::OnCommand_epbrakecontroltoggle( TTrain *Train, command_data const & // potentially turn on if( Train->mvOccupied->EpFuseSwitch( true ) ) { // audio feedback - Train->dsbPneumaticSwitch.play(); + if( Train->dsbPneumaticSwitch ) { + Train->dsbPneumaticSwitch->play(); + } }; } // visual feedback @@ -6379,23 +6386,27 @@ bool TTrain::Update( double const Deltatime ) bPants[iUnitNo - 1][end::front] = ( bPants[iUnitNo - 1][end::front] || p->MoverParameters->Pantographs[end::front].is_active ); bPants[iUnitNo - 1][end::rear] = ( bPants[iUnitNo - 1][end::rear] || p->MoverParameters->Pantographs[end::rear].is_active ); } - bComp[iUnitNo - 1][0] = (bComp[iUnitNo - 1][0] || p->MoverParameters->CompressorAllow || (p->MoverParameters->CompressorStart == start_t::automatic)); - bSlip[i] = p->MoverParameters->SlippingWheels; + // TBD, TODO: clean up compressor data arrangement? + if( iUnitNo <= 8 ) { + bComp[iUnitNo - 1][0] = (bComp[iUnitNo - 1][0] || p->MoverParameters->CompressorAllow || (p->MoverParameters->CompressorStart == start_t::automatic)); + } if (p->MoverParameters->CompressorSpeed > 0.00001) { - bComp[iUnitNo - 1][1] = (bComp[iUnitNo - 1][1] || p->MoverParameters->CompressorFlag); + if( iUnitNo <= 8 ) { + bComp[iUnitNo - 1][1] = (bComp[iUnitNo - 1][1] || p->MoverParameters->CompressorFlag); + } bCompressors.emplace_back( p->MoverParameters->CompressorAllow || (p->MoverParameters->CompressorStart == start_t::automatic), p->MoverParameters->CompressorFlag, i); } + bSlip[i] = p->MoverParameters->SlippingWheels; if ((in < 8) && (p->MoverParameters->eimc[eimc_p_Pmax] > 1)) { fEIMParams[1 + in][0] = p->MoverParameters->eimv[eimv_Fmax]; fEIMParams[1 + in][1] = Max0R(fEIMParams[1 + in][0], 0); fEIMParams[1 + in][2] = -Min0R(fEIMParams[1 + in][0], 0); - fEIMParams[1 + in][3] = p->MoverParameters->eimv[eimv_Fmax] / - Max0R(p->MoverParameters->eimv[eimv_Fful], 1); + fEIMParams[1 + in][3] = p->MoverParameters->eimv[eimv_Fmax] / Max0R(p->MoverParameters->eimv[eimv_Fful], 1); fEIMParams[1 + in][4] = Max0R(fEIMParams[1 + in][3], 0); fEIMParams[1 + in][5] = -Min0R(fEIMParams[1 + in][3], 0); fEIMParams[1 + in][6] = p->MoverParameters->eimv[eimv_If]; @@ -6437,10 +6448,9 @@ bool TTrain::Update( double const Deltatime ) in++; iPowerNo = in; } - // p = p->NextC(4); //prev - if ((kier ? p->NextC(128) : p->PrevC(128)) != (kier ? p->NextC(4) : p->PrevC(4))) + if ((kier ? p->NextC(coupling::permanent) : p->PrevC(coupling::permanent)) != (kier ? p->NextC(coupling::control) : p->PrevC(coupling::control))) iUnitNo++; - p = (kier ? p->NextC(4) : p->PrevC(4)); + p = (kier ? p->NextC(coupling::control) : p->PrevC(coupling::control)); iCarNo = i + 1; } else @@ -7437,32 +7447,34 @@ TTrain::update_sounds( double const Deltatime ) { } m_lastlocalbrakepressure = mvOccupied->LocBrakePress; // local brake, release - if( ( m_localbrakepressurechange < -0.05f ) - && ( mvOccupied->LocBrakePress > mvOccupied->BrakePress - 0.05 ) ) { - rsSBHiss - .gain( clamp( rsSBHiss.m_amplitudeoffset + rsSBHiss.m_amplitudefactor * -m_localbrakepressurechange * 0.05, 0.0, 1.5 ) ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - // don't stop the sound too abruptly - volume = std::max( 0.0, rsSBHiss.gain() - 0.1 * Deltatime ); - rsSBHiss.gain( volume ); - if( volume < 0.05 ) { - rsSBHiss.stop(); + if( rsSBHiss ) { + if( ( m_localbrakepressurechange < -0.05f ) + && ( mvOccupied->LocBrakePress > mvOccupied->BrakePress - 0.05 ) ) { + rsSBHiss->gain( clamp( rsSBHiss->m_amplitudeoffset + rsSBHiss->m_amplitudefactor * -m_localbrakepressurechange * 0.05, 0.0, 1.5 ) ); + rsSBHiss->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + // don't stop the sound too abruptly + volume = std::max( 0.0, rsSBHiss->gain() - 0.1 * Deltatime ); + rsSBHiss->gain( volume ); + if( volume < 0.05 ) { + rsSBHiss->stop(); + } } } // local brake, engage - if( m_localbrakepressurechange > 0.05f ) { - rsSBHissU - .gain( clamp( rsSBHissU.m_amplitudeoffset + rsSBHissU.m_amplitudefactor * m_localbrakepressurechange * 0.05, 0.0, 1.5 ) ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - // don't stop the sound too abruptly - volume = std::max( 0.0, rsSBHissU.gain() - 0.1 * Deltatime ); - rsSBHissU.gain( volume ); - if( volume < 0.05 ) { - rsSBHissU.stop(); + if( rsSBHissU ) { + if( m_localbrakepressurechange > 0.05f ) { + rsSBHissU->gain( clamp( rsSBHissU->m_amplitudeoffset + rsSBHissU->m_amplitudefactor * m_localbrakepressurechange * 0.05, 0.0, 1.5 ) ); + rsSBHissU->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + // don't stop the sound too abruptly + volume = std::max( 0.0, rsSBHissU->gain() - 0.1 * Deltatime ); + rsSBHissU->gain( volume ); + if( volume < 0.05 ) { + rsSBHissU->stop(); + } } } @@ -7471,237 +7483,259 @@ TTrain::update_sounds( double const Deltatime ) { if( ( mvOccupied->BrakeHandle == TBrakeHandle::FV4a ) || ( mvOccupied->BrakeHandle == TBrakeHandle::FVel6 ) ) { // upuszczanie z PG - fPPress = interpolate( fPPress, static_cast( mvOccupied->Handle->GetSound( s_fv4a_b ) ), 0.05f ); - volume = ( - fPPress > 0 ? - rsHiss.m_amplitudefactor * fPPress * 0.25 : - 0 ); - if( volume * brakevolumescale > 0.05 ) { - rsHiss - .gain( volume * brakevolumescale ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - rsHiss.stop(); + if( rsHiss ) { + fPPress = interpolate( fPPress, static_cast( mvOccupied->Handle->GetSound( s_fv4a_b ) ), 0.05f ); + volume = ( + fPPress > 0 ? + rsHiss->m_amplitudefactor * fPPress * 0.25 : + 0 ); + if( volume * brakevolumescale > 0.05 ) { + rsHiss->gain( volume * brakevolumescale ); + rsHiss->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + rsHiss->stop(); + } } // napelnianie PG - fNPress = interpolate( fNPress, static_cast( mvOccupied->Handle->GetSound( s_fv4a_u ) ), 0.25f ); - volume = ( - fNPress > 0 ? - rsHissU.m_amplitudefactor * fNPress : - 0 ); - if( volume * brakevolumescale > 0.05 ) { - rsHissU - .gain( volume * brakevolumescale ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - rsHissU.stop(); + if( rsHissU ) { + fNPress = interpolate( fNPress, static_cast( mvOccupied->Handle->GetSound( s_fv4a_u ) ), 0.25f ); + volume = ( + fNPress > 0 ? + rsHissU->m_amplitudefactor * fNPress : + 0 ); + if( volume * brakevolumescale > 0.05 ) { + rsHissU->gain( volume * brakevolumescale ); + rsHissU->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + rsHissU->stop(); + } } // upuszczanie przy naglym - volume = mvOccupied->Handle->GetSound( s_fv4a_e ) * rsHissE.m_amplitudefactor; - if( volume * brakevolumescale > 0.05 ) { - rsHissE - .gain( volume * brakevolumescale ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - rsHissE.stop(); + if( rsHissE ) { + volume = mvOccupied->Handle->GetSound( s_fv4a_e ) * rsHissE->m_amplitudefactor; + if( volume * brakevolumescale > 0.05 ) { + rsHissE->gain( volume * brakevolumescale ); + rsHissE->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + rsHissE->stop(); + } } // upuszczanie sterujacego fala - volume = mvOccupied->Handle->GetSound( s_fv4a_x ) * rsHissX.m_amplitudefactor; - if( volume * brakevolumescale > 0.05 ) { - rsHissX - .gain( volume * brakevolumescale ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - rsHissX.stop(); + if( rsHissX ) { + volume = mvOccupied->Handle->GetSound( s_fv4a_x ) * rsHissX->m_amplitudefactor; + if( volume * brakevolumescale > 0.05 ) { + rsHissX->gain( volume * brakevolumescale ); + rsHissX->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + rsHissX->stop(); + } } // upuszczanie z czasowego - volume = mvOccupied->Handle->GetSound( s_fv4a_t ) * rsHissT.m_amplitudefactor; - if( volume * brakevolumescale > 0.05 ) { - rsHissT - .gain( volume * brakevolumescale ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - rsHissT.stop(); + if( rsHissT ) { + volume = mvOccupied->Handle->GetSound( s_fv4a_t ) * rsHissT->m_amplitudefactor; + if( volume * brakevolumescale > 0.05 ) { + rsHissT->gain( volume * brakevolumescale ); + rsHissT->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + rsHissT->stop(); + } } } else { // jesli nie FV4a // upuszczanie z PG - fPPress = ( 4.0f * fPPress + std::max( mvOccupied->dpLocalValve, mvOccupied->dpMainValve ) ) / ( 4.0f + 1.0f ); - volume = ( - fPPress > 0.0f ? - 2.0 * rsHiss.m_amplitudefactor * fPPress : - 0.0 ); - if( volume > 0.05 ) { - rsHiss - .gain( volume ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - rsHiss.stop(); + if( rsHiss ) { + fPPress = ( 4.0f * fPPress + std::max( mvOccupied->dpLocalValve, mvOccupied->dpMainValve ) ) / ( 4.0f + 1.0f ); + volume = ( + fPPress > 0.0f ? + 2.0 * rsHiss->m_amplitudefactor * fPPress : + 0.0 ); + if( volume > 0.05 ) { + rsHiss->gain( volume ); + rsHiss->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + rsHiss->stop(); + } } // napelnianie PG - fNPress = ( 4.0f * fNPress + Min0R( mvOccupied->dpLocalValve, mvOccupied->dpMainValve ) ) / ( 4.0f + 1.0f ); - volume = ( - fNPress < 0.0f ? - -1.0 * rsHissU.m_amplitudefactor * fNPress : - 0.0 ); - if( volume > 0.01 ) { - rsHissU - .gain( volume ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - rsHissU.stop(); + if( rsHissU ) { + fNPress = ( 4.0f * fNPress + Min0R( mvOccupied->dpLocalValve, mvOccupied->dpMainValve ) ) / ( 4.0f + 1.0f ); + volume = ( + fNPress < 0.0f ? + -1.0 * rsHissU->m_amplitudefactor * fNPress : + 0.0 ); + if( volume > 0.01 ) { + rsHissU->gain( volume ); + rsHissU->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + rsHissU->stop(); + } } } // koniec nie FV4a // brakes - if( ( mvOccupied->UnitBrakeForce > 10.0 ) - && ( mvOccupied->Vel > 0.05 ) ) { + if( rsBrake ) { + if( ( mvOccupied->UnitBrakeForce > 10.0 ) + && ( mvOccupied->Vel > 0.05 ) ) { - auto const brakeforceratio { - clamp( - mvOccupied->UnitBrakeForce / std::max( 1.0, mvOccupied->BrakeForceR( 1.0, mvOccupied->Vel ) / ( mvOccupied->NAxles * std::max( 1, mvOccupied->NBpA ) ) ), - 0.0, 1.0 ) }; - // HACK: in external view mute the sound rather than stop it, in case there's an opening bookend it'd (re)play on sound restart after returning inside - volume = ( - FreeFlyModeFlag ? - 0.0 : - rsBrake.m_amplitudeoffset - + std::sqrt( brakeforceratio * interpolate( 0.4, 1.0, ( mvOccupied->Vel / ( 1 + mvOccupied->Vmax ) ) ) ) * rsBrake.m_amplitudefactor ); - rsBrake - .pitch( rsBrake.m_frequencyoffset + mvOccupied->Vel * rsBrake.m_frequencyfactor ) - .gain( volume ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - rsBrake.stop(); + auto const brakeforceratio{ + clamp( + mvOccupied->UnitBrakeForce / std::max( 1.0, mvOccupied->BrakeForceR( 1.0, mvOccupied->Vel ) / ( mvOccupied->NAxles * std::max( 1, mvOccupied->NBpA ) ) ), + 0.0, 1.0 ) }; + // HACK: in external view mute the sound rather than stop it, in case there's an opening bookend it'd (re)play on sound restart after returning inside + volume = ( + FreeFlyModeFlag ? + 0.0 : + rsBrake->m_amplitudeoffset + + std::sqrt( brakeforceratio * interpolate( 0.4, 1.0, ( mvOccupied->Vel / ( 1 + mvOccupied->Vmax ) ) ) ) * rsBrake->m_amplitudefactor ); + rsBrake->pitch( rsBrake->m_frequencyoffset + mvOccupied->Vel * rsBrake->m_frequencyfactor ); + rsBrake->gain( volume ); + rsBrake->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + rsBrake->stop(); + } } // ambient sound // since it's typically ticking of the clock we can center it on tachometer or on middle of compartment bounding area - rsFadeSound.play( sound_flags::exclusive | sound_flags::looping ); + if( rsFadeSound ) { + rsFadeSound->play( sound_flags::exclusive | sound_flags::looping ); + } - if( mvControlled->TrainType == dt_181 ) { + if( dsbSlipAlarm ) { // alarm przy poslizgu dla 181/182 - BOMBARDIER if( ( mvControlled->SlippingWheels ) && ( DynamicObject->GetVelocity() > 1.0 ) ) { - dsbSlipAlarm.play( sound_flags::exclusive | sound_flags::looping ); + dsbSlipAlarm->play( sound_flags::exclusive | sound_flags::looping ); } else { - dsbSlipAlarm.stop(); + dsbSlipAlarm->stop(); } } // szum w czasie jazdy - if( ( false == FreeFlyModeFlag ) - && ( false == Global.CabWindowOpen ) - && ( DynamicObject->GetVelocity() > 0.5 ) ) { + if( rsRunningNoise ) { + if( ( false == FreeFlyModeFlag ) + && ( false == Global.CabWindowOpen ) + && ( DynamicObject->GetVelocity() > 0.5 ) ) { - update_sounds_runningnoise( rsRunningNoise ); - } - else { - // don't play the optional ending sound if the listener switches views - rsRunningNoise.stop( true == FreeFlyModeFlag ); + update_sounds_runningnoise( *rsRunningNoise ); + } + else { + // don't play the optional ending sound if the listener switches views + rsRunningNoise->stop( true == FreeFlyModeFlag ); + } } // hunting oscillation noise - if( ( false == FreeFlyModeFlag ) - && ( false == Global.CabWindowOpen ) - && ( DynamicObject->GetVelocity() > 0.5 ) - && ( DynamicObject->IsHunting ) ) { + if( rsHuntingNoise ) { + if( ( false == FreeFlyModeFlag ) + && ( false == Global.CabWindowOpen ) + && ( DynamicObject->GetVelocity() > 0.5 ) + && ( DynamicObject->IsHunting ) ) { - update_sounds_runningnoise( rsHuntingNoise ); - // modify calculated sound volume by hunting amount - auto const huntingamount = - interpolate( - 0.0, 1.0, - clamp( - ( mvOccupied->Vel - DynamicObject->HuntingShake.fadein_begin ) / ( DynamicObject->HuntingShake.fadein_end - DynamicObject->HuntingShake.fadein_begin ), - 0.0, 1.0 ) ); + update_sounds_runningnoise( *rsHuntingNoise ); + // modify calculated sound volume by hunting amount + auto const huntingamount = + interpolate( + 0.0, 1.0, + clamp( + ( mvOccupied->Vel - DynamicObject->HuntingShake.fadein_begin ) / ( DynamicObject->HuntingShake.fadein_end - DynamicObject->HuntingShake.fadein_begin ), + 0.0, 1.0 ) ); - rsHuntingNoise.gain( rsHuntingNoise.gain() * huntingamount ); - } - else { - // don't play the optional ending sound if the listener switches views - rsHuntingNoise.stop( true == FreeFlyModeFlag ); + rsHuntingNoise->gain( rsHuntingNoise->gain() * huntingamount ); + } + else { + // don't play the optional ending sound if the listener switches views + rsHuntingNoise->stop( true == FreeFlyModeFlag ); + } } // rain sound - if( ( false == FreeFlyModeFlag ) - && ( false == Global.CabWindowOpen ) - && ( Global.Weather == "rain:" ) ) { - if( m_rainsound.is_combined() ) { - m_rainsound.pitch( Global.Overcast - 1.0 ); + if( m_rainsound ) { + if( ( false == FreeFlyModeFlag ) + && ( false == Global.CabWindowOpen ) + && ( Global.Weather == "rain:" ) ) { + if( m_rainsound->is_combined() ) { + m_rainsound->pitch( Global.Overcast - 1.0 ); + } + m_rainsound->gain( m_rainsound->m_amplitudeoffset + m_rainsound->m_amplitudefactor * 1.f ); + m_rainsound->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + m_rainsound->stop(); } - m_rainsound - .gain( m_rainsound.m_amplitudeoffset + m_rainsound.m_amplitudefactor * 1.f ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - m_rainsound.stop(); } - if( fTachoCount >= 3.f ) { - auto const frequency { ( - true == dsbHasler.is_combined() ? - fTachoVelocity * 0.01 : - dsbHasler.m_frequencyoffset + dsbHasler.m_frequencyfactor ) }; - dsbHasler - .pitch( frequency ) - .gain( dsbHasler.m_amplitudeoffset + dsbHasler.m_amplitudefactor ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else if( fTachoCount < 1.f ) { - dsbHasler.stop(); + if( dsbHasler ) { + if( fTachoCount >= 3.f ) { + auto const frequency{ ( + true == dsbHasler->is_combined() ? + fTachoVelocity * 0.01 : + dsbHasler->m_frequencyoffset + dsbHasler->m_frequencyfactor ) }; + dsbHasler->pitch( frequency ); + dsbHasler->gain( dsbHasler->m_amplitudeoffset + dsbHasler->m_amplitudefactor ); + dsbHasler->play( sound_flags::exclusive | sound_flags::looping ); + } + else if( fTachoCount < 1.f ) { + dsbHasler->stop(); + } } // power-reliant sounds if( mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable ) { // McZapkie-141102: SHP i czuwak, TODO: sygnalizacja kabinowa // hunter-091012: rozdzielenie alarmow - if( TestFlag( mvOccupied->SecuritySystem.Status, s_CAalarm ) - || TestFlag( mvOccupied->SecuritySystem.Status, s_SHPalarm ) ) { + if( dsbBuzzer ) { + if( TestFlag( mvOccupied->SecuritySystem.Status, s_CAalarm ) + || TestFlag( mvOccupied->SecuritySystem.Status, s_SHPalarm ) ) { - if( false == dsbBuzzer.is_playing() ) { - dsbBuzzer - .pitch( dsbBuzzer.m_frequencyoffset + dsbBuzzer.m_frequencyfactor ) - .gain( dsbBuzzer.m_amplitudeoffset + dsbBuzzer.m_amplitudefactor ) - .play( sound_flags::looping ); - Console::BitsSet( 1 << 14 ); // ustawienie bitu 16 na PoKeys + if( false == dsbBuzzer->is_playing() ) { + dsbBuzzer->pitch( dsbBuzzer->m_frequencyoffset + dsbBuzzer->m_frequencyfactor ); + dsbBuzzer->gain( dsbBuzzer->m_amplitudeoffset + dsbBuzzer->m_amplitudefactor ); + dsbBuzzer->play( sound_flags::looping ); + Console::BitsSet( 1 << 14 ); // ustawienie bitu 16 na PoKeys + } } - } - else { - if( true == dsbBuzzer.is_playing() ) { - dsbBuzzer.stop(); - Console::BitsClear( 1 << 14 ); // ustawienie bitu 16 na PoKeys + else { + if( true == dsbBuzzer->is_playing() ) { + dsbBuzzer->stop(); + Console::BitsClear( 1 << 14 ); // ustawienie bitu 16 na PoKeys + } } } // distance meter alert - auto const *owner { ( - DynamicObject->ctOwner != nullptr ? - DynamicObject->ctOwner : - DynamicObject->Mechanik ) }; - if( m_distancecounter > owner->fLength ) { - // play assigned sound if the train travelled its full length since meter activation - // TBD: check all combinations of directions and active cab - m_distancecounter = -1.f; // turn off the meter after its task is done - m_distancecounterclear - .pitch( m_distancecounterclear.m_frequencyoffset + m_distancecounterclear.m_frequencyfactor ) - .gain( m_distancecounterclear.m_amplitudeoffset + m_distancecounterclear.m_amplitudefactor ) - .play( sound_flags::exclusive ); + if( m_distancecounterclear ) { + auto const *owner{ ( + DynamicObject->ctOwner != nullptr ? + DynamicObject->ctOwner : + DynamicObject->Mechanik ) }; + if( m_distancecounter > owner->fLength ) { + // play assigned sound if the train travelled its full length since meter activation + // TBD: check all combinations of directions and active cab + m_distancecounter = -1.f; // turn off the meter after its task is done + m_distancecounterclear->pitch( m_distancecounterclear->m_frequencyoffset + m_distancecounterclear->m_frequencyfactor ); + m_distancecounterclear->gain( m_distancecounterclear->m_amplitudeoffset + m_distancecounterclear->m_amplitudefactor ); + m_distancecounterclear->play( sound_flags::exclusive ); + } } } else { // stop power-reliant sounds if power is cut - if( true == dsbBuzzer.is_playing() ) { - dsbBuzzer.stop(); - Console::BitsClear( 1 << 14 ); // ustawienie bitu 16 na PoKeys + if( dsbBuzzer ) { + if( true == dsbBuzzer->is_playing() ) { + dsbBuzzer->stop(); + Console::BitsClear( 1 << 14 ); // ustawienie bitu 16 na PoKeys + } + } + if( m_distancecounterclear ) { + m_distancecounterclear->stop(); } - m_distancecounterclear.stop(); } update_sounds_radio(); @@ -7784,12 +7818,14 @@ void TTrain::update_sounds_radio() { message.second->gain( volume ); } // radiostop - if( ( true == radioenabled ) - && ( true == mvOccupied->RadioStopFlag ) ) { - m_radiostop.play( sound_flags::exclusive | sound_flags::looping ); - } - else { - m_radiostop.stop(); + if( m_radiostop ) { + if( ( true == radioenabled ) + && ( true == mvOccupied->RadioStopFlag ) ) { + m_radiostop->play( sound_flags::exclusive | sound_flags::looping ); + } + else { + m_radiostop->stop(); + } } } @@ -7832,10 +7868,43 @@ bool TTrain::CabChange(int iDirection) // wczytywanie pliku z danymi multimedialnymi (dzwieki, kontrolki, kabiny) bool TTrain::LoadMMediaFile(std::string const &asFileName) { + // initialize sounds so potential entries from previous vehicle don't stick around + std::unordered_map< + std::string, + std::tuple &, sound_placement, float, sound_type, int, double>> + internalsounds = { + {"ctrl:", {dsbNastawnikJazdy, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"ctrlscnd:", {dsbNastawnikBocz, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"reverserkey:", {dsbReverserKey, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"buzzer:", {dsbBuzzer, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"radiostop:", {m_radiostop, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"slipalarm:", {dsbSlipAlarm, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"distancecounter:", {m_distancecounterclear, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"tachoclock:", {dsbHasler, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"switch:", {dsbSwitch, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"pneumaticswitch:", {dsbPneumaticSwitch, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"airsound:", {rsHiss, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}}, + {"airsound2:", {rsHissU, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}}, + {"airsound3:", {rsHissE, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}}, + {"airsound4:", {rsHissX, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}}, + {"airsound5:", {rsHissT, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}}, + {"localbrakesound:", {rsSBHiss, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}}, + {"localbrakesound2:", {rsSBHissU, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}}, + {"brakesound:", {rsBrake, sound_placement::internal, -1, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, 100.0}}, + {"fadesound:", {rsFadeSound, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}}, + {"runningnoise:", {rsRunningNoise, sound_placement::internal, EU07_SOUND_GLOBALRANGE, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, mvOccupied->Vmax }}, + {"huntingnoise:", {rsHuntingNoise, sound_placement::internal, EU07_SOUND_GLOBALRANGE, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, mvOccupied->Vmax }}, + {"rainsound:", {m_rainsound, sound_placement::internal, -1, sound_type::single, 0, 100.0}}, + }; + for( auto &soundconfig : internalsounds ) { + std::get &>( soundconfig.second ).reset(); + } + // NOTE: since radiosound is an incomplete template not using std::optional it gets a special treatment + m_radiosound.owner( DynamicObject ); + cParser parser( asFileName, cParser::buffer_FILE, DynamicObject->asBaseDir ); // NOTE: yaml-style comments are disabled until conflict in use of # is resolved // parser.addCommentStyle( "#", "\n" ); - std::string token; do { @@ -7844,145 +7913,56 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) parser >> token; } while ((token != "") && (token != "internaldata:")); - if (token == "internaldata:") - { - do - { + if (token == "internaldata:") { + + do { token = ""; parser.getTokens(); parser >> token; - // SEKCJA DZWIEKOW - if (token == "ctrl:") { - // nastawnik: - dsbNastawnikJazdy.deserialize( parser, sound_type::single ); - dsbNastawnikJazdy.owner( DynamicObject ); - } - else if (token == "ctrlscnd:") { - // hunter-081211: nastawnik bocznikowania - dsbNastawnikBocz.deserialize( parser, sound_type::single ); - dsbNastawnikBocz.owner( DynamicObject ); - } - else if (token == "reverserkey:") { - // hunter-131211: dzwiek kierunkowego - dsbReverserKey.deserialize( parser, sound_type::single ); - dsbReverserKey.owner( DynamicObject ); - } - else if (token == "buzzer:") { - // bzyczek shp: - dsbBuzzer.deserialize( parser, sound_type::single ); - dsbBuzzer.owner( DynamicObject ); - } - else if( token == "radiostop:" ) { - // radiostop - m_radiostop.deserialize( parser, sound_type::single ); - m_radiostop.owner( DynamicObject ); - } - else if (token == "slipalarm:") { - // Bombardier 011010: alarm przy poslizgu: - dsbSlipAlarm.deserialize( parser, sound_type::single ); - dsbSlipAlarm.owner( DynamicObject ); - } - else if (token == "distancecounter:") { - // distance meter 'good to go' sound - m_distancecounterclear.deserialize( parser, sound_type::single ); - m_distancecounterclear.owner( DynamicObject ); - } - else if (token == "tachoclock:") { - // cykanie rejestratora: - dsbHasler.deserialize( parser, sound_type::single ); - dsbHasler.owner( DynamicObject ); - } - else if (token == "switch:") { - // przelaczniki: - dsbSwitch.deserialize( parser, sound_type::single ); - dsbSwitch.owner( DynamicObject ); - } - else if (token == "pneumaticswitch:") { - // stycznik EP: - dsbPneumaticSwitch.deserialize( parser, sound_type::single ); - dsbPneumaticSwitch.owner( DynamicObject ); - } - else if (token == "airsound:") { - // syk: - rsHiss.deserialize( parser, sound_type::single, sound_parameters::amplitude ); - rsHiss.owner( DynamicObject ); - if( true == rsSBHiss.empty() ) { - // fallback for vehicles without defined local brake hiss sound - rsSBHiss = rsHiss; - } - } - else if (token == "airsound2:") { - // syk: - rsHissU.deserialize( parser, sound_type::single, sound_parameters::amplitude ); - rsHissU.owner( DynamicObject ); - if( true == rsSBHissU.empty() ) { - // fallback for vehicles without defined local brake hiss sound - rsSBHissU = rsHissU; - } - } - else if (token == "airsound3:") { - // syk: - rsHissE.deserialize( parser, sound_type::single, sound_parameters::amplitude ); - rsHissE.owner( DynamicObject ); - } - else if (token == "airsound4:") { - // syk: - rsHissX.deserialize( parser, sound_type::single, sound_parameters::amplitude ); - rsHissX.owner( DynamicObject ); - } - else if (token == "airsound5:") { - // syk: - rsHissT.deserialize( parser, sound_type::single, sound_parameters::amplitude ); - rsHissT.owner( DynamicObject ); - } - else if (token == "localbrakesound:") { - // syk: - rsSBHiss.deserialize( parser, sound_type::single, sound_parameters::amplitude ); - rsSBHiss.owner( DynamicObject ); - } - else if( token == "localbrakesound2:" ) { - // syk: - rsSBHissU.deserialize( parser, sound_type::single, sound_parameters::amplitude ); - rsSBHissU.owner( DynamicObject ); - } - else if( token == "brakesound:" ) { - // the sound of vehicle body vibrations etc, when brakes are engaged - rsBrake.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency ); - rsBrake.owner( DynamicObject ); - // NOTE: can't pre-calculate amplitude normalization based on max brake force, as this varies depending on vehicle speed - rsBrake.m_frequencyfactor /= ( 1 + mvOccupied->Vmax ); - } - else if (token == "fadesound:") { - // ambient sound: - rsFadeSound.deserialize( parser, sound_type::single ); - rsFadeSound.owner( DynamicObject ); - } - else if( token == "runningnoise:" ) { - // szum podczas jazdy: - rsRunningNoise.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, mvOccupied->Vmax ); - rsRunningNoise.owner( DynamicObject ); - rsRunningNoise.m_frequencyfactor /= ( 1 + mvOccupied->Vmax ); - } - else if( token == "huntingnoise:" ) { - // hunting oscillation sound: - rsHuntingNoise.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, mvOccupied->Vmax ); - rsHuntingNoise.owner( DynamicObject ); - rsHuntingNoise.m_frequencyfactor /= ( 1 + mvOccupied->Vmax ); - } - else if( token == "rainsound:" ) { - // precipitation sound: - m_rainsound.deserialize( parser, sound_type::single ); - m_rainsound.owner( DynamicObject ); - } - } while (token != ""); + auto lookup { internalsounds.find( token ) }; + if( lookup == internalsounds.end() ) { continue; } + + auto const soundconfig { lookup->second }; + sound_source sound { + std::get( soundconfig ), + std::get( soundconfig ) }; + sound.deserialize( + parser, + std::get( soundconfig ), + std::get( soundconfig ), + std::get( soundconfig ) ); + sound.owner( DynamicObject ); + std::get &>( soundconfig ) = sound; + } while( token != "" ); + + + // assign default samples to sound emitters which weren't included in the config file + if( !m_rainsound ) { + sound_source rainsound; + rainsound.deserialize( "rainsound_default", sound_type::single ); + rainsound.owner( DynamicObject ); + m_rainsound = rainsound; + } + if (!rsSBHiss) { + // fallback for vehicles without defined local brake hiss sound + rsSBHiss = rsHiss; + } + if (!rsSBHissU) { + // fallback for vehicles without defined local brake hiss sound + rsSBHissU = rsHissU; + } + if (rsBrake) { + rsBrake->m_frequencyfactor /= (1 + mvOccupied->Vmax); + } + if (rsRunningNoise) { + rsRunningNoise->m_frequencyfactor /= (1 + mvOccupied->Vmax); + } + if (rsHuntingNoise) { + rsHuntingNoise->m_frequencyfactor /= (1 + mvOccupied->Vmax); + } } -/* - else - { - return false; - } // nie znalazl sekcji internal -*/ + return true; } @@ -7991,19 +7971,21 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) m_controlmapper.clear(); // clear python screens m_screens.clear(); - // reset sound positions and owner + // reset sound positions auto const nullvector { glm::vec3() }; - std::vector sounds = { - &dsbReverserKey, &dsbNastawnikJazdy, &dsbNastawnikBocz, - &dsbSwitch, &dsbPneumaticSwitch, - &rsHiss, &rsHissU, &rsHissE, &rsHissX, &rsHissT, &rsSBHiss, &rsSBHissU, - &rsFadeSound, &rsRunningNoise, &rsHuntingNoise, - &dsbHasler, &dsbBuzzer, &dsbSlipAlarm, &m_distancecounterclear, &m_radiosound, &m_radiostop + std::vector>> sounds = { + dsbReverserKey, dsbNastawnikJazdy, dsbNastawnikBocz, + dsbSwitch, dsbPneumaticSwitch, + rsHiss, rsHissU, rsHissE, rsHissX, rsHissT, rsSBHiss, rsSBHissU, + rsFadeSound, rsRunningNoise, rsHuntingNoise, + dsbHasler, dsbBuzzer, dsbSlipAlarm, m_distancecounterclear, m_rainsound, m_radiostop }; - for( auto sound : sounds ) { - sound->offset( nullvector ); - sound->owner( DynamicObject ); + for( auto &sound : sounds ) { + if( sound.get() ) { + sound.get()->offset( nullvector ); + } } + m_radiosound.offset( nullvector ); // reset view angles pMechViewAngle = { 0.0, 0.0 }; @@ -8011,7 +7993,7 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) bool parse = false; int cabindex = 0; - DynamicObject->mdKabina = NULL; // likwidacja wskaźnika na dotychczasową kabinę + DynamicObject->mdKabina = nullptr; // likwidacja wskaźnika na dotychczasową kabinę switch (NewCabNo) { // ustalenie numeru kabiny do wczytania case -1: @@ -8038,59 +8020,8 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) token = ""; parser.getTokens(); parser >> token; -/* - if( token == "locations:" ) { - do { - token = ""; - parser.getTokens(); parser >> token; - - if( token == "radio:" ) { - // point in 3d space, in format [ x, y, z ] - glm::vec3 radiolocation; - parser.getTokens( 3, false, "\n\r\t ,;[]" ); - parser - >> radiolocation.x - >> radiolocation.y - >> radiolocation.z; - radiolocation *= glm::vec3( NewCabNo, 1, NewCabNo ); - m_radiosound.offset( radiolocation ); - } - - else if( token == "alerter:" ) { - // point in 3d space, in format [ x, y, z ] - glm::vec3 alerterlocation; - parser.getTokens( 3, false, "\n\r\t ,;[]" ); - parser - >> alerterlocation.x - >> alerterlocation.y - >> alerterlocation.z; - alerterlocation *= glm::vec3( NewCabNo, 1, NewCabNo ); - dsbBuzzer.offset( alerterlocation ); - } - - } while( ( token != "" ) - && ( token != "endlocations" ) ); - - } // locations: -*/ } while ((token != "") && (token != cabstr)); -/* - if ((cabindex != 1) && (token != cabstr)) - { - // jeśli nie znaleziony wpis kabiny, próba szukania kabiny 1 - cabstr = "cab1definition:"; - // crude way to start parsing from beginning - parser = std::make_shared(asFileName, cParser::buffer_FILE); - // NOTE: yaml-style comments are disabled until conflict in use of # is resolved - // parser.addCommentStyle( "#", "\n" ); - do - { - token = ""; - parser.getTokens(); - parser >> token; - } while ((token != "") && (token != cabstr)); - } -*/ + if (token == cabstr) { // jeśli znaleziony wpis kabiny @@ -8288,63 +8219,53 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) if (DynamicObject->mdKabina) { */ - // assign default samples to sound emitters which weren't included in the config file - if( m_rainsound.empty() ) { - m_rainsound.deserialize( "rainsound_default", sound_type::single ); - m_rainsound.owner( DynamicObject ); - } // configure placement of sound emitters which aren't bound with any device model, and weren't placed manually - // try first to bind sounds to location of possible devices - if( dsbReverserKey.offset() == nullvector ) { - dsbReverserKey.offset( ggDirKey.model_offset() ); - } - if( dsbNastawnikJazdy.offset() == nullvector ) { - dsbNastawnikJazdy.offset( ggJointCtrl.model_offset() ); - } - if( dsbNastawnikJazdy.offset() == nullvector ) { - dsbNastawnikJazdy.offset( ggMainCtrl.model_offset() ); - } - if( dsbNastawnikBocz.offset() == nullvector ) { - dsbNastawnikBocz.offset( ggScndCtrl.model_offset() ); - } - if( dsbBuzzer.offset() == nullvector ) { - dsbBuzzer.offset( btLampkaCzuwaka.model_offset() ); - } - // HACK: alerter is likely to be located somewhere near computer displays - if( m_distancecounterclear.offset() == nullvector ) { - m_distancecounterclear.offset( btLampkaCzuwaka.model_offset() ); - } - // radio has two potential items which can provide the position + auto const caboffset { glm::dvec3 { ( Cabine[ cabindex ].CabPos1 + Cabine[ cabindex ].CabPos2 ) * 0.5 } +glm::dvec3 { 0, 1, 0 } }; + // NOTE: since radiosound is an incomplete template not using std::optional it gets a special treatment if( m_radiosound.offset() == nullvector ) { m_radiosound.offset( btLampkaRadio.model_offset() ); } - if( m_radiostop.offset() == nullvector ) { - m_radiostop.offset( m_radiosound.offset() ); + if( m_radiosound.offset() == nullvector ) { + m_radiosound.offset( caboffset ); } - auto const localbrakeoffset { ggLocalBrake.model_offset() }; - std::vector localbrakesounds = { - &rsSBHiss, &rsSBHissU - }; - for( auto sound : localbrakesounds ) { - if( sound->offset() == nullvector ) { - sound->offset( localbrakeoffset ); + std::vector>, glm::vec3>> + soundlocations = { + {dsbReverserKey, ggDirKey.model_offset()}, + {dsbNastawnikJazdy, ggJointCtrl.model_offset()}, + {dsbNastawnikJazdy, ggMainCtrl.model_offset()}, // NOTE: fallback for vehicles without universal controller + {dsbNastawnikBocz, ggScndCtrl.model_offset()}, + {dsbSwitch, caboffset}, + {dsbPneumaticSwitch, caboffset}, + {rsHiss, ggBrakeCtrl.model_offset()}, + {rsHissU, ggBrakeCtrl.model_offset()}, + {rsHissE, ggBrakeCtrl.model_offset()}, + {rsHissX, ggBrakeCtrl.model_offset()}, + {rsHissT, ggBrakeCtrl.model_offset()}, + {rsSBHiss, ggLocalBrake.model_offset()}, + {rsSBHiss, ggBrakeCtrl.model_offset()}, // NOTE: fallback if the local brake model can't be located + {rsSBHissU, ggLocalBrake.model_offset()}, + {rsSBHissU, ggBrakeCtrl.model_offset()}, // NOTE: fallback if the local brake model can't be located + {rsFadeSound, caboffset}, + {rsRunningNoise, caboffset}, + {rsHuntingNoise, caboffset}, + {dsbHasler, caboffset}, + {dsbBuzzer, btLampkaCzuwaka.model_offset()}, + {dsbSlipAlarm, caboffset}, + {m_distancecounterclear, btLampkaCzuwaka.model_offset()}, + {m_rainsound, caboffset}, + {m_radiostop, m_radiosound.offset()}, + }; + for( auto & sound : soundlocations ) { + if( ( sound.first.get() ) + && ( sound.first.get()->offset() == nullvector ) ) { + sound.first.get()->offset( sound.second ); } } - // NOTE: if the local brake model can't be located the emitter will also be assigned location of main brake - auto const brakeoffset { ggBrakeCtrl.model_offset() }; - std::vector brakesounds = { - &rsHiss, &rsHissU, &rsHissE, &rsHissX, &rsHissT, &rsSBHiss, &rsSBHissU, - }; - for( auto sound : brakesounds ) { - if( sound->offset() == nullvector ) { - sound->offset( brakeoffset ); - } - } - // for whatever is left fallback on generic location, centre of the cab - auto const caboffset { glm::dvec3 { ( Cabine[ cabindex ].CabPos1 + Cabine[ cabindex ].CabPos2 ) * 0.5 } + glm::dvec3 { 0, 1, 0 } }; - for( auto sound : sounds ) { - if( sound->offset() == nullvector ) { - sound->offset( caboffset ); + // second pass, in case some items received no positioning due to missing submodels etc + for( auto & sound : soundlocations ) { + if( ( sound.first.get() ) + && ( sound.first.get()->offset() == nullvector ) ) { + sound.first.get()->offset( caboffset ); } } @@ -8521,9 +8442,13 @@ TTrain::MoveToVehicle(TDynamicObject *target) { DynamicSet(target); } - InitializeCab( - Occupied()->CabActive, - Occupied()->TypeName + ".mmd" ); + { + auto const filename { Occupied()->TypeName + ".mmd" }; + LoadMMediaFile( filename ); + InitializeCab( + Occupied()->CabActive, + filename ); + } Dynamic()->ABuSetModelShake( {} ); // zerowanie przesunięcia przed powrotem? @@ -9544,8 +9469,9 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con gauge.Load(Parser, DynamicObject); gauge.AssignFloat(&fTachoVelocityJump); // bind tachometer sound location to the meter - if( dsbHasler.offset() == glm::vec3() ) { - dsbHasler.offset( gauge.model_offset() ); + if( dsbHasler + && dsbHasler->offset() == glm::vec3() ) { + dsbHasler->offset( gauge.model_offset() ); } } else if (Label == "tachometern:") @@ -9555,8 +9481,9 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con gauge.Load(Parser, DynamicObject); gauge.AssignFloat(&fTachoVelocity); // bind tachometer sound location to the meter - if( dsbHasler.offset() == glm::vec3() ) { - dsbHasler.offset( gauge.model_offset() ); + if( dsbHasler + && dsbHasler->offset() == glm::vec3() ) { + dsbHasler->offset( gauge.model_offset() ); } } else if (Label == "tachometerd:") @@ -9566,8 +9493,9 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con gauge.Load(Parser, DynamicObject); gauge.AssignFloat(&fTachoVelocity); // bind tachometer sound location to the meter - if( dsbHasler.offset() == glm::vec3() ) { - dsbHasler.offset( gauge.model_offset() ); + if( dsbHasler + && dsbHasler->offset() == glm::vec3() ) { + dsbHasler->offset( gauge.model_offset() ); } } else if ((Label == "hvcurrent1:") || (Label == "hvcurrent1b:")) diff --git a/Train.h b/Train.h index 3fe5e4b2..604b4613 100644 --- a/Train.h +++ b/Train.h @@ -728,35 +728,33 @@ public: // reszta może by?publiczna TButton btHaslerBrakes; // ciśnienie w cylindrach TButton btHaslerCurrent; // prąd na silnikach - sound_source dsbReverserKey { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // hunter-121211 - sound_source dsbNastawnikJazdy { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; - sound_source dsbNastawnikBocz { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // hunter-081211 - sound_source dsbSwitch { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; - sound_source dsbPneumaticSwitch { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; - - sound_source rsHiss { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // upuszczanie - sound_source rsHissU { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // napelnianie - sound_source rsHissE { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // nagle - sound_source rsHissX { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // fala - sound_source rsHissT { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // czasowy - sound_source rsSBHiss { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // local - sound_source rsSBHissU { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // local, engage brakes - float m_lastlocalbrakepressure { -1.f }; // helper, cached level of pressure in local brake cylinder - float m_localbrakepressurechange { 0.f }; // recent change of pressure in local brake cylinder - sound_source rsBrake { sound_placement::internal, -1 }; - - sound_source rsFadeSound { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; - sound_source rsRunningNoise{ sound_placement::internal, EU07_SOUND_GLOBALRANGE }; - sound_source rsHuntingNoise{ sound_placement::internal, EU07_SOUND_GLOBALRANGE }; - sound_source m_rainsound { sound_placement::internal, -1 }; - - sound_source dsbHasler { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; - sound_source dsbBuzzer { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; - sound_source dsbSlipAlarm { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // Bombardier 011010: alarm przy poslizgu dla 181/182 + std::optional + dsbNastawnikJazdy, + dsbNastawnikBocz, + dsbReverserKey, + dsbBuzzer, + m_radiostop, + dsbSlipAlarm, + m_distancecounterclear, + dsbHasler, + dsbSwitch, + dsbPneumaticSwitch, + rsHiss, + rsHissU, + rsHissE, + rsHissX, + rsHissT, + rsSBHiss, + rsSBHissU, + rsBrake, + rsFadeSound, + rsRunningNoise, + rsHuntingNoise, + m_rainsound; sound_source m_radiosound { sound_placement::internal, 2 * EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // cached template for radio messages std::vector>> m_radiomessages; // list of currently played radio messages - sound_source m_radiostop { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; - sound_source m_distancecounterclear { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // distance meter 'good to go' alert + float m_lastlocalbrakepressure { -1.f }; // helper, cached level of pressure in local brake cylinder + float m_localbrakepressurechange { 0.f }; // recent change of pressure in local brake cylinder /* int iCabLightFlag; // McZapkie:120503: oswietlenie kabiny (0: wyl, 1: przyciemnione, 2: pelne) bool bCabLight; // hunter-091012: czy swiatlo jest zapalone? diff --git a/version.h b/version.h index db807e11..dede6b2d 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 426 +#define VERSION_MINOR 508 #define VERSION_REVISION 0 From 05d8af139aea0aa9b740a674a6f792da2a8e64e7 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Mon, 10 May 2021 00:50:54 +0200 Subject: [PATCH 40/63] build 210509. configurable max draw range factor, moon visualization enhancement, night lighting tweak, minor bug fixes --- Globals.cpp | 10 ++++++-- Globals.h | 5 ++-- gl/ubo.h | 9 +++---- moon.cpp | 3 ++- moon.h | 2 +- opengl33renderer.cpp | 53 +++++++++++++++++++++++++-------------- opengl33renderer.h | 2 +- openglrenderer.cpp | 41 +++++++++++++++++++----------- simulation.cpp | 2 ++ simulationenvironment.cpp | 15 ++++++++--- simulationenvironment.h | 1 + uart.cpp | 2 +- version.h | 2 +- 13 files changed, 95 insertions(+), 52 deletions(-) diff --git a/Globals.cpp b/Globals.cpp index d62fab48..0f6f1b07 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -86,8 +86,7 @@ global_settings::ConfigParse(cParser &Parser) { { Parser.getTokens(1); Parser >> BaseDrawRange; - BaseDrawRange = clamp(BaseDrawRange, 500.f, - 5000.f); // arbitrary limits to keep users from hurting themselves + BaseDrawRange = clamp(BaseDrawRange, 500.f, 5000.f); // arbitrary limits to keep users from hurting themselves } else if (token == "fullscreen") { @@ -1009,6 +1008,12 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token Parser.getTokens(1); Parser >> gfx_shadergamma; } + else if (Token == "gfx.drawrange.factor.max") + { + Parser.getTokens(1); + Parser >> gfx_distance_factor_max; + gfx_distance_factor_max = clamp(gfx_distance_factor_max, 1.f, 3.f); + } else { tokenparsed = false; @@ -1230,6 +1235,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.skippipeline", gfx_skippipeline ); export_as_text( Output, "gfx.extraeffects", gfx_extraeffects ); export_as_text( Output, "gfx.shadergamma", gfx_shadergamma ); + export_as_text( Output, "gfx.drawrange.factor.max", gfx_distance_factor_max ); export_as_text( Output, "python.enabled", python_enabled ); export_as_text( Output, "python.threadedupload", python_threadedupload ); export_as_text( Output, "python.uploadmain", python_uploadmain ); diff --git a/Globals.h b/Globals.h index 9c0f6d3b..cec2e220 100644 --- a/Globals.h +++ b/Globals.h @@ -148,8 +148,8 @@ struct global_settings { bool ResourceMove{ false }; // gfx resources are moved between cpu and gpu side instead of sending a copy bool compress_tex{ true }; // all textures are compressed on gpu side std::string asSky{ "1" }; - double fFpsAverage{ 20.0 }; // oczekiwana wartosć FPS - double fFpsDeviation{ 5.0 }; // odchylenie standardowe FPS + float fFpsAverage{ 0.f }; // oczekiwana wartosć FPS + float fFpsDeviation{ 5.f }; // odchylenie standardowe FPS double fFpsMin{ 30.0 }; // dolna granica FPS, przy której promień scenerii będzie zmniejszany double fFpsMax{ 65.0 }; // górna granica FPS, przy której promień scenerii będzie zwiększany // audio @@ -228,6 +228,7 @@ struct global_settings { bool gfx_extraeffects = true; bool gfx_shadergamma = false; bool gfx_usegles = false; + float gfx_distance_factor_max { 3.f }; std::string exec_on_exit; diff --git a/gl/ubo.h b/gl/ubo.h index 22aa2324..ee900676 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -44,10 +44,10 @@ namespace gl glm::mat4 inv_view; glm::mat4 lightview[MAX_CASCADES]; glm::vec4 cascade_end; - float time; + float time; UBS_PAD( 12 ); }; - static_assert(sizeof(scene_ubs) == 340, "bad size of ubs"); + static_assert(sizeof(scene_ubs) == 352, "bad size of ubs"); const size_t MAX_PARAMS = 3; @@ -62,8 +62,7 @@ namespace gl float emission; float fog_density; float alpha_mult; - float shadow_tone; - UBS_PAD(4); + float shadow_tone; UBS_PAD(12); void set_modelview(const glm::mat4 &mv) { @@ -72,7 +71,7 @@ namespace gl } }; - static_assert(sizeof(model_ubs) == 200 + 16 * MAX_PARAMS, "bad size of ubs"); + static_assert(sizeof(model_ubs) == 208 + 16 * MAX_PARAMS, "bad size of ubs"); struct light_element_ubs { diff --git a/moon.cpp b/moon.cpp index fd93f3ca..ad79b87c 100644 --- a/moon.cpp +++ b/moon.cpp @@ -31,11 +31,12 @@ cMoon::init() { } void -cMoon::update() { +cMoon::update( bool const Includephase ) { m_observer.temp = Global.AirTemperature; move(); + if( Includephase ) { phase(); } glm::vec3 position( 0.f, 0.f, -1.f ); position = glm::rotateX( position, glm::radians( static_cast( m_body.elevref ) ) ); position = glm::rotateY( position, glm::radians( static_cast( -m_body.hrang ) ) ); diff --git a/moon.h b/moon.h index 92fd7243..5e7e3beb 100644 --- a/moon.h +++ b/moon.h @@ -12,7 +12,7 @@ public: // methods: void init(); - void update(); + void update( bool const Includephase = false ); void render(); // returns vector pointing at the sun glm::vec3 getDirection(); diff --git a/opengl33renderer.cpp b/opengl33renderer.cpp index 5f00b30f..e2608334 100644 --- a/opengl33renderer.cpp +++ b/opengl33renderer.cpp @@ -702,7 +702,7 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); scene_ubo->update(scene_ubs); - Render(&simulation::Environment); + Render(&simulation::Environment, false); if( Global.gfx_shadowmap_enabled ) setup_shadow_bind_map(); @@ -916,7 +916,7 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) setup_shadow_unbind_map(); scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); scene_ubo->update(scene_ubs); - Render(&simulation::Environment); + Render(&simulation::Environment, Global.gfx_skippipeline); // HACK: sun/moon drawing messes up rendering with skippipeline on TODO: investigate and fix // opaque parts... setup_drawing(false); setup_shadow_bind_map(); @@ -1521,7 +1521,7 @@ void opengl33_renderer::setup_sunlight_intensity( float const Factor ) { light_ubo->update( light_ubs ); } -bool opengl33_renderer::Render(world_environment *Environment) +bool opengl33_renderer::Render(world_environment *Environment, bool const Skipcelestialbodies ) { m_shadowcolor = colors::white; // prevent shadow from affecting sky setup_shadow_color( m_shadowcolor ); @@ -1534,6 +1534,7 @@ bool opengl33_renderer::Render(world_environment *Environment) Bind_Material(null_handle); ::glDisable(GL_DEPTH_TEST); + ::glDepthMask(GL_FALSE); ::glPushMatrix(); // skydome @@ -1554,7 +1555,7 @@ bool opengl33_renderer::Render(world_environment *Environment) ::glBlendFunc( GL_SRC_ALPHA, GL_ONE ); // stars - if (Environment->m_stars.m_stars != nullptr) + if (Environment->m_stars.m_stars != nullptr && !Skipcelestialbodies ) { // setup ::glPushMatrix(); @@ -1581,6 +1582,7 @@ bool opengl33_renderer::Render(world_environment *Environment) glm::vec3 suncolor = interpolate(glm::vec3(255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f), glm::vec3(235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f), duskfactor); // sun +// if( !Skipcelestialbodies ) { Bind_Texture(0, m_suntexture); glm::vec4 color(suncolor.x, suncolor.y, suncolor.z, clamp(1.5f - Global.Overcast, 0.f, 1.f) * fogfactor); @@ -1597,6 +1599,7 @@ bool opengl33_renderer::Render(world_environment *Environment) glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } // moon + if( !Skipcelestialbodies ) { Bind_Texture(0, m_moontexture); glm::vec3 mooncolor(255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f); @@ -1668,7 +1671,8 @@ bool opengl33_renderer::Render(world_environment *Environment) clamp( Environment->m_moon.getAngle(), 0.f, 90.f ) / 90.f ); model_ubs.param[0] = color; - model_ubs.param[1] = glm::vec4(glm::vec3(modelview * glm::vec4(moonvector, 1.0f)), /*0.00451f*/ size); +// model_ubs.param[1] = glm::vec4(glm::vec3(modelview * glm::vec4(moonvector, 1.0f)), 0.00451f); + model_ubs.param[1] = glm::vec4(glm::vec3(modelview * glm::vec4(moonvector, 1.0f)), size); model_ubs.param[2] = glm::vec4(moonu, moonv, 0.333f, 0.0f); model_ubo->update(model_ubs); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); @@ -1711,6 +1715,7 @@ bool opengl33_renderer::Render(world_environment *Environment) gl::program::unbind(); gl::vao::unbind(); ::glPopMatrix(); + ::glDepthMask(GL_TRUE); ::glEnable(GL_DEPTH_TEST); m_sunlight.apply_angle(); @@ -4135,13 +4140,32 @@ void opengl33_renderer::Update(double const Deltatime) // adjust draw ranges etc, based on recent performance // TODO: it doesn't make much sense with vsync - if( Global.targetfps == 0.0f ) { - // automatic adjustment + if( Global.targetfps != 0.f ) { + auto const fps_diff = m_framerate - Global.targetfps; + if( fps_diff < -0.5f ) { + Global.fDistanceFactor = std::min( std::max( 1.0f, Global.fDistanceFactor - 0.05f ), Global.gfx_distance_factor_max ); + } + else if( fps_diff > 0.5f ) { + Global.fDistanceFactor = std::min( std::min( 3.0f, Global.fDistanceFactor + 0.05f ), Global.gfx_distance_factor_max ); + } + } + // legacy framerate parameters + else if( Global.fFpsAverage != 0.f ) { + auto const fps_diff = m_framerate - Global.fFpsAverage; + if( fps_diff < -Global.fFpsDeviation ) { + Global.fDistanceFactor = std::min( std::max( 1.0f, Global.fDistanceFactor - 0.05f ), Global.gfx_distance_factor_max ); + } + else if( fps_diff > Global.fFpsDeviation ) { + Global.fDistanceFactor = std::min( std::min( 3.0f, Global.fDistanceFactor + 0.05f ), Global.gfx_distance_factor_max ); + } + } + // automatic adjustment + else { auto const framerate = interpolate( 1000.f / Timer::subsystem.gfx_color.average(), m_framerate, 0.75f ); float targetfactor; - if( framerate > 120.0 ) { targetfactor = 3.00f; } - else if( framerate > 90.0 ) { targetfactor = 1.50f; } - else if( framerate > 60.0 ) { targetfactor = 1.25f; } + if( framerate > 120.f ) { targetfactor = std::min( Global.gfx_distance_factor_max, 3.00f ); } + else if( framerate > 90.f ) { targetfactor = std::min( Global.gfx_distance_factor_max, 1.50f ); } + else if( framerate > 60.f ) { targetfactor = std::min( Global.gfx_distance_factor_max, 1.25f ); } else { targetfactor = 1.00f; } if( targetfactor > Global.fDistanceFactor ) { Global.fDistanceFactor = std::min( targetfactor, Global.fDistanceFactor + 0.05f ); @@ -4150,15 +4174,6 @@ void opengl33_renderer::Update(double const Deltatime) Global.fDistanceFactor = std::max( targetfactor, Global.fDistanceFactor - 0.05f ); } } - else { - auto const fps_diff = Global.targetfps - m_framerate; - if( fps_diff > 0.5f ) { - Global.fDistanceFactor = std::max( 1.0f, Global.fDistanceFactor - 0.05f ); - } - else if( fps_diff < 1.0f ) { - Global.fDistanceFactor = std::min( 3.0f, Global.fDistanceFactor + 0.05f ); - } - } if( Global.UpdateMaterials ) { // update resources if there was environmental change diff --git a/opengl33renderer.h b/opengl33renderer.h index 1a05c4b9..e282545e 100644 --- a/opengl33renderer.h +++ b/opengl33renderer.h @@ -234,7 +234,7 @@ class opengl33_renderer : public gfx_renderer { void Render_pass(viewport_config &vp, rendermode const Mode); // creates dynamic environment cubemap bool Render_reflections(viewport_config &vp); - bool Render(world_environment *Environment); + bool Render(world_environment *Environment, bool const Skipcelestialbodies ); void Render(scene::basic_region *Region); void Render(section_sequence::iterator First, section_sequence::iterator Last); void Render(cell_sequence::iterator First, cell_sequence::iterator Last); diff --git a/openglrenderer.cpp b/openglrenderer.cpp index d897fac3..57f3d217 100644 --- a/openglrenderer.cpp +++ b/openglrenderer.cpp @@ -4185,14 +4185,34 @@ opengl_renderer::Update( double const Deltatime ) { m_updateaccumulator = 0.0; m_framerate = 1000.f / ( Timer::subsystem.gfx_total.average() ); - // adjust draw ranges etc, based on recent performance - if( Global.targetfps == 0.0f ) { - // automatic adjustment + // adjust draw ranges etc, based on recent performance + // TODO: it doesn't make much sense with vsync + if( Global.targetfps != 0.f ) { + auto const fps_diff = m_framerate - Global.targetfps; + if( fps_diff < -0.5f ) { + Global.fDistanceFactor = std::min( std::max( 1.0f, Global.fDistanceFactor - 0.05f ), Global.gfx_distance_factor_max ); + } + else if( fps_diff > 0.5f ) { + Global.fDistanceFactor = std::min( std::min( 3.0f, Global.fDistanceFactor + 0.05f ), Global.gfx_distance_factor_max ); + } + } + // legacy framerate parameters + else if( Global.fFpsAverage != 0.f ) { + auto const fps_diff = m_framerate - Global.fFpsAverage; + if( fps_diff < -Global.fFpsDeviation ) { + Global.fDistanceFactor = std::min( std::max( 1.0f, Global.fDistanceFactor - 0.05f ), Global.gfx_distance_factor_max ); + } + else if( fps_diff > Global.fFpsDeviation ) { + Global.fDistanceFactor = std::min( std::min( 3.0f, Global.fDistanceFactor + 0.05f ), Global.gfx_distance_factor_max ); + } + } + // automatic adjustment + else { auto const framerate = interpolate( 1000.f / Timer::subsystem.gfx_color.average(), m_framerate, 0.75f ); float targetfactor; - if( framerate > 120.0 ) { targetfactor = 3.00f; } - else if( framerate > 90.0 ) { targetfactor = 1.50f; } - else if( framerate > 60.0 ) { targetfactor = 1.25f; } + if( framerate > 120.f ) { targetfactor = std::min( Global.gfx_distance_factor_max, 3.00f ); } + else if( framerate > 90.f ) { targetfactor = std::min( Global.gfx_distance_factor_max, 1.50f ); } + else if( framerate > 60.f ) { targetfactor = std::min( Global.gfx_distance_factor_max, 1.25f ); } else { targetfactor = 1.00f; } if( targetfactor > Global.fDistanceFactor ) { Global.fDistanceFactor = std::min( targetfactor, Global.fDistanceFactor + 0.05f ); @@ -4201,15 +4221,6 @@ opengl_renderer::Update( double const Deltatime ) { Global.fDistanceFactor = std::max( targetfactor, Global.fDistanceFactor - 0.05f ); } } - else { - auto const fps_diff = Global.targetfps - m_framerate; - if( fps_diff > 0.5f ) { - Global.fDistanceFactor = std::max( 1.0f, Global.fDistanceFactor - 0.05f ); - } - else if( fps_diff < 1.0f ) { - Global.fDistanceFactor = std::min( 3.0f, Global.fDistanceFactor + 0.05f ); - } - } if( Global.UpdateMaterials ) { // update resources if there was environmental change diff --git a/simulation.cpp b/simulation.cpp index b5e13531..9a1bad0f 100644 --- a/simulation.cpp +++ b/simulation.cpp @@ -314,6 +314,8 @@ void state_manager::process_commands() { // HACK: force re-calculation of precipitation Global.Overcast = clamp( Global.Overcast - 0.0001f, 0.0f, 2.0f ); } + + simulation::Environment.update_moon(); } if (commanddata.command == user_command::setweather) { diff --git a/simulationenvironment.cpp b/simulationenvironment.cpp index 6969663e..82406eb0 100644 --- a/simulationenvironment.cpp +++ b/simulationenvironment.cpp @@ -126,7 +126,7 @@ world_environment::update() { Global.DayLight.position = m_moon.getDirection(); Global.DayLight.direction = -1.0f * m_moon.getDirection(); keylightintensity = moonlightlevel; - m_lightintensity = 0.35f; + m_lightintensity = moonlightlevel; // if the moon is up, it overrides the twilight twilightfactor = 0.0f; keylightcolor = glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 202.0f / 255.0f ); @@ -165,9 +165,10 @@ world_environment::update() { // tonal impact of skydome color is inversely proportional to how high the sun is above the horizon // (this is pure conjecture, aimed more to 'look right' than be accurate) float const ambienttone = clamp( 1.0f - ( Global.SunAngle / 90.0f ), 0.0f, 1.0f ); - Global.DayLight.ambient[ 0 ] = interpolate( skydomehsv.z, skydomecolour.r, ambienttone ); - Global.DayLight.ambient[ 1 ] = interpolate( skydomehsv.z, skydomecolour.g, ambienttone ); - Global.DayLight.ambient[ 2 ] = interpolate( skydomehsv.z, skydomecolour.b, ambienttone ); + float const ambientintensitynightfactor = 1.f - 0.75f * clamp( -m_sun.getAngle(), 0.0f, 18.0f ) / 18.0f; + Global.DayLight.ambient[ 0 ] = interpolate( skydomehsv.z, skydomecolour.r, ambienttone ) * ambientintensitynightfactor; + Global.DayLight.ambient[ 1 ] = interpolate( skydomehsv.z, skydomecolour.g, ambienttone ) * ambientintensitynightfactor; + Global.DayLight.ambient[ 2 ] = interpolate( skydomehsv.z, skydomecolour.b, ambienttone ) * ambientintensitynightfactor; Global.fLuminance = intensity; @@ -210,6 +211,12 @@ world_environment::update_precipitation() { m_precipitation.update(); } +void +world_environment::update_moon() { + + m_moon.update( true ); +} + void world_environment::update_wind() { diff --git a/simulationenvironment.h b/simulationenvironment.h index a146ddf8..cc33a869 100644 --- a/simulationenvironment.h +++ b/simulationenvironment.h @@ -31,6 +31,7 @@ public: void init(); void update(); void update_precipitation(); + void update_moon(); void time( int const Hour = -1, int const Minute = -1, int const Second = -1 ); // switches between static and dynamic daylight calculation void on_daylight_change(); diff --git a/uart.cpp b/uart.cpp index d2900aef..3ce34458 100644 --- a/uart.cpp +++ b/uart.cpp @@ -199,7 +199,7 @@ void uart_input::poll() if (!sync) { int sync_cnt = 0; int sync_fail = 0; - char sc = 0; + unsigned char sc = 0; while ((ret = sp_blocking_read(port, &sc, 1, 10)) >= 0) { if (conf.debug) WriteLog("uart: read byte: " + std::to_string((int)sc)); diff --git a/version.h b/version.h index dede6b2d..ae0dccc8 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 508 +#define VERSION_MINOR 509 #define VERSION_REVISION 0 From 8a8e8d9c61137e95243c1144e286491614170fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Mon, 17 May 2021 16:53:02 +0200 Subject: [PATCH 41/63] AI driver has more histeresis when using ep brake --- Driver.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Driver.cpp b/Driver.cpp index 28186853..f9c3c56d 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -7730,8 +7730,11 @@ void TController::control_braking_force() { fAccGravity < 0.025 ? // HACK: when going downhill be more responsive to desired deceleration fAccThreshold : std::max( -0.2, fAccThreshold ) ) }; + auto const AccMax{ std::min(fBrake_a0[0] + 12 * fBrake_a1[0], mvOccupied->MED_amax) }; + auto const accmargin = ((AccMax > 1.1 * AccDesired) && (fAccGravity < 0.025)) ? + 0.05 : 0.0; if( ( AccDesired < accthreshold ) // jeśli hamować - u góry ustawia się hamowanie na fAccThreshold - && ( ( AbsAccS > AccDesired ) + && ( ( AbsAccS > AccDesired + accmargin) || ( BrakeCtrlPosition < 0 ) ) ) { // hamować bardziej, gdy aktualne opóźnienie hamowania mniejsze niż (AccDesired) cue_action( locale::string::driver_hint_brakingforceincrease ); From 07d70ed401ab8965b41feb1c359f06ddf435e2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Wed, 19 May 2021 21:48:02 +0200 Subject: [PATCH 42/63] Changed acceleration conditions for AI driver --- Driver.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index f9c3c56d..051c110a 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -7511,7 +7511,11 @@ TController::adjust_desired_speed_for_current_speed() { } } // HACK: limit acceleration for cargo trains, to reduce probability of breaking couplers on sudden jolts + auto MaxAcc{ 0.5 * (mvOccupied->Couplers[mvOccupied->DirAbsolute >= 0 ? 1 : 0].FmaxC) / fMass }; + MaxAcc *= clamp(vel * 0.2, 0.2, 1.0); + AccDesired = std::min(AccDesired, clamp(MaxAcc, HeavyCargoTrainAcceleration, AccPreferred)); // TBD: expand this behaviour to all trains with car(s) exceeding certain weight? + /* if( ( IsPassengerTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) { AccDesired = std::min( AccDesired, ( iVehicles - ControlledEnginesCount > 8 ? HeavyPassengetTrainAcceleration : PassengetTrainAcceleration ) ); } @@ -7520,7 +7524,7 @@ TController::adjust_desired_speed_for_current_speed() { } if( ( IsHeavyCargoTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) { AccDesired = std::min( AccDesired, HeavyCargoTrainAcceleration ); - } + } */ } else { // for cars the older version works better @@ -7644,7 +7648,7 @@ void TController::control_tractive_force() { auto const velocity { DirectionalVel() }; // jeśli przyspieszenie pojazdu jest mniejsze niż żądane oraz... if( ( AccDesired > EU07_AI_NOACCELERATION ) // don't add power if not asked for actual speed-up - && ( AbsAccS < AccDesired /* - 0.05 */ ) + && (( AbsAccS < AccDesired /* - 0.05 */ ) || (mvOccupied->SpeedCtrlUnit.IsActive && velocity < mvOccupied->SpeedCtrlUnit.FullPowerVelocity)) && ( false == TestFlag( iDrivigFlags, movePress ) ) ) { // ...jeśli prędkość w kierunku czoła jest mniejsza od dozwolonej o margines... if( velocity < ( @@ -7674,7 +7678,7 @@ void TController::control_tractive_force() { else if( ( velocity > VelDesired + SpeedCtrlMargin) || ( fAccGravity < -0.01 ? AccDesired < 0.0 : - ( AbsAccS > AccDesired + 0.05 ) ) + ( AbsAccS > AccDesired + 10.05 ) ) || ( IsAnyCouplerStretched ) ) { // jak za bardzo przyspiesza albo prędkość przekroczona // dodany wyjatek na "pelna w przod" From 873364d4052d6ce2ca0fa9638b4020cb984d0ba6 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Wed, 19 May 2021 21:57:41 +0200 Subject: [PATCH 43/63] security system magnet interaction enhancement, vehicle startup check tweak --- Classes.h | 1 + Driver.cpp | 40 +++++++++++++++++++++++++++++++++++++++- Event.cpp | 4 ++++ McZapkie/MOVER.h | 2 ++ McZapkie/Mover.cpp | 29 +++++++++++++++++------------ MemCell.cpp | 4 ++++ 6 files changed, 67 insertions(+), 13 deletions(-) diff --git a/Classes.h b/Classes.h index 9145e739..284b1aad 100644 --- a/Classes.h +++ b/Classes.h @@ -78,6 +78,7 @@ enum class TCommandType cm_OutsideStation, // cm_Shunt, // unused? cm_EmergencyBrake, + cm_SecuritySystemMagnet, cm_Command // komenda pobierana z komórki }; diff --git a/Driver.cpp b/Driver.cpp index e968e086..71903b69 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -271,6 +271,9 @@ void TSpeedPos::CommandCheck() case TCommandType::cm_EmergencyBrake: fVelNext = -1; break; + case TCommandType::cm_SecuritySystemMagnet: + fVelNext = -1; + break; default: // inna komenda w evencie skanowanym powoduje zatrzymanie i wysłanie tej komendy // nie manewrowa, nie przystanek, nie zatrzymać na SBL @@ -880,6 +883,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN eSignNext = nullptr; IsAtPassengerStop = false; IsScheduledPassengerStopVisible = false; + mvOccupied->SecuritySystem.SHPLock = false; // te flagi są ustawiane tutaj, w razie potrzeby iDrivigFlags &= ~(moveTrackEnd | moveSwitchFound | moveSemaphorFound | /*moveSpeedLimitFound*/ moveStopPointFound ); @@ -1404,9 +1408,29 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo SemNextStopIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód } switch( Point.evEvent->input_command() ) { + // TBD, TODO: expand emergency_brake handling to a more generic security system signal case TCommandType::cm_EmergencyBrake: { pVehicle->RadioStop(); Point.Clear(); // signal received, deactivate + return true; + } + case TCommandType::cm_SecuritySystemMagnet: { + // NOTE: magnet induction calculation presumes the driver is located in the front vehicle + // TBD, TODO: take into account actual position of controlled/occupied vehicle in the consist, whichever comes first + auto const magnetlocation { pVehicles[ end::front ]->MoverParameters->SecuritySystem.MagnetLocation }; + if( Point.fDist < -( magnetlocation ) ) { + mvOccupied->SecuritySystem.SHPLock |= ( !AIControllFlag ); // don't make life difficult for the ai, but a human driver is a fair game + PutCommand( + Point.evEvent->input_text(), + Point.evEvent->input_value( 1 ), + Point.evEvent->input_value( 2 ), + nullptr ); + } + if( Point.fDist < -( magnetlocation + 0.5 ) ) { + Point.Clear(); // magnet passed, deactivate + mvOccupied->SecuritySystem.SHPLock = false; + } + return true; } default: { break; @@ -1415,6 +1439,19 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo } // check signals ahead if( Point.fDist > 0.0 ) { + // bail out early for signals which activate when passed + // TBD: make it an event method? + switch( Point.evEvent->input_command() ) { + case TCommandType::cm_EmergencyBrake: { + return true; + } + case TCommandType::cm_SecuritySystemMagnet: { + return true; + } + default: { + break; + } + } if( Point.IsProperSemaphor( OrderCurrentGet() ) ) { // special rule for cars: ignore stop signals at distance too short to come to a stop @@ -2793,7 +2830,8 @@ bool TController::PrepareEngine() && ( true == IsAnyConverterEnabled ) && ( true == IsAnyCompressorEnabled ) && ( ( mvControlling->ScndPipePress > 4.5 ) || ( mvControlling->VeselVolume == 0.0 ) ) - && ( ( mvOccupied->fBrakeCtrlPos == mvOccupied->Handle->GetPos( bh_RP ) || ( mvOccupied->BrakeHandle == TBrakeHandle::NoHandle ) ) ); + && ( ( static_cast( mvOccupied->fBrakeCtrlPos ) == static_cast( mvOccupied->Handle->GetPos( bh_RP ) ) ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::NoHandle ) ); } if( true == isready ) { diff --git a/Event.cpp b/Event.cpp index f2c7cd8a..d2bf4763 100644 --- a/Event.cpp +++ b/Event.cpp @@ -740,6 +740,10 @@ putvalues_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) 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; } diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index f6b16f6c..c3dc62b0 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -664,6 +664,8 @@ struct TSecuritySystem int NextVelocityAllowed; /*predkosc pokazywana przez sygnalizacje kabinowa*/ bool RadioStop; // czy jest RadioStop bool PoweredUp { false }; // helper, for detection of power state change + bool SHPLock{ false }; // prevents tps reset when true + double MagnetLocation { 0.0 }; // placement of tps magnet, as offset from vehicle front inline bool is_beeping() const { return TestFlag( Status, s_SHPalarm ); diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index eb29e1e2..80b7df23 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -2664,6 +2664,7 @@ bool TMoverParameters::CabDeactivisation( bool const Enforce ) DirAbsolute = DirActive * CabActive; DepartureSignal = false; // nie buczeć z nieaktywnej kabiny SecuritySystem.Status = s_off; // deactivate alerter TODO: make it part of control based cab selection + SecuritySystem.SHPLock = false; SendCtrlToNext("CabActivisation", 0, CabOccupied); // CabActive==0! } @@ -2827,18 +2828,18 @@ void TMoverParameters::SSReset(void) SetFlag(SecuritySystem.Status, -s_aware); SetFlag(SecuritySystem.Status, -s_CAalarm); SetFlag(SecuritySystem.Status, -s_CAebrake); - // EmergencyBrakeFlag = false; //YB-HN SecuritySystem.VelocityAllowed = -1; } else if (TestFlag(SecuritySystem.Status, s_active)) { - SecuritySystem.SystemBrakeSHPTimer = 0; - SecuritySystem.SystemSoundSHPTimer = 0; - SetFlag(SecuritySystem.Status, -s_active); - SetFlag(SecuritySystem.Status, -s_SHPalarm); - SetFlag(SecuritySystem.Status, -s_SHPebrake); - // EmergencyBrakeFlag = false; //YB-HN - SecuritySystem.VelocityAllowed = -1; + if( false == SecuritySystem.SHPLock ) { + SecuritySystem.SystemBrakeSHPTimer = 0; + SecuritySystem.SystemSoundSHPTimer = 0; + SetFlag( SecuritySystem.Status, -s_active ); + SetFlag( SecuritySystem.Status, -s_SHPalarm ); + SetFlag( SecuritySystem.Status, -s_SHPebrake ); + SecuritySystem.VelocityAllowed = -1; + } } } @@ -10542,7 +10543,7 @@ void TMoverParameters::LoadFIZ_Security( std::string const &line ) { extract_value( SecuritySystem.SoundSignalDelay, "SoundSignalDelay", line, "" ); extract_value( SecuritySystem.EmergencyBrakeDelay, "EmergencyBrakeDelay", line, "" ); extract_value( SecuritySystem.RadioStop, "RadioStop", line, "" ); - + extract_value( SecuritySystem.MagnetLocation, "MagnetLocation", line, "" ); extract_value( EmergencyBrakeWarningSignal, "EmergencyBrakeWarningSignal", line, "" ); } @@ -11487,9 +11488,6 @@ bool TMoverParameters::CheckLocomotiveParameters(bool ReadyFlag, int Dir) BrakeDelay[b] = BrakeDelay[b] * (2.5 + Random(0.0, 0.2)) / 3.0; } - if (TrainType == dt_ET22) - CompressorPower = 0; - Hamulec->Init(PipePress, HighPipePress, LowPipePress, BrakePress, BrakeDelayFlag); /* ScndPipePress = Compressor; @@ -11505,6 +11503,13 @@ bool TMoverParameters::CheckLocomotiveParameters(bool ReadyFlag, int Dir) } } } + + // security system + // by default place the magnet in the vehicle centre + if( SecuritySystem.MagnetLocation == 0 ) { + SecuritySystem.MagnetLocation = Dim.L / 2 - 0.25; + } + SecuritySystem.MagnetLocation = clamp( SecuritySystem.MagnetLocation, 0.0, Dim.L ); return OK; } diff --git a/MemCell.cpp b/MemCell.cpp index f0cd8bca..751543fb 100644 --- a/MemCell.cpp +++ b/MemCell.cpp @@ -86,6 +86,10 @@ TCommandType TMemCell::CommandCheck() eCommand = TCommandType::cm_EmergencyBrake; bCommand = false; } + else if( szText == "CabSignal" ) { + eCommand = TCommandType::cm_SecuritySystemMagnet; + bCommand = false; + } else { eCommand = TCommandType::cm_Unknown; // ciąg nierozpoznany (nie jest komendą) From 696251d71a411e576172f13a8393c8ab34a077c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Mon, 17 May 2021 16:53:02 +0200 Subject: [PATCH 44/63] AI driver has more histeresis when using ep brake --- Driver.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Driver.cpp b/Driver.cpp index 71903b69..6e3ab20a 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -7768,8 +7768,11 @@ void TController::control_braking_force() { fAccGravity < 0.025 ? // HACK: when going downhill be more responsive to desired deceleration fAccThreshold : std::max( -0.2, fAccThreshold ) ) }; + auto const AccMax{ std::min(fBrake_a0[0] + 12 * fBrake_a1[0], mvOccupied->MED_amax) }; + auto const accmargin = ((AccMax > 1.1 * AccDesired) && (fAccGravity < 0.025)) ? + 0.05 : 0.0; if( ( AccDesired < accthreshold ) // jeśli hamować - u góry ustawia się hamowanie na fAccThreshold - && ( ( AbsAccS > AccDesired ) + && ( ( AbsAccS > AccDesired + accmargin) || ( BrakeCtrlPosition < 0 ) ) ) { // hamować bardziej, gdy aktualne opóźnienie hamowania mniejsze niż (AccDesired) cue_action( locale::string::driver_hint_brakingforceincrease ); From eaa3b647e43f376d2014525a31ed02ac3091d461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Wed, 19 May 2021 21:48:02 +0200 Subject: [PATCH 45/63] Changed acceleration conditions for AI driver --- Driver.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 6e3ab20a..89392179 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -7549,7 +7549,11 @@ TController::adjust_desired_speed_for_current_speed() { } } // HACK: limit acceleration for cargo trains, to reduce probability of breaking couplers on sudden jolts + auto MaxAcc{ 0.5 * (mvOccupied->Couplers[mvOccupied->DirAbsolute >= 0 ? 1 : 0].FmaxC) / fMass }; + MaxAcc *= clamp(vel * 0.2, 0.2, 1.0); + AccDesired = std::min(AccDesired, clamp(MaxAcc, HeavyCargoTrainAcceleration, AccPreferred)); // TBD: expand this behaviour to all trains with car(s) exceeding certain weight? + /* if( ( IsPassengerTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) { AccDesired = std::min( AccDesired, ( iVehicles - ControlledEnginesCount > 8 ? HeavyPassengetTrainAcceleration : PassengetTrainAcceleration ) ); } @@ -7558,7 +7562,7 @@ TController::adjust_desired_speed_for_current_speed() { } if( ( IsHeavyCargoTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) { AccDesired = std::min( AccDesired, HeavyCargoTrainAcceleration ); - } + } */ } else { // for cars the older version works better @@ -7682,7 +7686,7 @@ void TController::control_tractive_force() { auto const velocity { DirectionalVel() }; // jeśli przyspieszenie pojazdu jest mniejsze niż żądane oraz... if( ( AccDesired > EU07_AI_NOACCELERATION ) // don't add power if not asked for actual speed-up - && ( AbsAccS < AccDesired /* - 0.05 */ ) + && (( AbsAccS < AccDesired /* - 0.05 */ ) || (mvOccupied->SpeedCtrlUnit.IsActive && velocity < mvOccupied->SpeedCtrlUnit.FullPowerVelocity)) && ( false == TestFlag( iDrivigFlags, movePress ) ) ) { // ...jeśli prędkość w kierunku czoła jest mniejsza od dozwolonej o margines... if( velocity < ( @@ -7712,7 +7716,7 @@ void TController::control_tractive_force() { else if( ( velocity > VelDesired + SpeedCtrlMargin) || ( fAccGravity < -0.01 ? AccDesired < 0.0 : - ( AbsAccS > AccDesired + 0.05 ) ) + ( AbsAccS > AccDesired + 10.05 ) ) || ( IsAnyCouplerStretched ) ) { // jak za bardzo przyspiesza albo prędkość przekroczona // dodany wyjatek na "pelna w przod" From b4392c25b9c5f2c39b14e3c92e9c151b06630f2b Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sat, 22 May 2021 23:29:05 +0200 Subject: [PATCH 46/63] train acceleration logic tweak, train protection system magnet activation tweak --- Driver.cpp | 30 ++++++++++++++++++++---------- McZapkie/Mover.cpp | 4 ++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 89392179..47abcc7b 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -1418,16 +1418,24 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo // NOTE: magnet induction calculation presumes the driver is located in the front vehicle // TBD, TODO: take into account actual position of controlled/occupied vehicle in the consist, whichever comes first auto const magnetlocation { pVehicles[ end::front ]->MoverParameters->SecuritySystem.MagnetLocation }; + auto const magnetrange { 1.0 }; + auto const ismagnetpassed { Point.fDist < -( magnetlocation + magnetrange ) }; if( Point.fDist < -( magnetlocation ) ) { - mvOccupied->SecuritySystem.SHPLock |= ( !AIControllFlag ); // don't make life difficult for the ai, but a human driver is a fair game - PutCommand( - Point.evEvent->input_text(), - Point.evEvent->input_value( 1 ), - Point.evEvent->input_value( 2 ), - nullptr ); + // NOTE: normally we'd activate the magnet once the leading vehicle passes it + // but on a fresh scan after direction change it would be detected as long as it's under consist, and meet the (simple) activation condition + // thus we're doing a more precise check in such situation (we presume direction change takes place only if the vehicle is standing still) + if( ( mvOccupied->Vel > EU07_AI_NOMOVEMENT ) + || ( false == ismagnetpassed ) ) { + mvOccupied->SecuritySystem.SHPLock |= ( !AIControllFlag ); // don't make life difficult for the ai, but a human driver is a fair game + PutCommand( + Point.evEvent->input_text(), + Point.evEvent->input_value( 1 ), + Point.evEvent->input_value( 2 ), + nullptr ); + } } - if( Point.fDist < -( magnetlocation + 0.5 ) ) { - Point.Clear(); // magnet passed, deactivate + if( ismagnetpassed ) { + Point.Clear(); mvOccupied->SecuritySystem.SHPLock = false; } return true; @@ -7549,8 +7557,10 @@ TController::adjust_desired_speed_for_current_speed() { } } // HACK: limit acceleration for cargo trains, to reduce probability of breaking couplers on sudden jolts - auto MaxAcc{ 0.5 * (mvOccupied->Couplers[mvOccupied->DirAbsolute >= 0 ? 1 : 0].FmaxC) / fMass }; - MaxAcc *= clamp(vel * 0.2, 0.2, 1.0); + auto MaxAcc{ 0.5 * mvOccupied->Couplers[(mvOccupied->DirAbsolute >= 0 ? end::rear : end::front)].FmaxC / fMass }; + if( iVehicles - ControlledEnginesCount > 0 ) { + MaxAcc *= clamp( vel * 0.025, 0.2, 1.0 ); + } AccDesired = std::min(AccDesired, clamp(MaxAcc, HeavyCargoTrainAcceleration, AccPreferred)); // TBD: expand this behaviour to all trains with car(s) exceeding certain weight? /* diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 80b7df23..1167c093 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -11507,7 +11507,7 @@ bool TMoverParameters::CheckLocomotiveParameters(bool ReadyFlag, int Dir) // security system // by default place the magnet in the vehicle centre if( SecuritySystem.MagnetLocation == 0 ) { - SecuritySystem.MagnetLocation = Dim.L / 2 - 0.25; + SecuritySystem.MagnetLocation = Dim.L / 2 - 0.5; } SecuritySystem.MagnetLocation = clamp( SecuritySystem.MagnetLocation, 0.0, Dim.L ); @@ -12042,7 +12042,7 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C { SecuritySystem.VelocityAllowed = static_cast(floor(CValue1)); SecuritySystem.NextVelocityAllowed = static_cast(floor(CValue2)); - SecuritySystem.SystemSoundSHPTimer = 0; // hunter-091012 +// SecuritySystem.SystemSoundSHPTimer = 0; // hunter-091012 SetFlag(SecuritySystem.Status, s_active); } // else OK:=false; From 1eafc46ec9e4f974ff26077abde2c03c329f52d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Tue, 25 May 2021 20:26:16 +0200 Subject: [PATCH 47/63] randomized delay for using of security system button by AI --- Driver.h | 1 + driverhints.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Driver.h b/Driver.h index 02040691..70764f8f 100644 --- a/Driver.h +++ b/Driver.h @@ -435,6 +435,7 @@ private: double LastReactionTime = 0.0; double fActionTime = 0.0; // czas używany przy regulacji prędkości i zamykaniu drzwi double m_radiocontroltime{ 0.0 }; // timer used to control speed of radio operations + double m_securitysystemreset { 1.0 }; // timer used to control speed of security system resetting TAction eAction{ TAction::actUnknown }; // aktualny stan std::list< std::tuple > m_hints; // queued ai operations diff --git a/driverhints.cpp b/driverhints.cpp index e9e16ce0..41972773 100644 --- a/driverhints.cpp +++ b/driverhints.cpp @@ -1170,7 +1170,12 @@ TController::cue_action( locale::string const Action, float const Actionparamete case locale::string::driver_hint_securitysystemreset: { if( AIControllFlag ) { - mvOccupied->SecuritySystemReset(); + auto time{ std::max(std::max(mvOccupied->SecuritySystem.SystemSoundSHPTimer, mvOccupied->SecuritySystem.SystemSoundCATimer), + std::max(mvOccupied->SecuritySystem.SystemBrakeSHPTimer, mvOccupied->SecuritySystem.SystemBrakeCATimer)) }; + if (time > m_securitysystemreset) { + mvOccupied->SecuritySystemReset(); + m_securitysystemreset = 0.5 + Random(); + } } hint( Action, From b3940d47cb069535c1d46a15ca81b297a855791c Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Thu, 27 May 2021 13:13:18 +0200 Subject: [PATCH 48/63] minor ai heater logic tweak, vehicle preparation check tweak --- Driver.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 47abcc7b..e21cc839 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -2459,6 +2459,7 @@ bool TController::CheckVehicles(TOrders user) // with the order established the virtual train manager can do their work p = pVehicles[ end::front ]; ControlledEnginesCount = ( p->MoverParameters->Power > 1.0 ? 1 : 0 ); + auto hasheaters { false }; while (p) { if( p != pVehicle ) { @@ -2470,6 +2471,7 @@ bool TController::CheckVehicles(TOrders user) if( p->MoverParameters->HeatingPower > 0 ) { p->MoverParameters->HeatingAllow = true; p->MoverParameters->ConverterSwitch( true, range_t::local ); + hasheaters = true; } } else { @@ -2526,16 +2528,18 @@ bool TController::CheckVehicles(TOrders user) cue_action( locale::string::driver_hint_consistdoorlockson ); // potentially enable train heating { - // HACK: to account for su-45/46 shortcomings diesel-powered engines only activate heating in cold conditions - // TODO: take instead into account presence of converters in attached cars, once said presence is possible to specify auto const ispassengertrain { ( IsPassengerTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) }; - auto const isheatingcouplingactive { pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::heating ) }; + // TODO: replace connection test with connection check between last engine and first car, specifically + auto const isheatingcouplingactive { ( + ControlledEnginesCount == 1 ? + pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::heating ) : + true ) }; auto const isheatingneeded { (is_emu() || is_dmu() ? true : -// false == isheatingcouplingactive ? false : (OrderCurrentGet() & (Obey_train | Bank)) == 0 ? false : - ispassengertrain ? (has_diesel_engine() ? (Global.AirTemperature < 10) : true) : - false)}; + false == isheatingcouplingactive ? false : + ispassengertrain ? hasheaters : + false) }; if( mvControlling->HeatingAllow != isheatingneeded ) { cue_action( isheatingneeded ? @@ -2839,6 +2843,7 @@ bool TController::PrepareEngine() && ( true == IsAnyCompressorEnabled ) && ( ( mvControlling->ScndPipePress > 4.5 ) || ( mvControlling->VeselVolume == 0.0 ) ) && ( ( static_cast( mvOccupied->fBrakeCtrlPos ) == static_cast( mvOccupied->Handle->GetPos( bh_RP ) ) ) + || ( static_cast( mvOccupied->fBrakeCtrlPos ) != static_cast( mvOccupied->Handle->GetPos( bh_NP ) ) ) || ( mvOccupied->BrakeHandle == TBrakeHandle::NoHandle ) ); } From 7b816594bac668c2ef20223eade6db1202568360 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Thu, 27 May 2021 14:23:00 +0200 Subject: [PATCH 49/63] maintenance: string search methods --- AnimModel.cpp | 8 +++--- Console/PoKeys55.cpp | 2 +- Driver.cpp | 2 +- DynObj.cpp | 32 ++++++++++++------------ Event.cpp | 33 ++++++++++++------------- Globals.cpp | 2 +- McZapkie/Mover.cpp | 12 ++++----- McZapkie/Oerlikon_ESt.cpp | 16 ++++++------ MdlMngr.cpp | 2 +- Model3d.cpp | 7 +++--- Texture.cpp | 7 +++--- audio.cpp | 8 +++--- driveruipanels.cpp | 2 +- editoruipanels.cpp | 2 +- material.cpp | 3 ++- mtable.cpp | 46 +++++++++++++++++------------------ parser.cpp | 8 +++--- scene.cpp | 2 +- scenenode.cpp | 6 ++--- simulationstateserializer.cpp | 4 +-- station.cpp | 6 ++--- utilities.cpp | 13 ++++++++++ utilities.h | 3 +++ 23 files changed, 120 insertions(+), 106 deletions(-) diff --git a/AnimModel.cpp b/AnimModel.cpp index 5b184f96..d42370a5 100644 --- a/AnimModel.cpp +++ b/AnimModel.cpp @@ -453,7 +453,7 @@ bool TAnimModel::Load(cParser *parser, bool ter) { // gdy brak modelu if (ter) // jeśli teren { - if( name.substr( name.rfind( '.' ) ) == ".t3d" ) { + if( ends_with( name, ".t3d" ) ) { name[ name.length() - 3 ] = 'e'; } #ifdef EU07_USE_OLD_TERRAINCODE @@ -959,11 +959,11 @@ TAnimModel::export_as_text_( std::ostream &Output ) const { // don't include 'textures/' in the path texturefile.erase( 0, std::string{ szTexturePath }.size() ); } - if( texturefile.find( ' ' ) == std::string::npos ) { - Output << texturefile << ' '; + if( contains( texturefile, ' ' ) ) { + Output << "\"" << texturefile << "\"" << ' '; } else { - Output << "\"" << texturefile << "\"" << ' '; + Output << texturefile << ' '; } // light submodels activation configuration if( iNumLights > 0 ) { diff --git a/Console/PoKeys55.cpp b/Console/PoKeys55.cpp index abe24616..5774947b 100644 --- a/Console/PoKeys55.cpp +++ b/Console/PoKeys55.cpp @@ -145,7 +145,7 @@ bool TPoKeys55::Connect() DeviceIDFromRegistry = ToLower( DeviceIDFromRegistry ); DeviceIDToFind = ToLower( DeviceIDToFind ); // Now check if the hardware ID we are looking at contains the correct VID/PID - MatchFound = ( DeviceIDFromRegistry.find( DeviceIDToFind ) != std::string::npos ); + MatchFound = ( contains( DeviceIDFromRegistry, DeviceIDToFind ) ); if (MatchFound == true) { // Device must have been found. Open read and write handles. In order to do this,we diff --git a/Driver.cpp b/Driver.cpp index e21cc839..7eeff552 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -5130,7 +5130,7 @@ void TController::OrdersInit(double fVel) + ", " + std::to_string(t->Dh) + ":" + std::to_string(t->Dm) + " " + t->StationWare); } - if (t->StationWare.find('@') != std::string::npos) + if ( contains( t->StationWare, '@' ) ) { // zmiana kierunku i dalsza jazda wg rozk?adu if (iDrivigFlags & movePushPull) // SZT również! SN61 zależnie od wagonów... { // jeśli skład zespolony, wystarczy zmienić kierunek jazdy diff --git a/DynObj.cpp b/DynObj.cpp index 928deef6..794abab0 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -169,7 +169,7 @@ void material_data::assign( std::string const &Replacableskin ) { // check for the pipe method first - if( Replacableskin.find( '|' ) != std::string::npos ) { + if( contains( Replacableskin, '|' ) ) { cParser nameparser( Replacableskin ); nameparser.getTokens( 4, true, "|" ); int skinindex = 0; @@ -1172,7 +1172,7 @@ void TDynamicObject::ABuLittleUpdate(double ObjSqrDist) if( cabsection ) { // check whether we're still processing cab sections auto const §ionname { section.compartment->pName }; - cabsection &= ( ( sectionname.size() >= 4 ) && ( sectionname.substr( 0, 3 ) == "cab" ) ); + cabsection &= ( ( sectionname.size() >= 4 ) && ( starts_with( sectionname, "cab" ) ) ); } // TODO: add cablight devices auto const sectionlightlevel { section.light_level * ( cabsection ? 1.0f : MoverParameters->CompartmentLights.intensity ) }; @@ -1904,40 +1904,40 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" if (ActPar.substr(0, 1) == "B") // jesli hamulce { // sprawdzanie kolejno nastaw WriteLog("Wpis hamulca: " + ActPar); - if (ActPar.find('G') != std::string::npos) + if ( contains( ActPar, 'G') ) { MoverParameters->BrakeDelaySwitch(bdelay_G); } - if( ActPar.find( 'P' ) != std::string::npos ) + if( contains( ActPar, 'P' ) ) { MoverParameters->BrakeDelaySwitch(bdelay_P); } - if( ActPar.find( 'R' ) != std::string::npos ) + if( contains( ActPar, 'R' ) ) { MoverParameters->BrakeDelaySwitch(bdelay_R); } - if( ActPar.find( 'M' ) != std::string::npos ) + if( contains( ActPar, 'M' ) ) { MoverParameters->BrakeDelaySwitch(bdelay_R); MoverParameters->BrakeDelaySwitch(bdelay_R + bdelay_M); } // wylaczanie hamulca - if (ActPar.find("<>") != std::string::npos) // wylaczanie na probe hamowania naglego + if ( contains( ActPar, "<>") ) // wylaczanie na probe hamowania naglego { MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz } - if (ActPar.find('0') != std::string::npos) // wylaczanie na sztywno + if ( contains( ActPar, '0' ) ) // wylaczanie na sztywno { MoverParameters->Hamulec->ForceEmptiness(); MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz } - if (ActPar.find('E') != std::string::npos) // oprozniony + if ( contains( ActPar, 'E' ) ) // oprozniony { MoverParameters->Hamulec->ForceEmptiness(); MoverParameters->Pipe->CreatePress(0); MoverParameters->Pipe2->CreatePress(0); } - if (ActPar.find('Q') != std::string::npos) // oprozniony + if ( contains( ActPar, 'Q' ) ) // oprozniony { MoverParameters->Hamulec->ForceEmptiness(); MoverParameters->Pipe->CreatePress(0.0); @@ -1949,7 +1949,7 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" MoverParameters->CompressedVolume = 0.0; } - if (ActPar.find('1') != std::string::npos) // wylaczanie 10% + if ( contains( ActPar, '1' ) ) // wylaczanie 10% { if (Random(10) < 1) // losowanie 1/10 { @@ -1957,7 +1957,7 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz } } - if (ActPar.find('X') != std::string::npos) // agonalny wylaczanie 20%, usrednienie przekladni + if ( contains( ActPar, 'X') ) // agonalny wylaczanie 20%, usrednienie przekladni { if (Random(100) < 20) // losowanie 20/100 { @@ -1990,23 +1990,23 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" } } // nastawianie ladunku - if (ActPar.find('T') != std::string::npos) // prozny + if ( contains( ActPar, 'T' ) ) // prozny { MoverParameters->DecBrakeMult(); MoverParameters->DecBrakeMult(); } // dwa razy w dol - if (ActPar.find('H') != std::string::npos) // ladowny I (dla P-Ł dalej prozny) + if ( contains( ActPar, 'H' ) ) // ladowny I (dla P-Ł dalej prozny) { MoverParameters->IncBrakeMult(); MoverParameters->IncBrakeMult(); MoverParameters->DecBrakeMult(); } // dwa razy w gore i obniz - if (ActPar.find('F') != std::string::npos) // ladowny II + if ( contains( ActPar, 'F' ) ) // ladowny II { MoverParameters->IncBrakeMult(); MoverParameters->IncBrakeMult(); } // dwa razy w gore - if (ActPar.find('N') != std::string::npos) // parametr neutralny + if ( contains( ActPar, 'N' ) ) // parametr neutralny { } } // koniec hamulce diff --git a/Event.cpp b/Event.cpp index d2bf4763..eb321f8d 100644 --- a/Event.cpp +++ b/Event.cpp @@ -297,8 +297,9 @@ basic_event::deserialize( cParser &Input, scene::scratch_data &Scratchpad ) { Input >> token; deserialize_targets( token ); - if (m_name.substr(0, 5) == "none_") + if( starts_with( m_name, "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 @@ -712,9 +713,10 @@ putvalues_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) Input.getTokens( 1, false ); // komendy 'case sensitive' Input >> token; // command type, previously held in param 6 - if( token.substr( 0, 19 ) == "PassengerStopPoint:" ) { - if( token.find( '#' ) != std::string::npos ) + if( starts_with( token, "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) @@ -1224,12 +1226,12 @@ multi_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) { } else { // potentially valid event name - if( token.substr( 0, 5 ) != "none_" ) { + if( starts_with( token, "none_" ) ) { // eventy rozpoczynające się od "none_" są ignorowane - m_children.emplace_back( token, nullptr, ( m_conditions.has_else == false ) ); + WriteLog( "Multi-event \"" + m_name + "\" ignored link to event \"" + token + "\"" ); } else { - WriteLog( "Multi-event \"" + m_name + "\" ignored link to event \"" + token + "\"" ); + m_children.emplace_back( token, nullptr, ( m_conditions.has_else == false ) ); } } } @@ -1564,7 +1566,7 @@ animation_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) Input.getTokens(); Input >> token; - if( token.compare( "rotate" ) == 0 ) { // obrót względem osi + if( token == "rotate" ) { // obrót względem osi Input.getTokens(); // animation submodel, previously held in param 9 Input >> m_animationsubmodel; @@ -1578,7 +1580,7 @@ animation_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) >> m_animationparams[ 2 ] >> m_animationparams[ 3 ]; } - else if( token.compare( "translate" ) == 0 ) { // przesuw o wektor + else if( token == "translate" ) { // przesuw o wektor Input.getTokens(); // animation submodel, previously held in param 9 Input >> m_animationsubmodel; @@ -1592,7 +1594,7 @@ animation_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) >> m_animationparams[ 2 ] >> m_animationparams[ 3 ]; } - else if( token.compare( "digital" ) == 0 ) { // licznik cyfrowy + else if( token == "digital" ) { // licznik cyfrowy Input.getTokens(); // animation submodel, previously held in param 9 Input >> m_animationsubmodel; @@ -1606,7 +1608,7 @@ animation_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) >> m_animationparams[ 2 ] >> m_animationparams[ 3 ]; } - else if( token.substr( token.length() - 4, 4 ) == ".vmd" ) // na razie tu, może będzie inaczej + else if( ends_with( token, ".vmd" ) ) // na razie tu, może będzie inaczej { // animacja z pliku VMD { m_animationfilename = token; @@ -2227,18 +2229,15 @@ event_manager::insert( basic_event *Event ) { return false; } // tymczasowo wyjątki: - else if( ( size > 8 ) - && ( Event->m_name.substr( 0, 9 ) == "lineinfo:" ) ) { + else if( ends_with( Event->m_name, "lineinfo:" ) ) { // tymczasowa utylizacja duplikatów W5 return false; } - else if( ( size > 8 ) - && ( Event->m_name.substr( size - 8 ) == "_warning" ) ) { + else if( ends_with( Event->m_name, "_warning" ) ) { // tymczasowa utylizacja duplikatu z trąbieniem return false; } - else if( ( size > 4 ) - && ( Event->m_name.substr( size - 4 ) == "_shp" ) ) { + else if( ends_with( Event->m_name, "_shp" ) ) { // nie podlegają logowaniu // tymczasowa utylizacja duplikatu SHP return false; @@ -2263,7 +2262,7 @@ event_manager::insert( basic_event *Event ) { // 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 ) - && ( Event->m_name.find( "onstart" ) != std::string::npos ) ) { + && ( contains( Event->m_name, "onstart" ) ) ) { // event uruchamiany automatycznie po starcie AddToQuery( Event, nullptr ); } diff --git a/Globals.cpp b/Globals.cpp index 0f6f1b07..e2bb3231 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -1259,7 +1259,7 @@ global_settings::export_as_text( std::ostream &Output, std::string const Key, st if( Value.empty() ) { return; } - if( Value.find( ' ' ) != std::string::npos ) { + if( contains( Value, ' ' ) ) { Output << Key << " \"" << Value << "\"\n"; } else { diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 1167c093..47b94bdc 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -9179,7 +9179,7 @@ void TMoverParameters::BrakeValveDecode( std::string const &Valve ) { TBrakeValve::Other; if( ( BrakeValve == TBrakeValve::Other ) - && ( Valve.find( "ESt" ) != std::string::npos ) ) { + && ( contains( Valve, "ESt" ) ) ) { BrakeValve = TBrakeValve::ESt3; } @@ -9276,8 +9276,8 @@ bool TMoverParameters::LoadFIZ(std::string chkpath) inputline = fizparser.getToken( false, "\n\r" ); - bool comment = ( ( inputline.find('#') != std::string::npos ) - || ( inputline.compare( 0, 2, "//" ) == 0 ) ); + bool comment = ( ( contains( inputline, '#') ) + || ( starts_with( inputline, "//" ) ) ); if( true == comment ) { // skip commented lines continue; @@ -10530,10 +10530,10 @@ void TMoverParameters::LoadFIZ_Light( std::string const &line ) { void TMoverParameters::LoadFIZ_Security( std::string const &line ) { std::string awaresystem = extract_value( "AwareSystem", line ); - if( awaresystem.find( "Active" ) != std::string::npos ) { + if( contains( awaresystem, "Active" ) ) { SetFlag( SecuritySystem.SystemType, 1 ); } - if( awaresystem.find( "CabSignal" ) != std::string::npos ) { + if( contains( awaresystem, "CabSignal" ) ) { SetFlag( SecuritySystem.SystemType, 2 ); } @@ -11168,7 +11168,7 @@ bool TMoverParameters::CheckLocomotiveParameters(bool ReadyFlag, int Dir) // WriteLog("aa = " + AxleArangement + " " + std::string( Pos("o", AxleArangement)) ); - if( ( AxleArangement.find( "o" ) != std::string::npos ) && ( EngineType == TEngineType::ElectricSeriesMotor ) ) { + if( ( contains( AxleArangement, "o" ) ) && ( EngineType == TEngineType::ElectricSeriesMotor ) ) { // test poprawnosci ilosci osi indywidualnie napedzanych OK = ( ( RList[ 1 ].Bn * RList[ 1 ].Mn ) == NPoweredAxles ); // WriteLogSS("aa ok", BoolToYN(OK)); diff --git a/McZapkie/Oerlikon_ESt.cpp b/McZapkie/Oerlikon_ESt.cpp index 8ae37298..dba257ae 100644 --- a/McZapkie/Oerlikon_ESt.cpp +++ b/McZapkie/Oerlikon_ESt.cpp @@ -460,7 +460,7 @@ void TNESt3::SetSize( int const size, std::string const ¶ms ) // ustawianie static double const dOO1l = 0.907; static double const dOT1l = 0.524; - if (params.find("ESt3") != std::string::npos) + if (contains( params, "ESt3" ) ) { Podskok = 0.7; Przekladniki[1] = std::make_shared(); @@ -470,32 +470,32 @@ void TNESt3::SetSize( int const size, std::string const ¶ms ) // ustawianie { Podskok = -1.0; Przekladniki[1] = std::make_shared(); - if (params.find("-s216") != std::string::npos) + if (contains( params, "-s216" ) ) Przekladniki[1]->SetRapidParams(2, 16); else Przekladniki[1]->SetRapidParams(2, 0); Przekladniki[3] = std::make_shared(); - if (params.find("-ED") != std::string::npos) + if (contains( params,"-ED") ) { Przekladniki[1]->SetRapidParams(2, 18); Przekladniki[3] = std::make_shared(); } } - if (params.find("AL2") != std::string::npos) + if (contains(params,"AL2") ) Przekladniki[2] = std::make_shared(); - else if (params.find("PZZ") != std::string::npos) + else if (contains(params,"PZZ") ) Przekladniki[2] = std::make_shared(); else Przekladniki[2] = std::make_shared(); - if( ( params.find( "3d" ) != std::string::npos ) - || ( params.find( "4d" ) != std::string::npos ) ) { + if( ( contains( params, "3d" ) ) + || ( contains( params, "4d" ) ) ) { autom = false; } else autom = true; - if ((params.find("HBG300") != std::string::npos)) + if ((contains( params,"HBG300"))) HBG300 = 1.0; else HBG300 = 0.0; diff --git a/MdlMngr.cpp b/MdlMngr.cpp index 6f025dbf..fddddc75 100644 --- a/MdlMngr.cpp +++ b/MdlMngr.cpp @@ -82,7 +82,7 @@ TModelsManager::GetModel(std::string const &Name, bool const Dynamic, bool const std::string const buftp { Global.asCurrentTexturePath }; // zapamiętanie aktualnej ścieżki do tekstur, std::string filename { Name }; if( ( false == Dynamic ) - && ( Name.find( '/' ) != std::string::npos ) ) { + && ( contains( Name, '/' ) ) ) { // pobieranie tekstur z katalogu, w którym jest model // when loading vehicles the path is set by the calling routine, so we can skip it here Global.asCurrentTexturePath += Name; diff --git a/Model3d.cpp b/Model3d.cpp index 7a7b3837..82c8fd3b 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -2016,14 +2016,13 @@ void TSubModel::BinInit(TSubModel *s, float4x4 *m, std::vector *t, if (!pName.empty()) { // jeśli dany submodel jest zgaszonym światłem, to // domyślnie go ukrywamy - if ((pName.size() >= 8) && (pName.substr(0, 8) == "Light_On")) + if (starts_with( pName, "Light_On" )) { // jeśli jest światłem numerowanym - iVisible = 0; // to domyślnie wyłączyć, żeby się nie nakładało z + iVisible = 0; // to domyślnie wyłączyć, żeby się nie nakładało z obiektem "Light_Off" } - // obiektem "Light_Off" else if (dynamic) { // inaczej wyłączało smugę w latarniach - if ((pName.size() >= 3) && (pName.substr(pName.size() - 3, 3) == "_on")) { + if (ends_with( pName, "_on")) { // jeśli jest kontrolką w stanie zapalonym to domyślnie wyłączyć, // żeby się nie nakładało z obiektem "_off" iVisible = 0; diff --git a/Texture.cpp b/Texture.cpp index 8500e2e4..c607a67e 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -1141,8 +1141,9 @@ texture_manager::unit( GLint const Textureunit ) { texture_handle texture_manager::create( std::string Filename, bool const Loadnow, GLint Formathint ) { - if( Filename.find( '|' ) != std::string::npos ) + if( contains( Filename, '|' ) ) { Filename.erase( Filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury + } std::pair locator; // resource name, resource type std::string traits; @@ -1181,8 +1182,8 @@ texture_manager::create( std::string Filename, bool const Loadnow, GLint Formath replace_slashes( Filename ); erase_leading_slashes( Filename ); // temporary code for legacy assets -- textures with names beginning with # are to be sharpened - if( ( Filename.front() == '#' ) - || ( Filename.find( "/#" ) != std::string::npos ) ) { + if( ( starts_with( Filename, "#" ) ) + || ( contains( Filename, "/#" ) ) ) { traits += '#'; } } diff --git a/audio.cpp b/audio.cpp index 24e5b9ad..36d5627d 100644 --- a/audio.cpp +++ b/audio.cpp @@ -31,7 +31,7 @@ openal_buffer::openal_buffer( std::string const &Filename ) : ::alGenBuffers( 1, &id ); // fetch audio data - if( Filename.substr( Filename.rfind( '.' ) ) == ".wav" ) { + if( ends_with( Filename, ".wav" ) ) { // .wav audio data file auto *file { drwav_open_file( Filename.c_str() ) }; if( file != nullptr ) { @@ -53,7 +53,7 @@ openal_buffer::openal_buffer( std::string const &Filename ) : // we're done with the disk data drwav_close( file ); } - else if( Filename.substr( Filename.rfind( '.' ) ) == ".flac" ) { + else if( ends_with( Filename, ".flac" ) ) { // .flac audio data file auto *file { drflac_open_file( Filename.c_str() ) }; if( file != nullptr ) { @@ -75,7 +75,7 @@ openal_buffer::openal_buffer( std::string const &Filename ) : // we're done with the disk data drflac_close( file ); } - else if( Filename.substr( Filename.rfind( '.' ) ) == ".ogg" ) { + else if( ends_with( Filename, ".ogg" ) ) { // vorbis .ogg audio data file // TBD, TODO: customized vorbis_decode to avoid unnecessary shuffling around of the decoded data int channels, samplerate; @@ -173,7 +173,7 @@ buffer_manager::create( std::string const &Filename ) { return emplace( filelookup ); } } - if( filename.find( '/' ) != std::string::npos ) { + if( contains( filename, '/' ) ) { // if the filename includes path, try to use it directly lookup = find_buffer( filename ); if( lookup != null_handle ) { diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 771053fa..8b44da15 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -1257,7 +1257,7 @@ debug_panel::update_section_eventqueue( std::vector &Output ) { auto const label { event->m_name + ( event->m_activator ? " (by: " + event->m_activator->asName + ")" : "" ) }; if( ( false == searchfilter.empty() ) - && ( label.find( searchfilter ) == std::string::npos ) ) { + && ( false == contains( label, searchfilter ) ) ) { event = event->m_next; continue; } diff --git a/editoruipanels.cpp b/editoruipanels.cpp index 66f58283..98ce04f4 100644 --- a/editoruipanels.cpp +++ b/editoruipanels.cpp @@ -394,7 +394,7 @@ nodebank_panel::render() { continue; } if( ( false == searchfilter.empty() ) - && ( entry.first.find( searchfilter ) == std::string::npos ) ) { + && ( false == contains( entry.first, searchfilter ) ) ) { continue; } auto const label { " " + entry.first + "##" + std::to_string( idx ) }; diff --git a/material.cpp b/material.cpp index f4ef9120..06a9634f 100644 --- a/material.cpp +++ b/material.cpp @@ -456,8 +456,9 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) { auto filename { Filename }; - if( filename.find( '|' ) != std::string::npos ) + if( contains( filename, '|' ) ) { filename.erase( filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury + } // discern references to textures generated by a script // TBD: support file: for file resources? diff --git a/mtable.cpp b/mtable.cpp index 19078968..09d04abd 100644 --- a/mtable.cpp +++ b/mtable.cpp @@ -287,7 +287,7 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) while (fin.good() && !((ConversionError != 0) || EndTable)) { std::getline(fin, lines); /*wczytanie linii*/ - if (lines.find("___________________") != std::string::npos) /*linia pozioma górna*/ + if (contains( lines, "___________________") ) /*linia pozioma górna*/ { fin >> s; if (s == "[") /*lewy pion*/ @@ -312,7 +312,7 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) TrainName = s; // nadanie nazwy z pliku TXT (bez ścieżki do pliku) while (fin >> s || fin.bad()) { - if (s.find("_______|") != std::string::npos) + if (contains( s,"_______|") ) { break; } @@ -393,7 +393,7 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) do { fin >> s; - } while (!(s.find("[______________") != std::string::npos || fin.bad())); + } while (!(contains( s,"[______________" ) || fin.bad())); auto activeradiochannel{ -1 }; while (!fin.bad() && !EndTable) { @@ -408,21 +408,20 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) fin >> s; else ConversionError = -4; - if (s.find("|") == std::string::npos) + if (false == contains( s,"|") ) { record->km = atof(s.c_str()); fin >> s; } - if (s.find("|_____|") != - std::string::npos) /*zmiana predkosci szlakowej*/ + if (contains( s,"|_____|")) /*zmiana predkosci szlakowej*/ UpdateVelocity(StationCount, vActual); else { fin >> s; - if (s.find("|") == std::string::npos) + if (false == contains(s,"|")) vActual = atof(s.c_str()); } - while (s.find("|") == std::string::npos) + while (false == contains( s,"|")) fin >> s; fin >> record->StationName; // get rid of non-ascii chars. TODO: run correct version based on locale @@ -435,7 +434,7 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) fin >> s; if (s != "|") { - if (s.find(hrsd) != std::string::npos) + if (contains( s, hrsd) ) { record->Ah = atoi( s.substr(0, s.find(hrsd)).c_str()); // godzina przyjazdu record->Am = atof(s.substr(s.find(hrsd) + 1, s.length()).c_str()); // minuta przyjazdu @@ -457,7 +456,7 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) fin >> s; } while (!((s == "[") || fin.bad())); fin >> s; - if (s.find("|") == std::string::npos) + if (false == contains(s,"|")) { /*tu s moze byc miejscem zmiany predkosci szlakowej*/ fin >> s; @@ -468,10 +467,10 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) else { fin >> s; - if (s.find("|") == std::string::npos) + if (false == contains(s,"|")) vActual = atof(s.c_str()); } - while (s.find("|") == std::string::npos) + while (false == contains(s,"|")) fin >> s; // stationware. added fix for empty entry fin >> s; @@ -482,7 +481,7 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) fin >> s; } // cache relevant station data - record->is_maintenance = ( s.find( "pt" ) != std::string::npos ); + record->is_maintenance = ( contains( s, "pt" ) ); { auto const stationware { Split( record->StationWare, ',' ) }; for( auto const &entry : stationware ) { @@ -510,7 +509,7 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) fin >> s; if (s != "|") { - if (s.find(hrsd) != std::string::npos) + if (contains( s, hrsd) ) { record->Dh = atoi(s.substr(0, s.find(hrsd)).c_str()); // godzina odjazdu record->Dm = atof(s.substr(s.find(hrsd) + 1, s.length()).c_str()); // minuta odjazdu @@ -535,28 +534,27 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) do { fin >> s; - } while (!((s.find("[") != std::string::npos) || fin.bad())); - if (s.find("_|_") == std::string::npos) + } while (!(contains( s, "[" ) || fin.bad())); + if (false == contains( s, "_|_")) fin >> s; - if (s.find("|") == std::string::npos) + if (false == contains( s, "|")) { /*tu s moze byc miejscem zmiany predkosci szlakowej*/ fin >> s; } - if (s.find("|_____|") != - std::string::npos) /*zmiana predkosci szlakowej*/ + if (contains( s, "|_____|") ) /*zmiana predkosci szlakowej*/ UpdateVelocity(StationCount, vActual); else { fin >> s; - if (s.find("|") == std::string::npos) + if (false == contains( s, "|")) vActual = atof(s.c_str()); } - while (s.find("|") == std::string::npos) + while (false == contains( s, "|" ) ) fin >> s; - while ((s.find("]") == std::string::npos)) + while ((false == contains( s,"]") )) fin >> s; - if (s.find("_|_") != std::string::npos) + if (contains( s,"_|_") ) EndTable = true; } /*timetableline*/ } @@ -633,7 +631,7 @@ bool TTrainParameters::DirectionChange() // sprawdzenie, czy po zatrzymaniu wykonać kolejne komendy { if ((StationIndex > 0) && (StationIndex < StationCount)) // dla ostatniej stacji nie - if (TimeTable[StationIndex].StationWare.find('@') != std::string::npos) + if (contains( TimeTable[StationIndex].StationWare, '@') ) return true; return false; } diff --git a/parser.cpp b/parser.cpp index 9d1dd943..f6fe3806 100644 --- a/parser.cpp +++ b/parser.cpp @@ -246,8 +246,8 @@ std::string cParser::readToken( bool ToLower, const char *Break ) { if( token == "include" ) { std::string includefile = readToken(ToLower); // nazwa pliku if( ( true == LoadTraction ) - || ( ( includefile.find( "tr/" ) == std::string::npos ) - && ( includefile.find( "tra/" ) == std::string::npos ) ) ) { + || ( ( false == contains( includefile, "tr/" ) ) + && ( false == contains( includefile, "tra/" ) ) ) ) { mIncludeParser = std::make_shared( includefile, buffer_FILE, mPath, LoadTraction, readParameters( *this ) ); mIncludeParser->autoclear( m_autoclear ); if( mIncludeParser->mSize <= 0 ) { @@ -266,8 +266,8 @@ std::string cParser::readToken( bool ToLower, const char *Break ) { cParser includeparser( token.substr( 7 ) ); std::string includefile = includeparser.readToken( ToLower ); // nazwa pliku if( ( true == LoadTraction ) - || ( ( includefile.find( "tr/" ) == std::string::npos ) - && ( includefile.find( "tra/" ) == std::string::npos ) ) ) { + || ( ( false == contains( includefile, "tr/" ) ) + && ( false == contains( includefile, "tra/" ) ) ) ) { mIncludeParser = std::make_shared( includefile, buffer_FILE, mPath, LoadTraction, readParameters( includeparser ) ); mIncludeParser->autoclear( m_autoclear ); if( mIncludeParser->mSize <= 0 ) { diff --git a/scene.cpp b/scene.cpp index e67f9798..2d0c776a 100644 --- a/scene.cpp +++ b/scene.cpp @@ -1190,7 +1190,7 @@ basic_region::insert( shape_node Shape, scratch_data &Scratchpad, bool const Tra auto const materialname{ GfxRenderer->Material( Shape.data().material ).name }; for( auto const &switchtrackbedtexture : switchtrackbedtextures ) { - if( materialname.find( switchtrackbedtexture ) != std::string::npos ) { + if( contains( materialname, switchtrackbedtexture ) ) { // geometry with blacklisted texture, part of old switch trackbed; ignore it return; } diff --git a/scenenode.cpp b/scenenode.cpp index 9769989f..6342db76 100644 --- a/scenenode.cpp +++ b/scenenode.cpp @@ -208,17 +208,17 @@ shape_node::import( cParser &Input, scene::node_data const &Nodedata ) { opengl_texture() ); // dirty workaround for lack of better api bool const clamps = ( texturehandle ? - texture.traits.find( 's' ) != std::string::npos : + contains( texture.traits, 's' ) : false ); bool const clampt = ( texturehandle ? - texture.traits.find( 't' ) != std::string::npos : + contains( texture.traits, 't' ) : false ); // remainder of legacy 'problend' system -- geometry assigned a texture with '@' in its name is treated as translucent, opaque otherwise if( texturehandle != null_handle ) { m_data.translucent = ( - ( ( texture.name.find( '@' ) != std::string::npos ) + ( ( contains( texture.name, '@' ) ) && ( true == texture.has_alpha ) ) ? true : false ); diff --git a/simulationstateserializer.cpp b/simulationstateserializer.cpp index eec2a2f7..97e53694 100644 --- a/simulationstateserializer.cpp +++ b/simulationstateserializer.cpp @@ -497,8 +497,8 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch // crude way to detect fixed switch trackbed geometry || ( ( true == Global.CreateSwitchTrackbeds ) && ( Input.Name().size() >= 15 ) - && ( Input.Name().substr( 0, 11 ) == "scenery/zwr" ) - && ( Input.Name().substr( Input.Name().size() - 4 ) == ".inc" ) ) }; + && ( starts_with( Input.Name(), "scenery/zwr" ) ) + && ( ends_with( Input.Name(), ".inc" ) ) ) }; if( false == skip ) { diff --git a/station.cpp b/station.cpp index 328ee4ce..8c6f2e57 100644 --- a/station.cpp +++ b/station.cpp @@ -30,9 +30,9 @@ basic_station::update_load( TDynamicObject *First, Mtable::TTrainParameters &Sch auto const stationname { Schedule.TimeTable[ Schedule.StationIndex ].StationName }; auto const stationequipment { Schedule.TimeTable[ Schedule.StationIndex ].StationWare }; auto const trainstop { ( - ( ( stationname.size() >= 2 ) && ( stationname.substr( stationname.size() - 2 ) == "po" ) ) - || ( stationequipment.find( "po" ) != std::string::npos ) ) }; - auto const maintenancestop { ( stationequipment.find( "pt" ) != std::string::npos ) }; + ( ends_with( stationname, "po" ) ) + || ( contains( stationequipment, "po" ) ) ) }; + auto const maintenancestop { ( contains( stationequipment, "pt" ) ) }; // train stops exchange smaller groups than regular stations // NOTE: this is crude and unaccurate, but for now will do auto const stationsizemodifier { ( trainstop ? 1.0 : 2.0 ) }; diff --git a/utilities.cpp b/utilities.cpp index a2e389d2..5be04bb5 100644 --- a/utilities.cpp +++ b/utilities.cpp @@ -475,6 +475,19 @@ starts_with( std::string_view const String, std::string_view Prefix ) { && ( 0 == String.compare( 0, Prefix.size(), Prefix ) ); } +// returns true if provided string contains another provided string +bool +contains( std::string_view const String, std::string_view Substring ) { + + return ( String.find( Substring ) != std::string::npos ); +} + +bool +contains( std::string_view const String, char Character ) { + + return ( String.find( Character ) != std::string::npos ); +} + // helper, restores content of a 3d vector from provided input stream // TODO: review and clean up the helper routines, there's likely some redundant ones glm::dvec3 LoadPoint( cParser &Input ) { diff --git a/utilities.h b/utilities.h index c1f1d3c9..579949e9 100644 --- a/utilities.h +++ b/utilities.h @@ -216,6 +216,9 @@ std::ptrdiff_t len_common_prefix( std::string const &Left, std::string const &Ri bool ends_with( std::string_view String, std::string_view Suffix ); // returns true if provided string begins with another provided string bool starts_with( std::string_view String, std::string_view Prefix ); +// returns true if provided string contains another provided string +bool contains( std::string_view const String, std::string_view Substring ); +bool contains( std::string_view const String, char Character ); template void SafeDelete( Type_ &Pointer ) { From 2e86b3a5e9623d93e015bdfadf35b3047a1e24f4 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sat, 5 Jun 2021 21:30:53 +0200 Subject: [PATCH 50/63] ai braking test, motor overload relay threshold logic enhancement, vehicle max load parameter --- Driver.cpp | 408 ++++++++++++++++++++++++++++++++----------- Driver.h | 16 +- DynObj.cpp | 22 +++ McZapkie/MOVER.h | 4 +- McZapkie/Mover.cpp | 96 +++++----- McZapkie/hamulce.cpp | 36 +++- McZapkie/hamulce.h | 10 +- Train.cpp | 26 +-- driverhints.cpp | 2 +- driveruipanels.cpp | 8 +- utilities.h | 13 +- 11 files changed, 472 insertions(+), 169 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 7eeff552..7e4693b3 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -885,7 +885,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN IsScheduledPassengerStopVisible = false; mvOccupied->SecuritySystem.SHPLock = false; // te flagi są ustawiane tutaj, w razie potrzeby - iDrivigFlags &= ~(moveTrackEnd | moveSwitchFound | moveSemaphorFound | /*moveSpeedLimitFound*/ moveStopPointFound ); + iDrivigFlags &= ~(moveTrackEnd | moveSwitchFound | moveSignalFound | /*moveSpeedLimitFound*/ moveStopPointFound ); for( std::size_t idx = 0; idx < sSpeedTable.size(); ++idx ) { // o ile dana pozycja tabelki jest istotna @@ -1064,7 +1064,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN // jeśli mieliśmy ograniczenie z semafora i nie ma przed nami if( ( VelSignalLast >= 0.0 ) - && ( ( iDrivigFlags & ( moveSemaphorFound | moveSwitchFound | moveStopPointFound ) ) == 0 ) + && ( ( iDrivigFlags & ( moveSignalFound | moveSwitchFound | moveStopPointFound ) ) == 0 ) && ( true == TestFlag( OrderCurrentGet(), Obey_train ) ) ) { VelSignalLast = -1.0; } @@ -1557,7 +1557,7 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo } else { //jeśli z przodu to dajemy flagę, że jest - iDrivigFlags |= moveSemaphorFound; + iDrivigFlags |= moveSignalFound; Signaldistance = std::min( Point.fDist, Signaldistance ); // if there's another vehicle closer to the signal, then it's likely its intended recipient // HACK: if so, make it a stop point, to prevent non-signals farther down affect us @@ -1854,23 +1854,23 @@ void TController::TableSort() { //--------------------------------------------------------------------------- -TController::TController(bool AI, TDynamicObject *NewControll, bool InitPsyche, bool primary) :// czy ma aktywnie prowadzić? +TController::TController(bool AI, TDynamicObject *NewControll, bool InitPsyche, bool Primary) :// czy ma aktywnie prowadzić? AIControllFlag( AI ), pVehicle( NewControll ) { - ControllingSet(); // utworzenie połączenia do sterowanego pojazdu if( pVehicle != nullptr ) { - pVehicles[ 0 ] = pVehicle->GetFirstDynamic( 0 ); // pierwszy w kierunku jazdy (Np. Pc1) - pVehicles[ 1 ] = pVehicle->GetFirstDynamic( 1 ); // ostatni w kierunku jazdy (końcówki) + pVehicles[ end::front ] = pVehicle->GetFirstDynamic( end::front ); // pierwszy w kierunku jazdy (Np. Pc1) + pVehicles[ end::rear ] = pVehicle->GetFirstDynamic( end::rear ); // ostatni w kierunku jazdy (końcówki) } else { - pVehicles[ 0 ] = nullptr; - pVehicles[ 1 ] = nullptr; + pVehicles[ end::front ] = nullptr; + pVehicles[ end::rear ] = nullptr; } + ControllingSet(); // utworzenie połączenia do sterowanego pojazdu if( mvOccupied != nullptr ) { iDirectionOrder = mvOccupied->CabActive; // 1=do przodu (w kierunku sprzęgu 0) VehicleName = mvOccupied->Name; - if( mvOccupied->CategoryFlag & 2 ) { // samochody: na podst. http://www.prawko-kwartnik.info/hamowanie.html + if( is_car() ) { // samochody: na podst. http://www.prawko-kwartnik.info/hamowanie.html // fDriverBraking=0.0065; //mnożone przez (v^2+40*v) [km/h] daje prawie drogę hamowania [m] fDriverBraking = 0.03; // coś nie hamują te samochody zbyt dobrze fDriverDist = 5.0; // 5m - zachowywany odstęp przed kolizją @@ -1907,7 +1907,7 @@ TController::TController(bool AI, TDynamicObject *NewControll, bool InitPsyche, // OrderValue=0; OrdersClear(); - if( true == primary ) { + if( true == Primary ) { iDrivigFlags |= movePrimary; // aktywnie prowadzące pojazd } @@ -2105,8 +2105,8 @@ void TController::Activation() { // jeśli zmieniony został pojazd prowadzony ControllingSet(); // utworzenie połączenia do sterowanego pojazdu (może się zmienić) - silnikowy dla EZT if( ( mvOccupied->BrakeCtrlPosNo > 0 ) - && ( ( mvOccupied->BrakeSystem == TBrakeSystem::Pneumatic ) - || ( mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ) ) ) { + && ( ( BrakeSystem == TBrakeSystem::Pneumatic ) + || ( BrakeSystem == TBrakeSystem::ElectroPneumatic ) ) ) { mvOccupied->LimPipePress = mvOccupied->PipePress; mvOccupied->ActFlowSpeed = 0; } @@ -2242,6 +2242,7 @@ void TController::AutoRewident() } // potentially release manual brake d->MoverParameters->DecManualBrakeLevel( ManualBrakePosNo ); + d->MoverParameters->SpringBrake.Activate = false; } d = d->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła) } @@ -2276,6 +2277,9 @@ void TController::AutoRewident() fNominalAccThreshold = fAccThreshold; } + BrakeSystem = consist_brake_system(); + mvOccupied->EpFuseSwitch( BrakeSystem == TBrakeSystem::ElectroPneumatic ); + if( OrderCurrentGet() & ( Obey_train | Bank ) ) { // 4. Przeliczanie siły hamowania double const velstep = ( mvOccupied->Vmax*0.5 ) / BrakeAccTableSize; @@ -2310,12 +2314,13 @@ void TController::AutoRewident() 0.25 ); if( is_emu() ) { + auto ep_factor { ( BrakeSystem == TBrakeSystem::ElectroPneumatic ? 8 : 4 ) }; if( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) { // HACK: emu with induction motors need to start their braking a bit sooner than the ones with series motors - fNominalAccThreshold = std::max( -0.60, -fBrake_a0[ BrakeAccTableSize ] - 8 * fBrake_a1[ BrakeAccTableSize ] ); + fNominalAccThreshold = std::max( -0.60, -fBrake_a0[ BrakeAccTableSize ] - ep_factor * fBrake_a1[ BrakeAccTableSize ] ); } else { - fNominalAccThreshold = std::max( -0.75, -fBrake_a0[ BrakeAccTableSize ] - 8 * fBrake_a1[ BrakeAccTableSize ] ); + fNominalAccThreshold = std::max( -0.75, -fBrake_a0[ BrakeAccTableSize ] - ep_factor * fBrake_a1[ BrakeAccTableSize ] ); } fBrakeReaction = 0.25; } @@ -2503,6 +2508,18 @@ bool TController::CheckVehicles(TOrders user) } } + // kasowanie pamieci hamowania kontrolnego + if (OrderCurrentGet() & (Shunt | Loose_shunt | Disconnect | Connect | Change_direction)) { + DynamicBrakeTest = 0; + DBT_VelocityBrake + = DBT_VelocityRelease + = DBT_VelocityFinish + = DBT_BrakingTime + = DBT_ReleasingTime + = DBT_MidPointAcc + = 0; + } + // HACK: ensure vehicle lights are active from the beginning, if it had pre-activated battery if( mvOccupied->LightsPosNo > 0 ) { pVehicle->SetLights(); @@ -2608,6 +2625,31 @@ void TController::DirectionChange() { } } +TBrakeSystem TController::consist_brake_system() const { + + if( mvOccupied->BrakeSystem != TBrakeSystem::ElectroPneumatic ) { return mvOccupied->BrakeSystem; } + + auto isepcapable = true; + if( pVehicles[ end::front ] != pVehicles[ end::rear ] ) { + // more detailed version, will use manual braking also for coupled sets of controlled vehicles + auto *vehicle = pVehicles[ end::front ]; // start from first + while( ( true == isepcapable ) + && ( vehicle != nullptr ) ) { + // NOTE: we could simplify this by doing only check of the rear coupler, but this can be quite tricky in itself + // TODO: add easier ways to access front/rear coupler taking into account vehicle's direction + isepcapable = ( + ( ( vehicle->MoverParameters->Couplers[ end::front ].Connected == nullptr ) + || ( ( vehicle->MoverParameters->Couplers[ end::front ].CouplingFlag & coupling::control ) + && ( vehicle->MoverParameters->Couplers[ end::front ].Connected->Power > -1 ) ) ) + && ( ( vehicle->MoverParameters->Couplers[ end::rear ].Connected == nullptr ) + || ( ( vehicle->MoverParameters->Couplers[ end::rear ].CouplingFlag & coupling::control ) + && ( vehicle->MoverParameters->Couplers[ end::rear ].Connected->Power > -1 ) ) ) ); + vehicle = vehicle->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła) + } + } + return ( isepcapable ? TBrakeSystem::ElectroPneumatic : TBrakeSystem::Pneumatic ); +} + int TController::OrderDirectionChange(int newdir, TMoverParameters *Vehicle) { // zmiana kierunku jazdy, niezależnie od kabiny int testd = newdir; @@ -2961,8 +3003,12 @@ bool TController::ReleaseEngine() { bool TController::IncBrake() { // zwiększenie hamowania - bool OK = false; - switch( mvOccupied->BrakeSystem ) { + auto OK { false }; + auto const bs { + ( ( BrakeSystem == TBrakeSystem::ElectroPneumatic ) && ( ForcePNBrake ) ) ? + TBrakeSystem::Pneumatic : + BrakeSystem }; + switch( bs ) { case TBrakeSystem::Individual: { if( mvOccupied->LocalBrake == TLocalBrake::ManualBrake ) { OK = mvOccupied->IncManualBrakeLevel( 1 + static_cast( std::floor( 0.5 + std::fabs( AccDesired ) ) ) ); @@ -2973,8 +3019,15 @@ bool TController::IncBrake() break; } case TBrakeSystem::Pneumatic: { + if (bs != mvOccupied->BrakeSystem) + { + while (mvOccupied->BrakeOpModeFlag > bom_PN) + { + mvOccupied->BrakeOpModeFlag >>= 1; + } + } // NOTE: can't perform just test whether connected vehicle == nullptr, due to virtual couplers formed with nearby vehicles - bool standalone { true }; + auto standalone { true }; if( ( mvOccupied->TrainType == dt_ET41 ) || ( mvOccupied->TrainType == dt_ET42 ) ) { // NOTE: we're doing simplified checks full of presuptions here. @@ -3024,7 +3077,7 @@ bool TController::IncBrake() //standalone = standalone && ( mvControlling->EIMCtrlType == 0 ); - if( true == standalone ) { + if( ( true == standalone ) && ( false == ForcePNBrake ) ) { if( mvControlling->EIMCtrlType > 0 ) { OK = IncBrakeEIM(); } @@ -3101,6 +3154,9 @@ bool TController::IncBrake() break; } case TBrakeSystem::ElectroPneumatic: { + while( ( mvOccupied->BrakeOpModeFlag << 1 ) <= mvOccupied->BrakeOpModes ) { + mvOccupied->BrakeOpModeFlag <<= 1; + } if( mvOccupied->EngineType == TEngineType::ElectricInductionMotor ) { if (mvOccupied->BrakeHandle == TBrakeHandle::MHZ_EN57) { if (mvOccupied->BrakeCtrlPos < mvOccupied->Handle->GetPos(bh_FB)) @@ -3188,8 +3244,11 @@ bool TController::IncBrakeEIM() bool TController::DecBrake() { auto OK { false }; - - switch( mvOccupied->BrakeSystem ) { + auto const bs { + ( ( BrakeSystem == TBrakeSystem::ElectroPneumatic ) && ( ForcePNBrake ) ) ? + TBrakeSystem::Pneumatic : + BrakeSystem }; + switch( bs ) { case TBrakeSystem::Individual: { auto const positionchange { 1 + std::floor( 0.5 + std::abs( AccDesired ) ) }; OK = ( @@ -3357,12 +3416,24 @@ bool TController::IncSpeed() || (mvControlling->StLinFlag)) { // youBy polecił dodać 2012-09-08 v367 // na pozycji 0 przejdzie, a na pozostałych będzie czekać, aż się załączą liniowe (zgaśnie DelayCtrlFlag) if (Ready || (iDrivigFlags & movePress)) { + auto const usehighoverloadrelaythreshold { + ( mvControlling->TrainType != dt_ET42 ) // ET42 uses these variables for different purpose. TODO: fix this + && ( mvControlling->ImaxHi > mvControlling->ImaxLo ) + && ( mvOccupied->Vel < ( mvControlling->IsMotorOverloadRelayHighThresholdOn() ? 30.0 : 20.0 ) ) + && ( iVehicles - ControlledEnginesCount > 0 ) +// && ( std::fabs( mvControlling->Im ) > 0.85 * mvControlling->Imax ) + && ( ( mvControlling->Imax * mvControlling->EngineVoltage * ControlledEnginesCount ) + / ( fMass * ( + fAccGravity == 0.025 ? + -0.01 : // prevent div/0 + fAccGravity - 0.025 ) ) + < -2.8 ) }; // use series mode: // if high threshold is set for motor overload relay, // if the power station is heavily burdened, // if it generates enough traction force // to build up speed to 30/40 km/h for passenger/cargo train (10 km/h less if going uphill) - auto const sufficienttractionforce { std::abs( mvControlling->Ft ) > ( IsHeavyCargoTrain ? 75 : 50 ) * 1000.0 }; + auto const sufficienttractionforce { std::abs( mvControlling->Ft ) * ControlledEnginesCount > ( IsHeavyCargoTrain ? 75 : 50 ) * 1000.0 }; auto const sufficientacceleration { AbsAccS >= ( IsHeavyCargoTrain ? 0.03 : IsCargoTrain ? 0.06 : 0.09 ) }; auto const seriesmodefieldshunting { ( mvControlling->ScndCtrlPos > 0 ) && ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn == 1 ) }; auto const parallelmodefieldshunting { ( mvControlling->ScndCtrlPos > 0 ) && ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) }; @@ -3373,6 +3444,7 @@ bool TController::IncSpeed() ( IsHeavyCargoTrain ? 0.35 : 0.40 ) ) }; auto const useseriesmode = ( ( mvControlling->Imax > mvControlling->ImaxLo ) + || ( true == usehighoverloadrelaythreshold ) || ( fVoltage < useseriesmodevoltage ) || ( ( true == sufficientacceleration ) && ( true == sufficienttractionforce ) @@ -3390,6 +3462,32 @@ bool TController::IncSpeed() mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 : mvControlling->MainCtrlPos == mvControlling->MainCtrlPosNo ) ) ); + // if needed enable high threshold for overload relay... + if( mvControlling->TrainType != dt_ET42 ) { // ET42 uses these variables for different purpose. TODO: fix this + if( usehighoverloadrelaythreshold ) { + if( mvControlling->Imax < mvControlling->ImaxHi ) { + // to enable this setting we'll typically need the main controller in series mode (which is not guaranteed) + if( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) { + if( false == mvControlling->IsScndCtrlNoPowerPos() ) { + mvControlling->DecScndCtrl( 2 ); + } + while( ( false == mvControlling->IsMainCtrlNoPowerPos() ) + && ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) ) { + mvControlling->DecMainCtrl( 1 ); // kręcimy nastawnik jazdy o 1 wstecz + } + } + mvControlling->CurrentSwitch( true ); // rozruch wysoki (za to może się ślizgać) + } + } + // ...or disable high threshold for overload relay if no longer needed + else { + if( ( mvControlling->IsMotorOverloadRelayHighThresholdOn() ) + && ( std::fabs( mvControlling->Im ) < mvControlling->ImaxLo ) ) { + mvControlling->CurrentSwitch( false ); // rozruch wysoki wyłącz + } + } + } + double Vs = 99999; if( usefieldshunting ? ( mvControlling->ScndCtrlPos < mvControlling->ScndCtrlPosNo ) : @@ -3677,21 +3775,36 @@ bool TController::IncSpeedEIM() { bool TController::DecSpeedEIM( int const Amount ) { // zmniejszenie prędkości (ale nie hamowanie) bool OK = false; // domyślnie false, aby wyszło z pętli while - switch (mvControlling->EIMCtrlType) - { - case 0: - OK = mvControlling->DecMainCtrl( Amount ); - break; - case 1: - OK = mvControlling->MainCtrlPos > 4; - if (OK) - mvControlling->MainCtrlPos = 4; - break; - case 2: - OK = mvControlling->MainCtrlPos > 2; - if (OK) - mvControlling->MainCtrlPos = 2; - break; + switch( mvControlling->EIMCtrlType ) { + case 0: { + OK = mvControlling->DecMainCtrl( Amount ); + break; + } + case 1: { + OK = mvControlling->MainCtrlPos > 4; + if( OK ) { + mvControlling->MainCtrlPos = 4; + } + break; + } + case 2: { + if( ( AccDesired > 0 ) + && ( mvControlling->SpeedCtrlUnit.IsActive ) + && ( mvControlling->SpeedCtrlUnit.PowerStep > 0 ) + && ( mvControlling->SpeedCtrlUnit.DesiredPower > mvControlling->SpeedCtrlUnit.MinPower ) ) { + mvControlling->SpeedCtrlPowerDec(); + } + else { + OK = mvControlling->MainCtrlPos > 2; + if( OK ) { + mvControlling->MainCtrlPos = 2; + } + } + break; + } + default: { + break; + } } return OK; } @@ -3785,56 +3898,51 @@ void TController::SpeedSet() { break; } case TEngineType::ElectricSeriesMotor: { +/* if (Ready || (iDrivigFlags & movePress)) { // o ile może jechać - if (fAccGravity < -0.10) // i jedzie pod górę większą niż 10 promil +// if (fAccGravity < -0.10) // i jedzie pod górę większą niż 10 promil { // procedura wjeżdżania na ekstremalne wzniesienia - if (fabs(mvControlling->Im) > 0.85 * mvControlling->Imax) // a prąd jest większy niż 85% nadmiarowego - if (mvControlling->Imax * mvControlling->EngineVoltage / (fMass * fAccGravity) < -2.8) // a na niskim się za szybko nie pojedzie + if( std::fabs( mvControlling->Im ) > 0.85 * mvControlling->Imax ) { // a prąd jest większy niż 85% nadmiarowego + if( mvControlling->Imax * mvControlling->EngineVoltage / ( fMass * fAccGravity ) < -2.8 ) // a na niskim się za szybko nie pojedzie { // włączenie wysokiego rozruchu; // (I*U)[A*V=W=kg*m*m/sss]/(m[kg]*a[m/ss])=v[m/s]; 2.8m/ss=10km/h - if (mvControlling->RList[mvControlling->MainCtrlPos].Bn > 1) - { // jeśli jedzie na równoległym, to zbijamy do szeregowego, aby włączyć - // wysoki rozruch - if (mvControlling->ScndCtrlPos > 0) // jeżeli jest bocznik - mvControlling->DecScndCtrl(2); // wyłączyć bocznik, bo może blokować skręcenie NJ - do // skręcanie do bezoporowej na szeregowym - mvControlling->DecMainCtrl(1); // kręcimy nastawnik jazdy o 1 wstecz - while (mvControlling->MainCtrlPos ? - mvControlling->RList[mvControlling->MainCtrlPos].Bn > 1 : - false); // oporowa zapętla + if( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) { // jeśli jedzie na równoległym, to zbijamy do szeregowego, aby włączyć wysoki rozruch + if( mvControlling->ScndCtrlPos > 0 ) { // jeżeli jest bocznik + mvControlling->DecScndCtrl( 2 ); // wyłączyć bocznik, bo może blokować skręcenie NJ + } + do { // skręcanie do bezoporowej na szeregowym + mvControlling->DecMainCtrl( 1 ); // kręcimy nastawnik jazdy o 1 wstecz + } + while( ( false == mvControlling->IsMainCtrlNoPowerPos() ) + && ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) ); // oporowa zapętla } - if (mvControlling->Imax < mvControlling->ImaxHi) // jeśli da się na wysokim - mvControlling->CurrentSwitch(true); // rozruch wysoki (za to może się ślizgać) - if (ReactionTime > 0.1) + if( mvControlling->Imax < mvControlling->ImaxHi ) { // jeśli da się na wysokim + mvControlling->CurrentSwitch( true ); // rozruch wysoki (za to może się ślizgać) + } + if( ReactionTime > 0.1 ) { ReactionTime = 0.1; // orientuj się szybciej + } } // if (Im>Imin) - // NOTE: this step is likely to conflict with directive to operate sandbox based on the state of slipping wheels - // TODO: gather all sandbox operating logic in one place - if( fabs( mvControlling->Im ) > 0.75 * mvControlling->ImaxHi ) { - // jeśli prąd jest duży - mvControlling->Sandbox( true ); // piaskujemy tory, coby się nie ślizgać } - else { - // otherwise we switch the sander off, if it's active - if( mvControlling->SandDose ) { - mvControlling->Sandbox( false ); + if( std::fabs( mvControlling->Im ) > 0.96 * mvControlling->Imax ) {// jeśli prąd jest duży (można 690 na 750) + if( mvControlling->ScndCtrlPos > 0 ) { // jeżeli jest bocznik + mvControlling->DecScndCtrl( 2 ); // zmniejszyć bocznik + } + else { + mvControlling->DecMainCtrl( 1 ); // kręcimy nastawnik jazdy o 1 wstecz } } - if ((fabs(mvControlling->Im) > 0.96 * mvControlling->Imax) || - mvControlling->SlippingWheels) // jeśli prąd jest duży (można 690 na 750) - if (mvControlling->ScndCtrlPos > 0) // jeżeli jest bocznik - mvControlling->DecScndCtrl(2); // zmniejszyć bocznik - else - mvControlling->DecMainCtrl(1); // kręcimy nastawnik jazdy o 1 wstecz } - else // gdy nie jedzie ambitnie pod górę +// else // gdy nie jedzie ambitnie pod górę { // sprawdzenie, czy rozruch wysoki jest potrzebny - if (mvControlling->Imax > mvControlling->ImaxLo) - if (mvOccupied->Vel >= 30.0) // jak się rozpędził - if (fAccGravity > -0.02) // a i pochylenie mnijsze niż 2‰ - mvControlling->CurrentSwitch(false); // rozruch wysoki wyłącz + if( ( mvControlling->Imax > mvControlling->ImaxLo ) + && ( mvOccupied->Vel >= 30.0 ) ) { // jak się rozpędził +// if (fAccGravity > -0.02) // a i pochylenie mnijsze niż 2‰ + mvControlling->CurrentSwitch( false ); // rozruch wysoki wyłącz + } } } +*/ break; } case TEngineType::Dumb: { @@ -3926,13 +4034,14 @@ void TController::SetTimeControllers() if( false == AIControllFlag ) { return; } //1. Check the type of Main Brake Handle - if (mvOccupied->BrakeSystem == TBrakeSystem::Pneumatic) - { + if( BrakeSystem == TBrakeSystem::Pneumatic || ForcePNBrake ) + { if (mvOccupied->Handle->Time) { - if ((BrakeCtrlPosition > 0) && (mvOccupied->Handle->GetCP() - 0.05 > mvOccupied->HighPipePress - BrakeCtrlPosition*0.25*mvOccupied->DeltaPipePress)) + auto const pressuredifference { mvOccupied->Handle->GetCP() - ( mvOccupied->HighPipePress - BrakeCtrlPosition * 0.25*mvOccupied->DeltaPipePress ) }; + if ((BrakeCtrlPosition > 0) && (pressuredifference > 0.05)) mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_FB)); - else if ((BrakeCtrlPosition > 0) && (mvOccupied->Handle->GetCP() + 0.05 < mvOccupied->HighPipePress - BrakeCtrlPosition*0.25*mvOccupied->DeltaPipePress)) + else if ((BrakeCtrlPosition > 0) && (pressuredifference < -0.05)) mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_RP)); else if (BrakeCtrlPosition == 0) mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_RP)); @@ -3941,21 +4050,29 @@ void TController::SetTimeControllers() else if (BrakeCtrlPosition == -2) mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_NP)); } - if (mvOccupied->BrakeHandle == TBrakeHandle::FV4a) mvOccupied->BrakeLevelSet(BrakeCtrlPosition); - if (mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P) - { - if (BrakeCtrlPosition == 0) - mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_RP)); - else if (BrakeCtrlPosition == -1) - mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_FS)); - else if (BrakeCtrlPosition == -2) - mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_NP)); - else if (BrakeCtrlPosition > 4.5) - mvOccupied->BrakeLevelSet(10); - else if (BrakeCtrlPosition > 3.70) - mvOccupied->BrakeLevelSet(9); - else - mvOccupied->BrakeLevelSet(round((BrakeCtrlPosition * 0.4 - 0.1) / 0.15)); + if( mvOccupied->BrakeHandle == TBrakeHandle::FV4a ) { + mvOccupied->BrakeLevelSet( BrakeCtrlPosition ); + } + if( ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P ) + || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_EN57 ) ) { + if( BrakeCtrlPosition == 0 ) { + mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_RP ) ); + } + else if( BrakeCtrlPosition == -1 ) { + mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_FS ) ); + } + else if( BrakeCtrlPosition == -2 ) { + mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_NP ) ); + } + else if( BrakeCtrlPosition > 4.5 ) { + mvOccupied->BrakeLevelSet( 10 ); + } + else if( BrakeCtrlPosition > 3.70 ) { + mvOccupied->BrakeLevelSet( 9 ); + } + else { + mvOccupied->BrakeLevelSet( round( ( BrakeCtrlPosition * 0.4 - 0.1 ) / 0.15 ) ); + } } } //2. Check the type of Secondary Brake Handle @@ -4130,12 +4247,12 @@ void TController::CheckTimeControllers() if( false == AIControllFlag ) { return; } //1. Check the type of Main Brake Handle - if (mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic && mvOccupied->Handle->TimeEP) - { + if( BrakeSystem == TBrakeSystem::ElectroPneumatic && mvOccupied->Handle->TimeEP && !ForcePNBrake ) + { mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_EPN)); } - if (mvOccupied->BrakeSystem == TBrakeSystem::Pneumatic && mvOccupied->Handle->Time) - { + if( ( BrakeSystem == TBrakeSystem::Pneumatic || ForcePNBrake ) && mvOccupied->Handle->Time ) + { if (BrakeCtrlPosition > 0) mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_MB)); else @@ -5754,6 +5871,7 @@ void TController::ControllingSet() lookup->MoverParameters : mvControlling ); } + BrakeSystem = consist_brake_system(); }; std::string TController::TableText( std::size_t const Index ) const @@ -5872,7 +5990,7 @@ TController::determine_consist_state() { Ready = true; // wstępnie gotowy fReady = 0.0; // założenie, że odhamowany IsConsistBraked = ( - mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ? + ( ( mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ) && ( false == ForcePNBrake ) ) ? mvOccupied->BrakePress > 2.0 : mvOccupied->PipePress < std::max( 3.9, mvOccupied->BrakePressureActual.PipePressureVal ) + 0.1 ); fAccGravity = 0.0; // przyspieszenie wynikające z pochylenia @@ -5888,8 +6006,10 @@ TController::determine_consist_state() { auto const bp { std::max( 0.0, vehicle->BrakePress - ( vehicle->SpeedCtrlUnit.Parking ? vehicle->MaxBrakePress[ 0 ] * vehicle->StopBrakeDecc : 0.0 ) ) }; if (Ready) { // bo jak coś nie odhamowane, to dalej nie ma co sprawdzać - if( ( TestFlag( vehicle->Hamulec->GetBrakeStatus(), b_hld ) ) - || ( ( vehicle->Vel < 1.0 ) && ( bp > 0.4 ) ) ) { // ensure the brakes are sufficiently released when starting to move + if( ( TestFlagAny( vehicle->Hamulec->GetBrakeStatus(), ( b_hld | b_on ) ) ) + || ( ( vehicle->Vel < 1.0 ? + ( bp > 0.4 ) : // ensure the brakes are sufficiently released when starting to move + ( vehicle->Fb * 0.001 > 10.0 ) ) ) ) { // once in motion we can make a more lenient check Ready = false; } // Ra: odluźnianie przeładowanych lokomotyw, ciągniętych na zimno - prowizorka... @@ -6028,6 +6148,9 @@ TController::control_wheelslip() { cue_action( locale::string::driver_hint_antislip ); ++iDriverFailCount; } + else if( std::fabs( mvControlling->Im ) > 0.75 * mvControlling->ImaxHi ) { + cue_action( locale::string::driver_hint_sandingon ); // piaskujemy tory, coby się nie ślizgać + } else { // deactivate sandbox if we aren't slipping if( mvControlling->SandDose ) { @@ -6970,7 +7093,7 @@ TController::UpdateDisconnect() { else { WriteLog( "Uncoupling [" + mvOccupied->Name + "]: direction change" ); /* // TODO: test if this block is needed - if( mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ) { + if( BrakeSystem == TBrakeSystem::ElectroPneumatic ) { // wyłączenie EP, gdy wystarczy (może nie być potrzebne, bo na początku jest) mvOccupied->BrakeLevelSet( 0 ); } @@ -7159,6 +7282,7 @@ TController::pick_optimal_speed( double const Range ) { adjust_desired_speed_for_limits(); adjust_desired_speed_for_target_speed( Range ); adjust_desired_speed_for_current_speed(); + adjust_desired_speed_for_braking_test(); // Ra 2F1I: wyłączyć kiedyś to uśrednianie i przeanalizować skanowanie, czemu migocze if( AccDesired > EU07_AI_NOACCELERATION ) { // hamowania lepeiej nie uśredniać @@ -7619,6 +7743,82 @@ TController::adjust_desired_speed_for_current_speed() { } } +// hamowanie kontrolne +void +TController::adjust_desired_speed_for_braking_test() { + +// if( false == Global.DynamicBrakeTest ) { return; } + if( DynamicBrakeTest >= 5 ) { return; } // all done + if( false == is_train() ) { return; } // not applicable + if( false == TestFlag( OrderCurrentGet(), Obey_train ) ) { return; } // not applicable + if( false == AIControllFlag ) { return; } // TBD: add notification about braking test and enable it for human driver as well? + + auto const vel { DirectionalVel() }; + + switch( DynamicBrakeTest ) { + case 0: { + if( ( primary() ) + && ( vel < VelDesired ) + && ( fAccGravity >= 0.0 ) // not if going uphill + && ( AccDesired >= EU07_AI_ACCELERATION ) + && ( TrainParams.TTVmax >= 10.0 ) + && ( vel > std::min( TrainParams.TTVmax, 60.0 ) - 2.0 ) ) { + DynamicBrakeTest = 1; + DBT_BrakingTime = ElapsedTime; + DBT_VelocityBrake = vel; + } + break; + } + case 1: { + AccDesired = EU07_AI_BRAKINGTESTACCELERATION; + VelDesired = DBT_VelocityBrake; + if( ElapsedTime - DBT_BrakingTime > 1 ) { + ForcePNBrake = true; + mvOccupied->EpFuseSwitch( false ); + DynamicBrakeTest = 2; + } + break; + } + case 2: { + AccDesired = EU07_AI_BRAKINGTESTACCELERATION; + VelDesired = DBT_VelocityBrake; + if( ElapsedTime - DBT_BrakingTime > 2 ) { + DBT_BrakingTime = ElapsedTime; + DBT_VelocityBrake = vel; + DBT_VelocityRelease = vel - 8.0; + DynamicBrakeTest = 3; + } + break; + } + case 3: { + AccDesired = clamp( -AbsAccS, fAccThreshold * 1.01, fAccThreshold * 1.21 ); + VelDesired = DBT_VelocityBrake; + if( vel <= DBT_VelocityRelease ) { + DynamicBrakeTest = 4; + DBT_BrakingTime = ElapsedTime - DBT_BrakingTime; + DBT_MidPointAcc = AbsAccS; + DBT_ReleasingTime = ElapsedTime; + } + break; + } + case 4: { + if( Ready ) { + if( BrakeSystem == TBrakeSystem::ElectroPneumatic ) { + mvOccupied->EpFuseSwitch( true ); + } + ForcePNBrake = false; + DynamicBrakeTest = 5; + DBT_ReleasingTime = ElapsedTime - DBT_ReleasingTime; + DBT_VelocityFinish = vel; + } + break; + } + default: { + break; + } + } +} + void TController::control_tractive_and_braking_force() { @@ -7701,7 +7901,7 @@ void TController::control_tractive_force() { auto const velocity { DirectionalVel() }; // jeśli przyspieszenie pojazdu jest mniejsze niż żądane oraz... if( ( AccDesired > EU07_AI_NOACCELERATION ) // don't add power if not asked for actual speed-up - && (( AbsAccS < AccDesired /* - 0.05 */ ) || (mvOccupied->SpeedCtrlUnit.IsActive && velocity < mvOccupied->SpeedCtrlUnit.FullPowerVelocity)) + && (( AbsAccS < AccDesired - std::min( 0.05, 0.01 * iDriverFailCount ) ) || (mvOccupied->SpeedCtrlUnit.IsActive && velocity < mvOccupied->SpeedCtrlUnit.FullPowerVelocity)) && ( false == TestFlag( iDrivigFlags, movePress ) ) ) { // ...jeśli prędkość w kierunku czoła jest mniejsza od dozwolonej o margines... if( velocity < ( @@ -7780,7 +7980,7 @@ void TController::control_braking_force() { } } - if( is_emu() ) { + if( is_emu() && ( !ForcePNBrake ) ) { // właściwie, to warunek powinien być na działający EP // Ra: to dobrze hamuje EP w EZT auto const accthreshold { ( @@ -7896,7 +8096,7 @@ void TController::control_releaser() { if( ( mvOccupied->Vel < 1.0 ) && ( fActionTime < 0.0 ) ) { return; } // TODO: combine all releaser handling in single decision tree instead of having bits all over the place // TODO: replace handle and system conditions with flag indicating releaser presence - if( mvOccupied->BrakeSystem != TBrakeSystem::Pneumatic ) { return; } + if( BrakeSystem != TBrakeSystem::Pneumatic ) { return; } auto const hasreleaser { ( false == ( is_dmu() || is_emu() ) ) // TODO: a more refined test than rejecting these types wholesale @@ -7964,7 +8164,7 @@ void TController::control_main_pipe() { } // napełnianie uderzeniowe - if( mvOccupied->BrakeSystem != TBrakeSystem::Pneumatic ) { return; } + if( BrakeSystem != TBrakeSystem::Pneumatic ) { return; } if( ( mvOccupied->BrakeHandle == TBrakeHandle::FV4a ) || ( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_6P ) diff --git a/Driver.h b/Driver.h index 9431c1e7..7e9e4be3 100644 --- a/Driver.h +++ b/Driver.h @@ -17,7 +17,9 @@ http://mozilla.org/MPL/2.0/. #include "mtable.h" #include "translation.h" +auto const EU07_AI_ACCELERATION = 0.05; auto const EU07_AI_NOACCELERATION = -0.05; +auto const EU07_AI_BRAKINGTESTACCELERATION = -0.06; auto const EU07_AI_NOMOVEMENT = 0.05; // standstill velocity threshold auto const EU07_AI_MOVEMENT = 1.0; // deliberate movement velocity threshold auto const EU07_AI_SPEEDLIMITEXTENDSBEYONDSCANRANGE = 10000.0; @@ -61,7 +63,7 @@ enum TMovementStatus moveVisibility = 0x10000, // jazda na widoczność po przejechaniu S1 na SBL moveDoorOpened = 0x20000, // drzwi zostały otwarte - doliczyć czas na zamknięcie movePushPull = 0x40000, // zmiana czoła przez zmianę kabiny - nie odczepiać przy zmianie kierunku - moveSemaphorFound = 0x80000, // na drodze skanowania został znaleziony semafor + moveSignalFound = 0x80000, // na drodze skanowania został znaleziony semafor moveStopPointFound = 0x100000 // stop point detected ahead /* moveSemaphorWasElapsed = 0x100000, // minięty został semafor @@ -250,6 +252,7 @@ public: return ( ( mvControlling->EngineType == TEngineType::DieselElectric ) || ( mvControlling->EngineType == TEngineType::DieselEngine ) ); } + TBrakeSystem consist_brake_system() const; private: void Activation(); // umieszczenie obsady w odpowiednim członie void ControllingSet(); // znajduje człon do sterowania @@ -327,6 +330,7 @@ private: void adjust_desired_speed_for_limits(); void adjust_desired_speed_for_target_speed( double const Range ); void adjust_desired_speed_for_current_speed(); + void adjust_desired_speed_for_braking_test(); void control_tractive_and_braking_force(); void control_releaser(); void control_main_pipe(); @@ -431,6 +435,16 @@ private: double ReactionTime = 0.0; // czas reakcji Ra: czego i na co? świadomości AI double fBrakeTime = 0.0; // wpisana wartość jest zmniejszana do 0, gdy ujemna należy zmienić nastawę hamulca double BrakeChargingCooldown{}; // prevents the ai from trying to charge the train brake too frequently + TBrakeSystem BrakeSystem = TBrakeSystem::Individual; //type of main brake + bool ForcePNBrake = false; //is it necessary to use PN brake instead of EP brake + int DynamicBrakeTest = 0; //is it necessary to make brake test while driving + double DBT_VelocityBrake = 0; + double DBT_VelocityRelease = 0; + double DBT_VelocityFinish = 0; + double DBT_BrakingTime = 0; + double DBT_ReleasingTime = 0; + double DBT_MidPointAcc = 0; + int StaticBrakeTest = 0; //is it necessary to make brake test while standing double LastReactionTime = 0.0; double fActionTime = 0.0; // czas używany przy regulacji prędkości i zamykaniu drzwi double m_radiocontroltime{ 0.0 }; // timer used to control speed of radio operations diff --git a/DynObj.cpp b/DynObj.cpp index 794abab0..c68a4dda 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -2093,6 +2093,28 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" MoverParameters->dizel_HeatSet( Global.AirTemperature ); } } // temperature + else if( ( ActPar.size() >= 2 ) + && ( ActPar.front() == 'L' ) ) { + // load + ActPar.erase( 0, 1 ); + // immediately followed by max load override + // TBD: make it instead an optional sub-parameter? + { + auto const indexstart { 0 }; + auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) }; + MoverParameters->MaxLoad = std::atoi( ActPar.substr( indexstart, indexend ).c_str() ); + ActPar.erase( 0, indexend ); + } + while( false == ActPar.empty() ) { + switch( ActPar.front() ) { + default: { + // unrecognized key + ActPar.erase( 0, 1 ); + break; + } + } + } + } // load /* else if (ActPar.substr(0, 1) == "") // tu mozna wpisac inny prefiks i inne rzeczy { // jakies inne prefiksy diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index c3dc62b0..7246ab39 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1193,6 +1193,7 @@ public: int IminLo = 0; int IminHi = 0; /*prady przelacznika automatycznego rozruchu, uzywane tez przez ai_driver*/ int ImaxLo = 0; // maksymalny prad niskiego rozruchu int ImaxHi = 0; // maksymalny prad wysokiego rozruchu + bool MotorOverloadRelayHighThreshold { false }; double nmax = 0.0; /*maksymalna dop. ilosc obrotow /s*/ double InitialCtrlDelay = 0.0; double CtrlDelay = 0.0; /* -//- -//- miedzy kolejnymi poz.*/ double CtrlDownDelay = 0.0; /* -//- -//- przy schodzeniu z poz.*/ /*hunter-101012*/ @@ -1675,6 +1676,7 @@ public: bool DecBrakeLevel(); bool ChangeCab(int direction); bool CurrentSwitch(bool const State); + bool IsMotorOverloadRelayHighThresholdOn() const; void UpdateBatteryVoltage(double dt); double ComputeMovement(double dt, double dt1, const TTrackShape &Shape, TTrackParam &Track, TTractionParam &ElectricTraction, TLocation const &NewLoc, TRotation const &NewRot); //oblicza przesuniecie pojazdu double FastComputeMovement(double dt, const TTrackShape &Shape, TTrackParam &Track, TLocation const &NewLoc, TRotation const &NewRot); //oblicza przesuniecie pojazdu - wersja zoptymalizowana @@ -1841,7 +1843,7 @@ public: bool CutOffEngine(void); //odlaczenie udszkodzonych silnikow /*funkcje automatycznego rozruchu np EN57*/ - bool MaxCurrentSwitch(bool State); //przelacznik pradu wysokiego rozruchu + bool MaxCurrentSwitch(bool State, range_t const Notify = range_t::consist ); //przelacznik pradu wysokiego rozruchu bool MinCurrentSwitch(bool State); //przelacznik pradu automatycznego rozruchu bool AutoRelaySwitch(bool State); //przelacznik automatycznego rozruchu bool AutoRelayCheck();//symulacja automatycznego rozruchu diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 47b94bdc..7dc41f8b 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -674,6 +674,12 @@ TMoverParameters::CurrentSwitch(bool const State) { return false; }; +bool +TMoverParameters::IsMotorOverloadRelayHighThresholdOn() const { + + return ( ( ImaxHi > ImaxLo ) && ( Imax > ImaxLo ) ); +} + // KURS90 - sprężarka pantografów; Ra 2014-07: teraz jest to zbiornik rozrządu, chociaż to jeszcze nie tak void TMoverParameters::UpdatePantVolume(double dt) { // check the pantograph compressor while at it @@ -1419,6 +1425,23 @@ void TMoverParameters::compute_movement_( double const Deltatime ) { RunInternalCommand(); + // relay settings + if( EngineType == TEngineType::ElectricSeriesMotor ) { + // adjust motor overload relay threshold + if( ImaxHi > ImaxLo ) { + if( MotorOverloadRelayHighThreshold ) { // set high threshold + if( ( TrainType != dt_ET42 ) ? ( RList[ MainCtrlPos ].Bn < 2 ) : ( MainCtrlPos == 0 ) ) { + Imax = ImaxHi; + } + } + else { // set low threshold + if( ( TrainType != dt_ET42 ) || ( MainCtrlPos == 0 ) ) { + Imax = ImaxLo; + } + } + } + } + // automatyczny rozruch if( EngineType == TEngineType::ElectricSeriesMotor ) { if( AutoRelayCheck() ) { @@ -2233,14 +2256,12 @@ bool TMoverParameters::IncMainCtrl(int CtrlSpeed) OK = true; if( Imax == ImaxHi ) { if( RList[ MainCtrlPos ].Bn > 1 ) { +/* NOTE: disabled, relay configuration was moved to compute_movement_ if( true == MaxCurrentSwitch( false )) { // wylaczanie wysokiego rozruchu SetFlag( SoundFlag, sound::relay ); - } // Q TODO: - // if (EngineType=ElectricSeriesMotor) and (MainCtrlPos=1) - // then - // MainCtrlActualPos:=1; - // + } +*/ if( TrainType == dt_ET42 ) { --MainCtrlPos; OK = false; @@ -3957,7 +3978,7 @@ bool TMoverParameters::SwitchEPBrake(int state) if ((BrakeHandle == TBrakeHandle::St113) && (CabOccupied != 0)) { if (state > 0) - temp = Handle->GetCP(); // TODO: przetlumaczyc + temp = Handle->GetEP(); // TODO: przetlumaczyc else temp = 0; Hamulec->SetEPS(temp); @@ -4519,7 +4540,7 @@ void TMoverParameters::UpdatePipePressure(double dt) && (DirActive != 0) && (EpFuse)) // tu powinien byc jeszcze bezpiecznik EP i baterie - // temp = (Handle as TFVel6).GetCP - temp = Handle->GetCP(); + temp = Handle->GetEP(); else temp = 0.0; @@ -6571,33 +6592,23 @@ bool TMoverParameters::CutOffEngine(void) // Q: 20160713 // Przełączenie wysoki / niski prąd rozruchu // ************************************************************************************************* -bool TMoverParameters::MaxCurrentSwitch(bool State) +bool TMoverParameters::MaxCurrentSwitch(bool State, range_t const Notify ) { - bool MCS = false; - if (EngineType == TEngineType::ElectricSeriesMotor) - if (ImaxHi > ImaxLo) - { - if (State && (Imax == ImaxLo) && (RList[MainCtrlPos].Bn < 2) && - !((TrainType == dt_ET42) && (MainCtrlPos > 0))) - { + auto const initialstate { MotorOverloadRelayHighThreshold }; - Imax = ImaxHi; - MCS = true; - if (CabActive != 0) - SendCtrlToNext("MaxCurrentSwitch", 1, CabActive); - } - if (!State) + MotorOverloadRelayHighThreshold = State; - if (Imax == ImaxHi) - if (!((TrainType == dt_ET42) && (MainCtrlPos > 0))) - { - Imax = ImaxLo; - MCS = true; - if (CabActive != 0) - SendCtrlToNext("MaxCurrentSwitch", 0, CabActive); - } - } - return MCS; + if( Notify != range_t::local ) { + SendCtrlToNext( + "MaxCurrentSwitch", + ( State ? 1 : 0 ), + CabActive, + ( Notify == range_t::unit ? + coupling::control | coupling::permanent : + coupling::control ) ); + } + + return State != initialstate; } // ************************************************************************************************* @@ -6739,19 +6750,24 @@ bool TMoverParameters::AutoRelayCheck(void) } else { // zmieniaj mainctrlactualpos - if ((DirActive < 0) && (TrainType != dt_PseudoDiesel)) - if (RList[MainCtrlActualPos + 1].Bn > 1) - { + if( ( DirActive < 0 ) && ( TrainType != dt_PseudoDiesel ) ) { + if( RList[ MainCtrlActualPos + 1 ].Bn > 1 ) { return false; // nie poprawiamy przy konwersji - // return ARC;// bbylo exit; //Ra: to powoduje, że EN57 nie wyłącza się przy - // IminLo + // return ARC;// bbylo exit; //Ra: to powoduje, że EN57 nie wyłącza się przy IminLo } + } // 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 ) && ( MainCtrlActualPos < RlistSize ) ) || ( ( TrainType == dt_ET22 ) && ( DelayCtrlFlag ) ) ) ) { + // prevent switch to parallel mode if motor overload relay is set to high threshold mode + if( ( IsMotorOverloadRelayHighThresholdOn() ) + && ( RList[ MainCtrlActualPos + 1 ].Bn > 1 ) ) { + return false; + } + if( ( RList[MainCtrlPos].R == 0 ) && ( MainCtrlPos > 0 ) && ( MainCtrlPos != MainCtrlPosNo ) @@ -11994,10 +12010,10 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C DropAllPantographs( CValue1 == 1, range_t::local ); OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); } - else if (Command == "MaxCurrentSwitch") - { - OK = MaxCurrentSwitch(CValue1 == 1); - } + else if (Command == "MaxCurrentSwitch") { + MaxCurrentSwitch( CValue1 == 1, range_t::local ); + OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); + } else if (Command == "MinCurrentSwitch") { OK = MinCurrentSwitch(CValue1 == 1); diff --git a/McZapkie/hamulce.cpp b/McZapkie/hamulce.cpp index 75b498b3..b2ff9cd0 100644 --- a/McZapkie/hamulce.cpp +++ b/McZapkie/hamulce.cpp @@ -2282,6 +2282,11 @@ double TDriverHandle::GetCP() return 0; } +double TDriverHandle::GetEP() +{ + return 0; +} + double TDriverHandle::GetSound(int i) { return 0; @@ -2814,8 +2819,8 @@ double TMHZ_K5P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) void TMHZ_K5P::Init(double Press) { CP = Press; - Time = true; - TimeEP = true; + Time = true; + TimeEP = true; } void TMHZ_K5P::SetReductor(double nAdj) @@ -3133,9 +3138,11 @@ double TSt113::GetPF(double i_bcp, double PP, double HP, double dt, double ep) double ActFlowSpeed; int BCP; + CP = PP; + BCP = lround(i_bcp); - EPS = BEP_K[BCP+1]; + EPS = BEP_K[BCP]; if (BCP > 0) BCP = BCP - 1; @@ -3169,7 +3176,12 @@ double TSt113::GetPF(double i_bcp, double PP, double HP, double dt, double ep) double TSt113::GetCP() { - return EPS; + return CP; +} + +double TSt113::GetEP() +{ + return EPS; } double TSt113::GetPos(int i) @@ -3301,6 +3313,8 @@ double TFVel6::GetPF(double i_bcp, double PP, double HP, double dt, double ep) double dpMainValve; double ActFlowSpeed; + CP = PP; + LimPP = Min0R(5 * int(i_bcp < 3.5), HP); if ((i_bcp >= 3.5) && ((i_bcp < 4.3) || (i_bcp > 5.5))) ActFlowSpeed = 0; @@ -3334,7 +3348,12 @@ double TFVel6::GetPF(double i_bcp, double PP, double HP, double dt, double ep) double TFVel6::GetCP() { - return EPS; + return CP; +} + +double TFVel6::GetEP() +{ + return EPS; } double TFVel6::GetPos(int i) @@ -3366,6 +3385,8 @@ double TFVE408::GetPF(double i_bcp, double PP, double HP, double dt, double ep) double dpMainValve; double ActFlowSpeed; + CP = PP; + LimPP = Min0R(5 * int(i_bcp < 6.5), HP); if ((i_bcp >= 6.5) && ((i_bcp < 7.5) || (i_bcp > 9.5))) ActFlowSpeed = 0; @@ -3398,6 +3419,11 @@ double TFVE408::GetPF(double i_bcp, double PP, double HP, double dt, double ep) } double TFVE408::GetCP() +{ + return CP; +} + +double TFVE408::GetEP() { return EPS; } diff --git a/McZapkie/hamulce.h b/McZapkie/hamulce.h index 851c1fb0..b0d6eff4 100644 --- a/McZapkie/hamulce.h +++ b/McZapkie/hamulce.h @@ -541,6 +541,7 @@ class TDriverHandle { virtual double GetPF(double i_bcp, double PP, double HP, double dt, double ep); virtual void Init(double Press); virtual double GetCP(); + virtual double GetEP(); virtual void SetReductor(double nAdj); //korekcja pozycji reduktora cisnienia virtual double GetSound(int i); //pobranie glosnosci wybranego dzwieku virtual double GetPos(int i); //pobranie numeru pozycji o zadanym kodzie (funkcji) @@ -617,7 +618,7 @@ class TMHZ_EN57 : public TDriverHandle { double GetSound(int i)/*override*/; double GetPos(int i)/*override*/; double GetCP()/*override*/; - double GetEP(double pos); + double GetEP(double pos); void SetParams(bool AO, bool MO, double OverP, double, double OMP, double OPD); inline TMHZ_EN57(void) : TDriverHandle() @@ -762,10 +763,11 @@ class TSt113 : public TH14K1 { static double const BPT_K[/*?*/ /*-1..4*/ (4) - (-1) + 1][2]; static double const BEP_K[/*?*/ /*-1..5*/ (5) - (-1) + 1]; static double const pos_table[11]; // = {-1, 5, -1, 0, 2, 3, 4, 5, 0, 0, 1}; - + double CP = 0; public: double GetPF(double i_bcp, double PP, double HP, double dt, double ep)/*override*/; double GetCP()/*override*/; + double GetEP()/*override*/; double GetPos(int i)/*override*/; void Init(double Press)/*override*/; @@ -830,10 +832,12 @@ class TFVel6 : public TDriverHandle { private: double EPS = 0.0; static double const pos_table[ 11 ]; // = {-1, 6, -1, 0, 6, 4, 4.7, 5, -1, 0, 1}; + double CP = 0.0; public: double GetPF(double i_bcp, double PP, double HP, double dt, double ep)/*override*/; double GetCP()/*override*/; + double GetEP()/*override*/; double GetPos(int i)/*override*/; double GetSound(int i)/*override*/; void Init(double Press)/*override*/; @@ -848,10 +852,12 @@ class TFVE408 : public TDriverHandle { private: double EPS = 0.0; static double const pos_table[11]; // = {-1, 6, -1, 0, 6, 4, 4.7, 5, -1, 0, 1}; + double CP = 0.0; public: double GetPF(double i_bcp, double PP, double HP, double dt, double ep)/*override*/; double GetCP()/*override*/; + double GetEP()/*override*/; double GetPos(int i)/*override*/; double GetSound(int i)/*override*/; void Init(double Press)/*override*/; diff --git a/Train.cpp b/Train.cpp index fcd0d114..697715f2 100644 --- a/Train.cpp +++ b/Train.cpp @@ -3823,7 +3823,7 @@ void TTrain::OnCommand_motoroverloadrelaythresholdtoggle( TTrain *Train, command // only reacting to press, so the switch doesn't flip back and forth if key is held down if( ( true == Train->mvControlled->ShuntModeAllow ? ( false == Train->mvControlled->ShuntMode ) : - ( Train->mvControlled->Imax < Train->mvControlled->ImaxHi ) ) ) { + ( false == Train->mvControlled->MotorOverloadRelayHighThreshold ) ) ) { // turn on OnCommand_motoroverloadrelaythresholdsethigh( Train, Command ); } @@ -3838,10 +3838,9 @@ void TTrain::OnCommand_motoroverloadrelaythresholdsetlow( TTrain *Train, command if( Command.action == GLFW_PRESS ) { // only reacting to press, so the switch doesn't flip back and forth if key is held down - if( true == Train->mvControlled->CurrentSwitch( false ) ) { - // visual feedback - Train->ggMaxCurrentCtrl.UpdateValue( 0.0, Train->dsbSwitch ); - } + Train->mvControlled->CurrentSwitch( false ); + // visual feedback + Train->ggMaxCurrentCtrl.UpdateValue( Train->mvControlled->MotorOverloadRelayHighThreshold ? 1 : 0, Train->dsbSwitch ); } } @@ -3849,10 +3848,9 @@ void TTrain::OnCommand_motoroverloadrelaythresholdsethigh( TTrain *Train, comman if( Command.action == GLFW_PRESS ) { // only reacting to press, so the switch doesn't flip back and forth if key is held down - if( true == Train->mvControlled->CurrentSwitch( true ) ) { - // visual feedback - Train->ggMaxCurrentCtrl.UpdateValue( 1.0, Train->dsbSwitch ); - } + Train->mvControlled->CurrentSwitch( true ); + // visual feedback + Train->ggMaxCurrentCtrl.UpdateValue( Train->mvControlled->MotorOverloadRelayHighThreshold ? 1 : 0, Train->dsbSwitch ); } } @@ -7223,6 +7221,14 @@ bool TTrain::Update( double const Deltatime ) ggBrakeProfileG.Update(); ggBrakeProfileR.Update(); ggBrakeOperationModeCtrl.Update(); + ggMaxCurrentCtrl.UpdateValue( + ( true == mvControlled->ShuntModeAllow ? + ( true == mvControlled->ShuntMode ? + 1.f : + 0.f ) : + ( mvControlled->MotorOverloadRelayHighThreshold ? + 1.f : + 0.f ) ) ); ggMaxCurrentCtrl.Update(); // NBMX wrzesien 2003 - drzwi ggDoorLeftPermitButton.Update( lowvoltagepower ); @@ -8873,7 +8879,7 @@ void TTrain::set_cab_controls( int const Cab ) { ( true == mvControlled->ShuntMode ? 1.f : 0.f ) : - ( mvControlled->Imax == mvControlled->ImaxHi ? + ( mvControlled->MotorOverloadRelayHighThreshold ? 1.f : 0.f ) ) ); // lights diff --git a/driverhints.cpp b/driverhints.cpp index e9e16ce0..2a485092 100644 --- a/driverhints.cpp +++ b/driverhints.cpp @@ -783,7 +783,7 @@ TController::cue_action( locale::string const Action, float const Actionparamete case locale::string::driver_hint_trainbrakeapply: { if( AIControllFlag ) { // za radą yB ustawiamy pozycję 3 kranu (ruszanie kranem w innych miejscach powino zostać wyłączone) - if( mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ) { + if( ( BrakeSystem == TBrakeSystem::ElectroPneumatic ) && ( false == ForcePNBrake ) ) { mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_EPB ) ); } else { diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 8b44da15..10ac02e4 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -1171,7 +1171,11 @@ debug_panel::update_section_ai( std::vector &Output ) { "Brakes:\n highest pressure: " + to_string( mechanik.fReady, 2 ) + ( mechanik.Ready ? " (all brakes released)" : "" ) + "\n activation threshold: " + to_string( mechanik.fAccThreshold, 2 ) + ", delays: " + to_string( mechanik.fBrake_a0[ 0 ], 2 ) + " + " + to_string( mechanik.fBrake_a1[ 0 ], 2 ) - + "\n virtual brake position: " + to_string( mechanik.BrakeCtrlPosition, 2 ); + + "\n virtual brake position: " + to_string( mechanik.BrakeCtrlPosition, 2 ) + + "\n test stage: " + to_string( mechanik.DynamicBrakeTest ) + + ", applied at: " + to_string( mechanik.DBT_VelocityBrake, 0 ) + + ", release at: " + to_string( mechanik.DBT_VelocityRelease, 0 ) + + ", done at: " + to_string( mechanik.DBT_VelocityFinish, 0 ); Output.emplace_back( textline, Global.UITextColor ); @@ -1179,7 +1183,7 @@ debug_panel::update_section_ai( std::vector &Output ) { std::vector const drivingflagnames { "StopCloser", "StopPoint", "Active", "Press", "Connect", "Primary", "Late", "StopHere", "StartHorn", "StartHornNow", "StartHornDone", "Oerlikons", "IncSpeed", "TrackEnd", "SwitchFound", "GuardSignal", - "Visibility", "DoorOpened", "PushPull", "SemaphorFound", "StopPointFound" /*"SemaphorWasElapsed", "TrainInsideStation", "SpeedLimitFound"*/ }; + "Visibility", "DoorOpened", "PushPull", "SignalFound", "StopPointFound" /*"SemaphorWasElapsed", "TrainInsideStation", "SpeedLimitFound"*/ }; textline = "Driving flags:"; for( int idx = 0, flagbit = 1; idx < drivingflagnames.size(); ++idx, flagbit <<= 1 ) { diff --git a/utilities.h b/utilities.h index 579949e9..dd2685b4 100644 --- a/utilities.h +++ b/utilities.h @@ -90,7 +90,14 @@ std::string Now(); double CompareTime( double t1h, double t1m, double t2h, double t2m ); /*funkcje logiczne*/ -inline bool TestFlag( int const Flag, int const Value ) { return ( ( Flag & Value ) == Value ); } +inline +bool TestFlag( int const Flag, int const Value ) { + return ( ( Flag & Value ) == Value ); +} +inline +bool TestFlagAny( int const Flag, int const Value ) { + return ( ( Flag & Value ) != 0 ); +} bool SetFlag( int &Flag, int const Value); bool ClearFlag(int &Flag, int const Value); @@ -117,8 +124,8 @@ std::string to_string(double Value, int precision, int width); std::string to_hex_str( int const Value, int const width = 4 ); std::string to_minutes_str( float const Minutes, bool const Leadingzero, int const Width ); -inline std::string to_string(bool Value) { - +inline +std::string to_string(bool Value) { return ( Value == true ? "true" : "false" ); } From f09e0ba1d9442cd9d29dd7763399b7b16bc50002 Mon Sep 17 00:00:00 2001 From: stele Date: Mon, 26 Jul 2021 12:25:46 +0200 Subject: [PATCH 51/63] Logarytmiczny fade sampli dzwiekow skladanych. --- sound.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sound.cpp b/sound.cpp index 62123844..c27571af 100644 --- a/sound.cpp +++ b/sound.cpp @@ -809,12 +809,15 @@ sound_source::update_crossfade( sound_handle const Chunk ) { // chunks other than the first can have fadein auto const fadeinwidth { chunkdata.threshold - chunkdata.fadein }; if( soundpoint < chunkdata.threshold ) { - m_properties.gain *= + float lineargain = interpolate( 0.f, 1.f, clamp( ( soundpoint - chunkdata.fadein ) / fadeinwidth, 0.f, 1.f ) ); + m_properties.gain *= + lineargain / + (1 + (1 - lineargain) * (-0.57)); // approximation of logarytmic fade in return; } } @@ -826,12 +829,14 @@ sound_source::update_crossfade( sound_handle const Chunk ) { auto const fadeoutwidth { chunkdata.fadeout - m_soundchunks[ chunkindex + 1 ].second.fadein }; auto const fadeoutstart { chunkdata.fadeout - fadeoutwidth }; if( soundpoint > fadeoutstart ) { - m_properties.gain *= + float lineargain = interpolate( - 1.f, 0.f, + 0.f, 1.f, clamp( ( soundpoint - fadeoutstart ) / fadeoutwidth, 0.f, 1.f ) ); + m_properties.gain *= (-lineargain + 1) / + (1 + lineargain * (-0.57)); // approximation of logarytmic fade out return; } } From 26d09440b0fbcddddb5a1cae62c4dfbb206f7e32 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sun, 5 Sep 2021 21:09:46 +0200 Subject: [PATCH 52/63] build 210905. vehicle startup logic enhancements, whois event enhancement, line breaker activation type, material shadow rank, shadow rank cutoff value, shadow angle cutoff value, minor bug fixes --- Driver.cpp | 99 ++++++++++++++++++++++++-------------------- Driver.h | 4 +- DynObj.cpp | 23 ++++++---- Event.cpp | 37 ++++++++++++++++- Globals.cpp | 19 +++++++++ Globals.h | 2 + McZapkie/MOVER.h | 3 ++ McZapkie/Mover.cpp | 59 +++++++++++++++++++++----- Spring.cpp | 2 + Spring.h | 4 +- TractionPower.cpp | 3 ++ Train.cpp | 1 - application.cpp | 7 ++-- audiorenderer.cpp | 20 +++++++-- audiorenderer.h | 4 +- driveruipanels.cpp | 18 ++++---- material.cpp | 5 +++ material.h | 1 + mtable.cpp | 6 ++- mtable.h | 1 + opengl33renderer.cpp | 72 ++++++++++++++++++++++++-------- opengl33renderer.h | 4 +- openglrenderer.cpp | 45 ++++++++++++++++++-- openglrenderer.h | 2 + particles.cpp | 4 +- sound.cpp | 19 +++++++++ sound.h | 6 +++ version.h | 2 +- 28 files changed, 360 insertions(+), 112 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index 7e4693b3..284d84f8 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -1111,7 +1111,7 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub // nastepnie ustawić następną na aktualną tak żeby prawidłowo ją obsłużył w następnym kroku if( true == TrainParams.RewindTimeTable( Point.evEvent->input_text() ) ) { asNextStop = TrainParams.NextStop(); - iStationStart = TrainParams.StationIndex; + TrainParams.StationStart = TrainParams.StationIndex; } } else if( Point.fDist < -fLength ) { @@ -2520,6 +2520,30 @@ bool TController::CheckVehicles(TOrders user) = 0; } + if( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) { + // nastawianie hamulca do jazdy pociągowej + AutoRewident(); +/* + if( ( true == TestFlag( iDrivigFlags, moveConnect ) ) + && ( true == TestFlag( OrderCurrentGet(), Connect ) ) ) { + iCoupler = 0; // dalsza jazda manewrowa już bez łączenia + iDrivigFlags &= ~moveConnect; // zdjęcie flagi doczepiania + SetVelocity( 0, 0, stopJoin ); // wyłączyć przyspieszanie + JumpToNextOrder(); // wykonanie następnej komendy + } +*/ + } + + // detect push-pull train configurations and mark them accordingly + if( pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::control ) ) { + // zmiana czoła przez zmianę kabiny + iDrivigFlags |= movePushPull; + } + else { + // zmiana czoła przez manewry + iDrivigFlags &= ~movePushPull; + } + // HACK: ensure vehicle lights are active from the beginning, if it had pre-activated battery if( mvOccupied->LightsPosNo > 0 ) { pVehicle->SetLights(); @@ -2565,29 +2589,6 @@ bool TController::CheckVehicles(TOrders user) } } } - if( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) { - // nastawianie hamulca do jazdy pociągowej - AutoRewident(); -/* - if( ( true == TestFlag( iDrivigFlags, moveConnect ) ) - && ( true == TestFlag( OrderCurrentGet(), Connect ) ) ) { - iCoupler = 0; // dalsza jazda manewrowa już bez łączenia - iDrivigFlags &= ~moveConnect; // zdjęcie flagi doczepiania - SetVelocity( 0, 0, stopJoin ); // wyłączyć przyspieszanie - JumpToNextOrder(); // wykonanie następnej komendy - } -*/ - } - - // detect push-pull train configurations and mark them accordingly - if( pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::control ) ) { - // zmiana czoła przez zmianę kabiny - iDrivigFlags |= movePushPull; - } - else { - // zmiana czoła przez manewry - iDrivigFlags &= ~movePushPull; - } if( ( user == Connect ) || ( user == Disconnect ) ) { @@ -2840,10 +2841,12 @@ bool TController::PrepareEngine() else { // main circuit or engine is on, set up vehicle devices and controls if( false == IsAnyConverterOverloadRelayOpen ) { - cue_action( locale::string::driver_hint_converteron ); + if( IsAnyConverterPresent ) { + cue_action( locale::string::driver_hint_converteron ); + } // w EN57 sprężarka w ra jest zasilana z silnikowego // TODO: change condition to presence of required voltage type - if( IsAnyConverterEnabled ) { + if( IsAnyCompressorPresent && IsAnyConverterEnabled ) { cue_action( locale::string::driver_hint_compressoron ); } if( ( mvControlling->ScndPipePress < 4.5 ) && ( mvControlling->VeselVolume > 0.0 ) ) { @@ -2881,8 +2884,8 @@ bool TController::PrepareEngine() isready = ( false == IsAnyConverterOverloadRelayOpen ) && ( mvOccupied->DirActive != 0 ) && ( false == IsAnyLineBreakerOpen ) - && ( true == IsAnyConverterEnabled ) - && ( true == IsAnyCompressorEnabled ) + && ( ( false == IsAnyConverterPresent ) || ( true == IsAnyConverterEnabled ) ) + && ( ( false == IsAnyCompressorPresent ) || ( true == IsAnyCompressorEnabled ) ) && ( ( mvControlling->ScndPipePress > 4.5 ) || ( mvControlling->VeselVolume == 0.0 ) ) && ( ( static_cast( mvOccupied->fBrakeCtrlPos ) == static_cast( mvOccupied->Handle->GetPos( bh_RP ) ) ) || ( static_cast( mvOccupied->fBrakeCtrlPos ) != static_cast( mvOccupied->Handle->GetPos( bh_NP ) ) ) @@ -3403,13 +3406,17 @@ bool TController::IncSpeed() iDrivigFlags |= moveIncSpeed; // ustawienie flagi jazdy return false; case TEngineType::ElectricSeriesMotor: - if (mvControlling->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) // jeśli pantografujący + if (mvPantographUnit->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) // jeśli pantografujący { if (fOverhead2 >= 0.0) // a jazda bezprądowa ustawiana eventami (albo opuszczenie) return false; // to nici z ruszania if (iOverheadZero) // jazda bezprądowa z poziomu toru ustawia bity return false; // to nici z ruszania } + // TODO: move all fVoltage assignments to a single, more generic place + if( mvControlling->EnginePowerSource.SourceType == TPowerSource::Accumulator ) { + fVoltage = mvControlling->BatteryVoltage; + } if ((!IsAnyMotorOverloadRelayOpen) &&(!mvControlling->ControlPressureSwitch)) { if ((mvControlling->IsMainCtrlNoPowerPos()) @@ -3437,15 +3444,17 @@ bool TController::IncSpeed() auto const sufficientacceleration { AbsAccS >= ( IsHeavyCargoTrain ? 0.03 : IsCargoTrain ? 0.06 : 0.09 ) }; auto const seriesmodefieldshunting { ( mvControlling->ScndCtrlPos > 0 ) && ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn == 1 ) }; auto const parallelmodefieldshunting { ( mvControlling->ScndCtrlPos > 0 ) && ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) }; - auto const useseriesmodevoltage { + auto const minvoltage { ( mvPantographUnit->EnginePowerSource.SourceType == TPowerSource::CurrentCollector ? mvPantographUnit->EnginePowerSource.CollectorParameters.MinV : 0.0 ) }; + auto const maxvoltage { ( mvPantographUnit->EnginePowerSource.SourceType == TPowerSource::CurrentCollector ? mvPantographUnit->EnginePowerSource.CollectorParameters.MaxV : 0.0 ) }; + auto const seriesmodevoltage { interpolate( - mvControlling->EnginePowerSource.CollectorParameters.MinV, - mvControlling->EnginePowerSource.CollectorParameters.MaxV, + minvoltage, + maxvoltage, ( IsHeavyCargoTrain ? 0.35 : 0.40 ) ) }; auto const useseriesmode = ( ( mvControlling->Imax > mvControlling->ImaxLo ) || ( true == usehighoverloadrelaythreshold ) - || ( fVoltage < useseriesmodevoltage ) + || ( fVoltage < seriesmodevoltage ) || ( ( true == sufficientacceleration ) && ( true == sufficienttractionforce ) && ( mvOccupied->Vel <= ( IsCargoTrain ? 40 : 30 ) + ( seriesmodefieldshunting ? 5 : 0 ) - ( ( fAccGravity < -0.025 ) ? 10 : 0 ) ) ) ); @@ -3501,7 +3510,7 @@ bool TController::IncSpeed() if( usefieldshunting ) { // to dać bocznik // engage the shuntfield only if there's sufficient power margin to draw from - auto const sufficientpowermargin { fVoltage - useseriesmodevoltage > ( IsHeavyCargoTrain ? 100.0 : 75.0 ) * ControlledEnginesCount }; + auto const sufficientpowermargin { fVoltage - seriesmodevoltage > ( IsHeavyCargoTrain ? 100.0 : 75.0 ) * ControlledEnginesCount }; OK = ( sufficientpowermargin ? @@ -3519,8 +3528,8 @@ bool TController::IncSpeed() auto const sufficientpowermargin { fVoltage - ( mvControlling->RList[ std::min( mvControlling->MainCtrlPos + 1, mvControlling->MainCtrlPosNo ) ].Bn == 1 ? - mvControlling->EnginePowerSource.CollectorParameters.MinV : - useseriesmodevoltage ) + minvoltage : + seriesmodevoltage ) > ( IsHeavyCargoTrain ? 100.0 : 75.0 ) * ControlledEnginesCount }; OK = ( @@ -4517,7 +4526,7 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N TrainParams.UpdateMTable( simulation::Time, TrainParams.NextStationName ); TrainParams.StationIndexInc(); // przejście do następnej - iStationStart = TrainParams.StationIndex; + TrainParams.StationStart = TrainParams.StationIndex; asNextStop = TrainParams.NextStop(); m_lastannouncement = announcement_t::idle; iDrivigFlags |= movePrimary; // skoro dostał rozkład, to jest teraz głównym @@ -6063,10 +6072,10 @@ TController::determine_consist_state() { IsAnyMotorOverloadRelayOpen = false; IsAnyGroundRelayOpen = false; IsAnyLineBreakerOpen = false; - // HACK: enable a make-believe compressor in all cars - // TBD, TODO: replace with a more flexible vehicle readiness check in PrepareEngine() - IsAnyCompressorEnabled = is_car(); + IsAnyCompressorPresent = false; + IsAnyCompressorEnabled = false; IsAnyCompressorExplicitlyEnabled = false; + IsAnyConverterPresent = false; IsAnyConverterEnabled = false; IsAnyConverterExplicitlyEnabled = false; @@ -6077,8 +6086,10 @@ TController::determine_consist_state() { IsAnyConverterOverloadRelayOpen |= vehicle->ConvOvldFlag; IsAnyMotorOverloadRelayOpen |= vehicle->FuseFlag; IsAnyGroundRelayOpen |= !( vehicle->GroundRelay ); + IsAnyCompressorPresent |= ( vehicle->CompressorSpeed > 0.0 ); IsAnyCompressorEnabled |= ( vehicle->CompressorSpeed > 0.0 ? ( vehicle->CompressorAllow || vehicle->CompressorStart == start_t::automatic ) && ( vehicle->CompressorAllowLocal ) : false ); IsAnyCompressorExplicitlyEnabled |= ( vehicle->CompressorSpeed > 0.0 ? ( vehicle->CompressorAllow && vehicle->CompressorAllowLocal ) : false ); + IsAnyConverterPresent |= ( vehicle->ConverterStart != start_t::disabled ); IsAnyConverterEnabled |= ( vehicle->ConverterAllow || vehicle->ConverterStart == start_t::automatic ) && ( vehicle->ConverterAllowLocal ); IsAnyConverterExplicitlyEnabled |= ( vehicle->ConverterAllow && vehicle->ConverterAllowLocal ); if( vehicle->Power > 0.01 ) { @@ -6134,8 +6145,8 @@ TController::determine_consist_state() { iEngineActive &= ( false == IsAnyConverterOverloadRelayOpen ) && ( false == IsAnyLineBreakerOpen ) - && ( true == IsAnyConverterEnabled ) - && ( true == IsAnyCompressorEnabled ); + && ( ( false == IsAnyConverterPresent ) || ( true == IsAnyConverterEnabled ) ) + && ( ( false == IsAnyCompressorPresent ) || ( true == IsAnyCompressorEnabled ) ); } void @@ -6513,7 +6524,7 @@ TController::UpdateNextStop() { if( ( fLastStopExpDist > 0.0 ) && ( mvOccupied->DistCounter > fLastStopExpDist ) ) { // zaktualizować wyświetlanie rozkładu - iStationStart = TrainParams.StationIndex; + TrainParams.StationStart = TrainParams.StationIndex; fLastStopExpDist = -1.0; // usunąć licznik if( true == m_makenextstopannouncement ) { announce( announcement_t::next ); @@ -7901,7 +7912,7 @@ void TController::control_tractive_force() { auto const velocity { DirectionalVel() }; // jeśli przyspieszenie pojazdu jest mniejsze niż żądane oraz... if( ( AccDesired > EU07_AI_NOACCELERATION ) // don't add power if not asked for actual speed-up - && (( AbsAccS < AccDesired - std::min( 0.05, 0.01 * iDriverFailCount ) ) || (mvOccupied->SpeedCtrlUnit.IsActive && velocity < mvOccupied->SpeedCtrlUnit.FullPowerVelocity)) + && (( AbsAccS < AccDesired /*- std::min( 0.05, 0.01 * iDriverFailCount )*/ ) || (mvOccupied->SpeedCtrlUnit.IsActive && velocity < mvOccupied->SpeedCtrlUnit.FullPowerVelocity)) && ( false == TestFlag( iDrivigFlags, movePress ) ) ) { // ...jeśli prędkość w kierunku czoła jest mniejsza od dozwolonej o margines... if( velocity < ( diff --git a/Driver.h b/Driver.h index 7e9e4be3..eeedc8cd 100644 --- a/Driver.h +++ b/Driver.h @@ -549,7 +549,7 @@ private: // members Mtable::TTrainParameters TrainParams; // rozkład jazdy zawsze jest, nawet jeśli pusty std::string asNextStop; // nazwa następnego punktu zatrzymania wg rozkładu - int iStationStart = 0; // numer pierwszej stacji pokazywanej na podglądzie rozkładu +// int iStationStart = 0; // numer pierwszej stacji pokazywanej na podglądzie rozkładu std::string m_lastexchangestop; // HACK: safeguard to prevent multiple load exchanges per station int m_lastexchangeplatforms { 0 }; // cached station platforms for last exchange int m_lastexchangedirection { 0 }; // @@ -595,8 +595,10 @@ private: bool IsAnyConverterOverloadRelayOpen{ false }; // state of converter overload relays in all vehicles under control bool IsAnyMotorOverloadRelayOpen{ false }; // state of motor overload relays in all vehicles under control bool IsAnyGroundRelayOpen{ false }; + bool IsAnyCompressorPresent { false }; bool IsAnyCompressorEnabled{ false }; bool IsAnyCompressorExplicitlyEnabled{ false }; // only takes into account manually controlled devices + bool IsAnyConverterPresent{ false }; bool IsAnyConverterEnabled{ false }; bool IsAnyConverterExplicitlyEnabled{ false }; // only takes into account manually controlled devices bool IsAnyCouplerStretched{ false }; // whether there's a coupler in the consist stretched above limit diff --git a/DynObj.cpp b/DynObj.cpp index c68a4dda..ced62243 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -2859,7 +2859,7 @@ TDynamicObject::update_load_visibility() { auto visiblechunkcount { ( SectionLoadOrder.empty() ? 0 : - static_cast( std::ceil( loadpercentage * SectionLoadOrder.size() ) ) ) }; + static_cast( std::ceil( loadpercentage * SectionLoadOrder.size() - 0.001f ) ) ) }; for( auto *section : SectionLoadOrder ) { if( visiblechunkcount == 0 ) { break; } section->load_chunks_visible++; @@ -5620,10 +5620,10 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co blowertemplate.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); blowertemplate.owner( this ); - auto const amplitudedivisor = static_cast( - MoverParameters->MotorBlowers[ end::front ].speed > 0.f ? - MoverParameters->MotorBlowers[ end::front ].speed * MoverParameters->nmax * 60 + MoverParameters->Power * 3 : - MoverParameters->MotorBlowers[ end::front ].speed * -1 ); + auto const amplitudedivisor { static_cast( + MoverParameters->MotorBlowers[ end::front ].speed > 0 ? MoverParameters->MotorBlowers[ end::front ].speed * MoverParameters->nmax * 60 + MoverParameters->Power * 3 : + blowertemplate.has_bookends() ? 1 : // NOTE: for motorblowers with fixed speed if the sound has defined bookends we skip revolutions-based part of frequency/volume adjustments + MoverParameters->MotorBlowers[ end::front ].speed * -1 ) }; blowertemplate.m_amplitudefactor /= amplitudedivisor; blowertemplate.m_frequencyfactor /= amplitudedivisor; @@ -6031,6 +6031,11 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co >> soundproofing[ 3 ] >> soundproofing[ 4 ] >> soundproofing[ 5 ]; + for( auto & soundproofingelement : soundproofing ) { + if( soundproofingelement != -1.f ) { + soundproofingelement = std::sqrtf( clamp( soundproofingelement, 0.f, 1.f ) ); + } + } m_pasystem.soundproofing = soundproofing; continue; } @@ -7890,14 +7895,16 @@ TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, doub for( auto &blowersound : motorblowers ) { // match the motor blower and the sound source based on whether they're located in the front or the back of the vehicle auto const &blower { Vehicle.MotorBlowers[ ( blowersound.offset().z > 0 ? end::front : end::rear ) ] }; + // TODO: for the sounds with provided bookends invoke stop() when the stop is triggered and revolutions start dropping, instead of after full stop if( blower.revolutions > 1 ) { - + // NOTE: for motorblowers with fixed speed if the sound has defined bookends we skip revolutions-based part of frequency/volume adjustments + auto const revolutionmodifier { ( Vehicle.MotorBlowers[ end::front ].speed < 0.f ) && ( blowersound.has_bookends() ) ? 1.f : blower.revolutions }; blowersound .pitch( true == blowersound.is_combined() ? blower.revolutions * 0.01f : - blowersound.m_frequencyoffset + blowersound.m_frequencyfactor * blower.revolutions ) - .gain( blowersound.m_amplitudeoffset + blowersound.m_amplitudefactor * blower.revolutions ) + blowersound.m_frequencyoffset + blowersound.m_frequencyfactor * revolutionmodifier ) + .gain( blowersound.m_amplitudeoffset + blowersound.m_amplitudefactor * revolutionmodifier ) .play( sound_flags::exclusive | sound_flags::looping ); } else { diff --git a/Event.cpp b/Event.cpp index eb321f8d..3ee6002d 100644 --- a/Event.cpp +++ b/Event.cpp @@ -1018,6 +1018,7 @@ whois_event::run_() { auto *targetcell { static_cast( std::get( 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 @@ -1025,6 +1026,35 @@ whois_event::run_() { // +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 { ( + ( ( m_activator->Mechanik != nullptr ) && ( m_activator->Mechanik->primary() ) ) ? + m_activator->Mechanik : + m_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 (" + to_string( m_input.flags ) + ") - " + + "[next station: " + nextstop + "], " + + "[X], " + + "[stop at next station: " + ( isstop != 0 ? "yes" : "no" ) + "]" ); + } + // vehicle name + else { targetcell->UpdateValues( m_activator->asName, // vehicle name 0, // unused @@ -1036,6 +1066,7 @@ whois_event::run_() { + "[name: " + m_activator->asName + "], " + "[X], " + "[X]" ); + } } else if( m_input.flags & flags::whois_load ) { // +16 or +24 @@ -1106,12 +1137,12 @@ whois_event::run_() { m_activator->Mechanik->IsStop() ? 1 : 0, // 1, gdy ma tu zatrzymanie - m_input.flags ); + m_input.flags & ( flags::text | flags::value1 | flags::value2 ) ); WriteLog( "Type: WhoIs (" + to_string( m_input.flags ) + ") - " + "[train: " + m_activator->Mechanik->TrainName() + "], " + "[stations left: " + to_string( m_activator->Mechanik->StationCount() - m_activator->Mechanik->StationIndex() ) + "], " - + "[stop at next: " + ( m_activator->Mechanik->IsStop() ? "yes" : "no") + "]" ); + + "[stop at next station: " + ( m_activator->Mechanik->IsStop() ? "yes" : "no") + "]" ); } } } @@ -2331,6 +2362,8 @@ event_manager::AddToQuery( basic_event *Event, TDynamicObject const *Owner ) { + Event->m_delaydeparture; } } + // NOTE: sanity check, as departure-based delay math can potentially produce negative overall delay + Event->m_launchtime = std::max( Event->m_launchtime, 0.0 ); if( QueryRootEvent != nullptr ) { basic_event *target { QueryRootEvent }; basic_event *previous { nullptr }; diff --git a/Globals.cpp b/Globals.cpp index e2bb3231..1e0bf3eb 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -1014,6 +1014,23 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token Parser >> gfx_distance_factor_max; gfx_distance_factor_max = clamp(gfx_distance_factor_max, 1.f, 3.f); } + else if (Token == "gfx.shadow.angle.min") + { + Parser.getTokens(1); + Parser >> gfx_shadow_angle_min; + // variable internally uses negative values, but is presented as positive in settings + // so it's likely it'll be supplied as positive number by external launcher + if( gfx_shadow_angle_min > 0 ) { + gfx_shadow_angle_min *= -1; + } + gfx_shadow_angle_min = clamp(gfx_shadow_angle_min, -1.f, -0.2f); + } + else if (Token == "gfx.shadow.rank.cutoff") + { + Parser.getTokens(1); + Parser >> gfx_shadow_rank_cutoff; + gfx_shadow_rank_cutoff = clamp(gfx_shadow_rank_cutoff, 1, 3); + } else { tokenparsed = false; @@ -1236,6 +1253,8 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.extraeffects", gfx_extraeffects ); export_as_text( Output, "gfx.shadergamma", gfx_shadergamma ); export_as_text( Output, "gfx.drawrange.factor.max", gfx_distance_factor_max ); + export_as_text( Output, "gfx.shadow.angle.min", gfx_shadow_angle_min ); + export_as_text( Output, "gfx.shadow.rank.cutoff", gfx_shadow_rank_cutoff ); export_as_text( Output, "python.enabled", python_enabled ); export_as_text( Output, "python.threadedupload", python_threadedupload ); export_as_text( Output, "python.uploadmain", python_uploadmain ); diff --git a/Globals.h b/Globals.h index cec2e220..c71336b6 100644 --- a/Globals.h +++ b/Globals.h @@ -229,6 +229,8 @@ struct global_settings { bool gfx_shadergamma = false; bool gfx_usegles = false; float gfx_distance_factor_max { 3.f }; + float gfx_shadow_angle_min { -0.2f }; + int gfx_shadow_rank_cutoff { 3 }; std::string exec_on_exit; diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 7246ab39..895a2c4b 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1364,6 +1364,7 @@ public: std::array Neighbours; // potential collision sources bool Power110vIsAvailable = false; // cached availability of 110v power bool Power24vIsAvailable = false; // cached availability of 110v power + double Power24vVoltage { 0.0 }; // cached battery voltage bool EventFlag = false; /*!o true jesli cos nietypowego sie wydarzy*/ int SoundFlag = 0; /*!o patrz stale sound_ */ int AIFlag{ 0 }; // HACK: events of interest for consist owner @@ -1489,6 +1490,7 @@ public: bool Mains = false; /*polozenie glownego wylacznika*/ 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 + start_t MainsStart { start_t::manual }; bool LineBreakerClosesOnlyAtNoPowerPos{ false }; bool ControlPressureSwitch{ false }; // activates if the main pipe and/or brake cylinder pressure aren't within operational levels bool HasControlPressureSwitch{ true }; @@ -1811,6 +1813,7 @@ public: bool ChangeCompressorPreset( int const Change, range_t const Notify = range_t::consist ); bool HeatingSwitch( bool const State, range_t const Notify = range_t::consist ); void HeatingSwitch_( bool const State ); + double EnginePowerSourceVoltage() const; // returns voltage of defined main engine power source /*-funkcje typowe dla lokomotywy elektrycznej*/ void LowVoltagePowerCheck( double const Deltatime ); diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 7dc41f8b..3d6a6a69 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -171,6 +171,10 @@ double TMoverParameters::Current(double n, double U) } else { Mn = RList[ MainCtrlActualPos ].Mn * RList[ MainCtrlActualPos ].Bn; + if( RList[ MainCtrlActualPos ].Bn > 1 ) { + Bn = 1; + R = CircuitRes; + } } if (DynamicBrakeFlag && (!FuseFlag) && (DynamicBrakeType == dbrake_automatic) && @@ -1511,13 +1515,14 @@ void TMoverParameters::compute_movement_( double const Deltatime ) { PowerCouplersCheck( Deltatime, coupling::power110v ); PowerCouplersCheck( Deltatime, coupling::power24v ); - Power24vIsAvailable = ( ( PowerCircuits[ 0 ].first > 0 ) || ( GetTrainsetVoltage( coupling::power24v ) > 0 ) ); + Power24vVoltage = std::max( PowerCircuits[ 0 ].first, GetTrainsetVoltage( coupling::power24v ) ); + Power24vIsAvailable = ( Power24vVoltage > 0 ); Power110vIsAvailable = ( ( PowerCircuits[ 1 ].first > 0 ) || ( GetTrainsetVoltage( coupling::power110v ) > 0 ) ); } void TMoverParameters::MainsCheck( double const Deltatime ) { - if( MainsInitTime == 0.0 ) { return; } +// if( MainsInitTime == 0.0 ) { return; } // TBD, TODO: move voltage calculation to separate method and use also in power coupler state calculation? auto localvoltage { 0.0 }; @@ -1529,6 +1534,13 @@ void TMoverParameters::MainsCheck( double const Deltatime ) { PantographVoltage ); break; } + case TPowerSource::Accumulator: { + localvoltage = + std::max( + localvoltage, + Power24vVoltage ); + break; + } default: { break; } @@ -1544,6 +1556,13 @@ void TMoverParameters::MainsCheck( double const Deltatime ) { // this allows for simpler rejection of cases where MainsInitTime == 0 MainsInitTimeCountdown -= Deltatime; } + else { + // optional automatic circuit start + if( ( MainsStart != start_t::manual ) + && ( false == ( Mains || dizel_startup ) ) ) { + MainSwitch( true ); + } + } } else { // no power supply @@ -2039,9 +2058,11 @@ void TMoverParameters::MotorBlowersCheck( double const Timestep ) { // activation check for( auto &blower : MotorBlowers ) { auto disable = blower.is_disabled; + auto const start { ( Vel >= blower.min_start_velocity && Im > 0.5 ) }; + auto const stop { ( Vel < 0.5 && Im < 0.5 ) }; if (blower.min_start_velocity >= 0) { - if ( Vel < 0.5 && Im < 0.5 ) + if ( stop ) { blower.stop_timer += Timestep; if (blower.stop_timer > blower.sustain_time) @@ -2049,7 +2070,7 @@ void TMoverParameters::MotorBlowersCheck( double const Timestep ) { disable = true; } } - else if (Vel >= blower.min_start_velocity && Im > 0.5) + else if ( start ) { blower.stop_timer = 0; } @@ -2067,9 +2088,10 @@ void TMoverParameters::MotorBlowersCheck( double const Timestep ) { // && ( true == blower.breaker ) && ( false == disable) && ( ( true == blower.is_active ) - || ( blower.start_type == start_t::manual ? + || ( ( blower.stop_timer == 0.f ) // HACK: will be true for blower with exceeded start_velocity, and for one without start_velocity + && ( blower.start_type == start_t::manual ? blower.is_enabled : - true ) ) ); + true ) ) ) ); } // update for( auto &fan : MotorBlowers ) { @@ -3570,7 +3592,8 @@ bool TMoverParameters::MainSwitchCheck() const { } case TEngineType::ElectricSeriesMotor: case TEngineType::ElectricInductionMotor: { - powerisavailable = ( std::max( GetTrainsetHighVoltage(), PantographVoltage ) > 0.5 * EnginePowerSource.MaxVoltage ); + // TODO: check whether we can simplify this check and skip the outer EngineType switch + powerisavailable = ( EnginePowerSourceVoltage() > 0.5 * EnginePowerSource.MaxVoltage ); break; } default: { @@ -3692,6 +3715,15 @@ void TMoverParameters::HeatingSwitch_( bool const State ) { HeatingAllow = State; } +// returns voltage of defined main engine power source +double TMoverParameters::EnginePowerSourceVoltage() const { + + return ( + EnginePowerSource.SourceType == TPowerSource::CurrentCollector ? std::max( GetTrainsetHighVoltage(), PantographVoltage ) : + EnginePowerSource.SourceType == TPowerSource::Accumulator ? Power24vVoltage : + 0.0 ); +} + // ************************************************************************************************* // Q: 20160711 // zwiększenie nastawy hamulca @@ -4892,10 +4924,8 @@ void TMoverParameters::ComputeTotalForce(double dt) { { // Ra 2014-03: uwzględnienie kierunku jazdy w napięciu na silnikach, a powinien być zdefiniowany nawrotnik EngineVoltage = ( Mains ? - std::max( - GetTrainsetHighVoltage(), - PantographVoltage ) : - 0.00 ); + EnginePowerSourceVoltage() : + 0.0 ); if( CabActive == 0 ) { EngineVoltage *= DirActive; } @@ -10360,6 +10390,13 @@ void TMoverParameters::LoadFIZ_Cntrl( std::string const &line ) { // main circuit extract_value( MainsInitTime, "MainInitTime", line, "" ); + { + auto lookup = starts.find( extract_value( "MainStart", line ) ); + MainsStart = + lookup != starts.end() ? + lookup->second : + start_t::manual; + } // battery { auto lookup = starts.find( extract_value( "BatteryStart", line ) ); diff --git a/Spring.cpp b/Spring.cpp index 587be4f7..da955280 100644 --- a/Spring.cpp +++ b/Spring.cpp @@ -13,6 +13,8 @@ http://mozilla.org/MPL/2.0/. void TSpring::Init(double nKs, double nKd) { Ks = nKs; Kd = nKd; + ks = Ks; + kd = Kd; } Math3D::vector3 TSpring::ComputateForces( Math3D::vector3 const &pPosition1, Math3D::vector3 const &pPosition2) { diff --git a/Spring.h b/Spring.h index 30fd0a1b..761bff53 100644 --- a/Spring.h +++ b/Spring.h @@ -29,11 +29,13 @@ public: // double nrestLen= -1.0f); void Init(double nKs = 0.5f, double nKd = 0.002f); Math3D::vector3 ComputateForces( Math3D::vector3 const &pPosition1, Math3D::vector3 const &pPosition2); -private: +//private: // members double restLen { 0.01 }; // LENGTH OF SPRING AT REST double Ks { 0.0 }; // SPRING CONSTANT double Kd { 0.0 }; // SPRING DAMPING + float ks{ 0.f }; + float kd{ 0.f }; }; //--------------------------------------------------------------------------- diff --git a/TractionPower.cpp b/TractionPower.cpp index 244151a4..00b17635 100644 --- a/TractionPower.cpp +++ b/TractionPower.cpp @@ -74,6 +74,9 @@ bool TTractionPowerSource::Load(cParser *parser) { bool TTractionPowerSource::Update(double dt) { // powinno być wykonane raz na krok fizyki // iloczyn napięcia i admitancji daje prąd + if( FastFuse || SlowFuse ) { + TotalCurrent = 0.0; + } if (TotalCurrent > MaxOutputCurrent) { FastFuse = true; diff --git a/Train.cpp b/Train.cpp index 697715f2..d662c574 100644 --- a/Train.cpp +++ b/Train.cpp @@ -758,7 +758,6 @@ dictionary_source *TTrain::GetTrainState( dictionary_source const &Extraparamete dict->insert( "actualproximitydist", driver->ActualProximityDist ); // train data driver->TrainTimetable().serialize( dict ); - dict->insert( "train_stationstart", driver->iStationStart ); dict->insert( "train_atpassengerstop", driver->IsAtPassengerStop ); dict->insert( "train_length", driver->fLength ); // world state data diff --git a/application.cpp b/application.cpp index e734a3a5..378f69cd 100644 --- a/application.cpp +++ b/application.cpp @@ -329,9 +329,10 @@ eu07_application::run() { if (m_network) m_network->update(); - auto frametime = Timer::subsystem.mainloop_total.stop(); - if (Global.minframetime.count() != 0.0f && (Global.minframetime - frametime).count() > 0.0f) - std::this_thread::sleep_for(Global.minframetime - frametime); + auto const frametime{ Timer::subsystem.mainloop_total.stop() }; + if( Global.minframetime.count() != 0.0f && ( Global.minframetime - frametime ).count() > 0.0f ) { + std::this_thread::sleep_for( Global.minframetime - frametime ); + } } return 0; diff --git a/audiorenderer.cpp b/audiorenderer.cpp index 4a729d6e..b6854e6f 100644 --- a/audiorenderer.cpp +++ b/audiorenderer.cpp @@ -95,15 +95,29 @@ openal_source::update( double const Deltatime, glm::vec3 const &Listenervelocity sound_change = false; ::alGetSourcei( id, AL_BUFFERS_PROCESSED, &sound_index ); - // for multipart sounds trim away processed sources until only one remains, the last one may be set to looping by the controller + // for multipart sounds trim away processed buffers until only one remains, the last one may be set to looping by the controller // TBD, TODO: instead of change flag move processed buffer ids to separate queue, for accurate tracking of longer buffer sequences - ALuint bufferid; + ALuint discard; while( ( sound_index > 0 ) && ( sounds.size() > 1 ) ) { - ::alSourceUnqueueBuffers( id, 1, &bufferid ); + ::alSourceUnqueueBuffers( id, 1, &discard ); sounds.erase( std::begin( sounds ) ); --sound_index; sound_change = true; + // potentially adjust starting point of the last buffer (to reduce chance of reverb effect with multiple, looping copies playing) + if( ( controller->start() > 0.f ) && ( sounds.size() == 1 ) ) { + ALint bufferid; + ::alGetSourcei( + id, + AL_BUFFER, + &bufferid ); + ALint buffersize; + ::alGetBufferi( bufferid, AL_SIZE, &buffersize ); + ::alSourcei( + id, + AL_SAMPLE_OFFSET, + static_cast( controller->start() * ( buffersize / sizeof( std::int16_t ) ) ) ); + } } int state; diff --git a/audiorenderer.h b/audiorenderer.h index 036ee7d3..580792b5 100644 --- a/audiorenderer.h +++ b/audiorenderer.h @@ -197,7 +197,9 @@ openal_source::bind( sound_source *Controller, uint32_sequence Sounds, Iterator_ ::alSourceQueueBuffers( id, static_cast( buffers.size() ), buffers.data() ); ::alSourceRewind( id ); // sound controller can potentially request playback to start from certain buffer point - if( controller->start() == 0.f ) { + // for multipart sounds the offset is applied only to last piece during playback + // for single sound we also make sure not to apply the offset to optional bookends + if( controller->start() == 0.f || is_multipart || controller->is_bookend( buffers.front() ) ) { // regular case with no offset, reset bound source just in case ::alSourcei( id, AL_SAMPLE_OFFSET, 0 ); } diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 10ac02e4..f4b0d092 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -359,12 +359,12 @@ timetable_panel::update() { if( is_expanded ) { - if( vehicle->MoverParameters->CategoryFlag == 1 ) { + if( owner->is_train() ) { // consist data auto consistmass { owner->fMass }; auto consistlength { owner->fLength }; - if( ( owner->mvControlling->TrainType != dt_DMU ) - && ( owner->mvControlling->TrainType != dt_EZT ) ) { + if( ( false == owner->is_dmu() ) + && ( false == owner->is_emu() ) ) { //odejmij lokomotywy czynne, a przynajmniej aktualną consistmass -= owner->pVehicle->MoverParameters->TotalMass; // subtract potential other half of a two-part vehicle @@ -392,12 +392,12 @@ timetable_panel::update() { m_tablelines.emplace_back( u8"┌─────┬────────────────────────────────────┬─────────┬─────┐", Global.UITextColor ); TMTableLine const *tableline; - for( int i = owner->iStationStart; i <= table.StationCount; ++i ) { + for( int i = table.StationStart; i <= table.StationCount; ++i ) { // wyświetlenie pozycji z rozkładu tableline = table.TimeTable + i; // linijka rozkładu bool vmaxchange { true }; - if( i > owner->iStationStart ) { + if( i > table.StationStart ) { auto const *previoustableline { tableline - 1 }; if( tableline->vmax == previoustableline->vmax ) { vmaxchange = false; @@ -427,7 +427,7 @@ timetable_panel::update() { to_string( int( 100 + tableline->Dh ) ).substr( 1, 2 ) + ":" + to_minutes_str( tableline->Dm, true, 3 ) : u8" │ " ) }; auto const candepart { ( - ( owner->iStationStart < table.StationIndex ) + ( table.StationStart < table.StationIndex ) && ( i < table.StationIndex ) && ( ( tableline->Ah < 0 ) // pass-through, always valid || ( tableline->is_maintenance ) // maintenance stop, always valid @@ -439,7 +439,7 @@ timetable_panel::update() { tableline->Ah >= 0 ? to_minutes_str( CompareTime( table.TimeTable[ i - 1 ].Dh, table.TimeTable[ i - 1 ].Dm, tableline->Ah, tableline->Am ), false, 3 ) : to_minutes_str( std::max( 0.0, CompareTime( table.TimeTable[ i - 1 ].Dh, table.TimeTable[ i - 1 ].Dm, tableline->Dh, tableline->Dm ) - 0.5 ), false, 3 ) ) }; auto const linecolor { ( - ( i != owner->iStationStart ) ? Global.UITextColor : + ( i != table.StationStart ) ? Global.UITextColor : loadchangeinprogress ? colors::uitextred : candepart ? colors::uitextgreen : // czas minął i odjazd był, to nazwa stacji będzie na zielono isatpassengerstop ? colors::uitextorange : @@ -1425,6 +1425,10 @@ debug_panel::render_section_settings() { ImGui::PopStyleColor(); // reflection fidelity ImGui::SliderInt( ( to_string( Global.reflectiontune.fidelity ) + "###reflectionfidelity" ).c_str(), &Global.reflectiontune.fidelity, 0, 2, "Reflection fidelity" ); + ImGui::SliderInt( ( to_string( Global.gfx_shadow_rank_cutoff ) + "###shadowrankcutoff" ).c_str(), &Global.gfx_shadow_rank_cutoff, 1, 3, "Shadow ranks" ); + if( ImGui::SliderFloat( ( to_string( std::abs( Global.gfx_shadow_angle_min ), 2 ) + "###shadowanglecutoff" ).c_str(), &Global.gfx_shadow_angle_min, -1.0, -0.2, "Shadow angle cutoff" ) ) { + Global.gfx_shadow_angle_min = quantize( Global.gfx_shadow_angle_min, 0.05f ); + }; if( DebugModeFlag ) { // sky sliders { diff --git a/material.cpp b/material.cpp index 06a9634f..e3a6a7c0 100644 --- a/material.cpp +++ b/material.cpp @@ -391,6 +391,11 @@ opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool c glossiness = std::stof(value); //m7t: handle exception m_glossiness_priority = Priority; } + else if (key == "shadow_rank:") + { + auto const value { deserialize_random_set( Input ) }; + shadow_rank = std::stof(value); //m7t: handle exception + } else if( key == "size:" ) { Input.getTokens( 2 ); Input diff --git a/material.h b/material.h index 7b77d26f..c14d9c0e 100644 --- a/material.h +++ b/material.h @@ -27,6 +27,7 @@ struct opengl_material { std::optional opacity; std::optional selfillum; float glossiness { 10.f }; + int shadow_rank { 0 }; // priority as shadow caster; higher = more likely to be skipped std::string name; glm::vec2 size { -1.f, -1.f }; // 'physical' size of bound texture, in meters diff --git a/mtable.cpp b/mtable.cpp index 09d04abd..54279d23 100644 --- a/mtable.cpp +++ b/mtable.cpp @@ -184,10 +184,10 @@ bool TTrainParameters::IsTimeToGo(double hh, double mm) // returns: difference between specified time and scheduled departure from current stop, in seconds double TTrainParameters::seconds_until_departure( double const Hour, double const Minute ) const { - if( ( TimeTable[ StationIndex ].Ah < 0 ) ) { // passthrough + if( ( TimeTable[ StationStart ].Ah < 0 ) ) { // passthrough return 0; } - return ( 60.0 * CompareTime( Hour, Minute, TimeTable[ StationIndex ].Dh, TimeTable[ StationIndex ].Dm ) ); + return ( 60.0 * CompareTime( Hour, Minute, TimeTable[ StationStart ].Dh, TimeTable[ StationStart ].Dm ) ); } std::string TTrainParameters::ShowRelation() const @@ -212,6 +212,7 @@ void TTrainParameters::NewName(std::string const &NewTrainName) TrainName = NewTrainName; StationCount = 0; StationIndex = 0; + StationStart = 0; NextStationName = "nowhere"; LastStationLatency = 0; Direction = 1; @@ -649,6 +650,7 @@ void TTrainParameters::serialize( dictionary_source *Output ) const { Output->insert( "train_stationto", Relation2 ); Output->insert( "train_stationindex", StationIndex ); Output->insert( "train_stationcount", StationCount ); + Output->insert( "train_stationstart", StationStart ); if( StationCount > 0 ) { // timetable stations data, if there's any for( auto stationidx = 1; stationidx <= StationCount; ++stationidx ) { diff --git a/mtable.h b/mtable.h index d5aea177..15b66940 100644 --- a/mtable.h +++ b/mtable.h @@ -61,6 +61,7 @@ class TTrainParameters TMTable TimeTable; int StationCount; // ilość przystanków (0-techniczny) int StationIndex; // numer najbliższego (aktualnego) przystanku + int StationStart; // numer pierwszej stacji pokazywanej na podglądzie rozkładu std::string NextStationName; double LastStationLatency; int Direction; /*kierunek jazdy w/g kilometrazu*/ diff --git a/opengl33renderer.cpp b/opengl33renderer.cpp index e2608334..51c7ff46 100644 --- a/opengl33renderer.cpp +++ b/opengl33renderer.cpp @@ -1289,7 +1289,7 @@ void opengl33_renderer::setup_pass(viewport_config &Viewport, renderpass_config bounding_box(frustumchunkmin, frustumchunkmax, std::begin(frustumchunkshapepoints), std::end(frustumchunkshapepoints)); auto const frustumchunkcentre = (frustumchunkmin + frustumchunkmax) * 0.5f; // ...cap the vertical angle to keep shadows from getting too long... - auto const lightvector = glm::normalize(glm::vec3{m_sunlight.direction.x, std::min(m_sunlight.direction.y, -0.2f), m_sunlight.direction.z}); + auto const lightvector = glm::normalize(glm::vec3{m_sunlight.direction.x, std::min(m_sunlight.direction.y, Global.gfx_shadow_angle_min), m_sunlight.direction.z}); // ...place the light source at the calculated centre and setup world space light view matrix... camera.position() = worldview.pass_camera.position() + glm::dvec3{frustumchunkcentre}; viewmatrix *= glm::lookAt(camera.position(), camera.position() + glm::dvec3{lightvector}, glm::dvec3{0.f, 1.f, 0.f}); @@ -1974,6 +1974,12 @@ opengl_material &opengl33_renderer::Material(material_handle const Material) return m_materials.material(Material); } +opengl_material const & opengl33_renderer::Material( TSubModel const * Submodel ) const { + + auto const material { Submodel->m_material >= 0 ? Submodel->m_material : Submodel->ReplacableSkinId[ -Submodel->m_material ] }; + return Material( material ); +} + texture_handle opengl33_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint) { return m_textures.create(Filename, Loadnow, format_hint); @@ -2001,11 +2007,6 @@ opengl_texture const &opengl33_renderer::Texture(texture_handle const Texture) c return m_textures.texture(Texture); } -void opengl33_renderer::Update_AnimModel(TAnimModel *model) -{ - model->RaAnimate(m_framestamp); -} - void opengl33_renderer::Render(scene::basic_region *Region) { @@ -2442,18 +2443,26 @@ void opengl33_renderer::Render(scene::shape_node const &Shape, bool const Ignore switch (m_renderpass.draw_mode) { case rendermode::color: - case rendermode::reflections: - Bind_Material(data.material, nullptr, &Shape.data().lighting ); - break; - case rendermode::shadows: - Bind_Material_Shadow(data.material); - break; + case rendermode::reflections: { + Bind_Material( data.material, nullptr, &Shape.data().lighting ); + break; + } + case rendermode::shadows: { + // skip if the shadow caster rank is too low for currently set threshold + if( Material( data.material ).shadow_rank > Global.gfx_shadow_rank_cutoff ) { + return; + } + Bind_Material_Shadow( data.material ); + break; + } case rendermode::pickscenery: - case rendermode::pickcontrols: - m_pick_shader->bind(); - break; - default: - break; + case rendermode::pickcontrols: { + m_pick_shader->bind(); + break; + } + default: { + break; + } } // render @@ -2921,6 +2930,13 @@ void opengl33_renderer::Render(TSubModel *Submodel) } case rendermode::shadows: { + // skip if the shadow caster rank is too low for currently set threshold + if( Material( Submodel ).shadow_rank > Global.gfx_shadow_rank_cutoff ) + { + --m_renderpass.draw_stats.submodels; + --m_renderpass.draw_stats.drawcalls; + break; + } if (Submodel->m_material < 0) { // zmienialne skóry Bind_Material_Shadow(Submodel->ReplacableSkinId[-Submodel->m_material]); @@ -3098,6 +3114,10 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator // shadows are only calculated for high enough roads, typically meaning track platforms continue; } + if( Material( track->m_material1 ).shadow_rank > Global.gfx_shadow_rank_cutoff ) { + // skip if the shadow caster rank is too low for currently set threshold + continue; + } Bind_Material_Shadow(track->m_material1); draw(std::begin(track->Geometry1), std::end(track->Geometry1)); break; @@ -3149,7 +3169,11 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator // shadows are only calculated for high enough trackbeds continue; } - Bind_Material_Shadow(track->m_material2); + if( Material( track->m_material2 ).shadow_rank > Global.gfx_shadow_rank_cutoff ) { + // skip if the shadow caster rank is too low for currently set threshold + continue; + } + Bind_Material_Shadow(track->m_material2); draw(std::begin(track->Geometry2), std::end(track->Geometry2)); break; } @@ -3206,6 +3230,11 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator // shadows are only calculated for high enough trackbeds continue; } + if( Material( track->SwitchExtension->m_material3 ).shadow_rank > Global.gfx_shadow_rank_cutoff ) + { + // skip if the shadow caster rank is too low for currently set threshold + continue; + } Bind_Material_Shadow(track->SwitchExtension->m_material3); draw(track->SwitchExtension->Geometry3); break; @@ -3739,6 +3768,13 @@ void opengl33_renderer::Render_Alpha(TSubModel *Submodel) } case rendermode::shadows: { + // skip if the shadow caster rank is too low for currently set threshold + if( Material( Submodel ).shadow_rank > Global.gfx_shadow_rank_cutoff ) + { + --m_renderpass.draw_stats.submodels; + --m_renderpass.draw_stats.drawcalls; + break; + } if (Submodel->m_material < 0) { // zmienialne skóry Bind_Material_Shadow(Submodel->ReplacableSkinId[-Submodel->m_material]); diff --git a/opengl33renderer.h b/opengl33renderer.h index e282545e..76f95985 100644 --- a/opengl33renderer.h +++ b/opengl33renderer.h @@ -123,15 +123,13 @@ class opengl33_renderer : public gfx_renderer { std::string const & info_stats() const override; - - opengl_material & Material( material_handle const Material ); + opengl_material const & Material( TSubModel const * Submodel ) const; // draws supplied geometry handles void Draw_Geometry(std::vector::iterator begin, std::vector::iterator end); void Draw_Geometry(const gfx::geometrybank_handle &handle); // material methods void Bind_Material_Shadow(material_handle const Material); - void Update_AnimModel(TAnimModel *model); // members GLenum static const sunlight{0}; diff --git a/openglrenderer.cpp b/openglrenderer.cpp index 57f3d217..1e19a63f 100644 --- a/openglrenderer.cpp +++ b/openglrenderer.cpp @@ -972,7 +972,7 @@ opengl_renderer::setup_pass( renderpass_config &Config, rendermode const Mode, f auto const lightvector = glm::normalize( glm::vec3{ m_sunlight.direction.x, - std::min( m_sunlight.direction.y, -0.2f ), + std::min( m_sunlight.direction.y, Global.gfx_shadow_angle_min ), m_sunlight.direction.z } ); // ...place the light source at the calculated centre and setup world space light view matrix... camera.position() = worldview.camera.position() + glm::dvec3{ frustumchunkcentre }; @@ -1024,7 +1024,7 @@ opengl_renderer::setup_pass( renderpass_config &Config, rendermode const Mode, f auto const lightvector = glm::normalize( glm::vec3{ m_sunlight.direction.x, - std::min( m_sunlight.direction.y, -0.2f ), + std::min( m_sunlight.direction.y, Global.gfx_shadow_angle_min ), m_sunlight.direction.z } ); camera.position() = Global.pCamera.Pos - glm::dvec3 { lightvector }; viewmatrix *= glm::lookAt( @@ -1757,6 +1757,13 @@ opengl_renderer::Material( material_handle const Material ) const { return m_materials.material( Material ); } +opengl_material const & +opengl_renderer::Material( TSubModel const * Submodel ) const { + + auto const material { Submodel->m_material >= 0 ? Submodel->m_material : Submodel->ReplacableSkinId[ -Submodel->m_material ] }; + return Material( material ); +} + // shader methods std::shared_ptr opengl_renderer::Fetch_Shader( std::string const &name ) { @@ -2285,7 +2292,13 @@ opengl_renderer::Render( scene::shape_node const &Shape, bool const Ignorerange break; } // pick modes are painted with custom colours, and shadow pass doesn't use any - case rendermode::shadows: + case rendermode::shadows: { + // skip if the shadow caster rank is too low for currently set threshold + if( Material( data.material ).shadow_rank > Global.gfx_shadow_rank_cutoff ) { + return; + } + } + [[ fallthrough ]]; case rendermode::cabshadows: case rendermode::pickscenery: case rendermode::pickcontrols: @@ -2815,7 +2828,16 @@ opengl_renderer::Render( TSubModel *Submodel ) { #endif break; } - case rendermode::shadows: + case rendermode::shadows: { + // skip if the shadow caster rank is too low for currently set threshold + if( Material( Submodel ).shadow_rank > Global.gfx_shadow_rank_cutoff ) + { + --m_renderpass.draw_stats.submodels; + --m_renderpass.draw_stats.drawcalls; + break; + } + } + [[fallthrough]]; case rendermode::cabshadows: case rendermode::pickscenery: { // scenery picking and shadow both use enforced colour and no frills @@ -3102,6 +3124,11 @@ opengl_renderer::Render( scene::basic_cell::path_sequence::const_iterator First, // shadows are only calculated for high enough roads, typically meaning track platforms continue; } + if( Material( track->m_material1 ).shadow_rank > Global.gfx_shadow_rank_cutoff ) + { + // skip if the shadow caster rank is too low for currently set threshold + continue; + } Bind_Material( track->m_material1 ); m_geometry.draw( std::begin( track->Geometry1 ), std::end( track->Geometry1 ) ); break; @@ -3147,6 +3174,11 @@ opengl_renderer::Render( scene::basic_cell::path_sequence::const_iterator First, // shadows are only calculated for high enough trackbeds continue; } + if( Material( track->m_material2 ).shadow_rank > Global.gfx_shadow_rank_cutoff ) + { + // skip if the shadow caster rank is too low for currently set threshold + continue; + } Bind_Material( track->m_material2 ); m_geometry.draw( std::begin( track->Geometry2 ), std::end( track->Geometry2 ) ); break; @@ -3195,6 +3227,11 @@ opengl_renderer::Render( scene::basic_cell::path_sequence::const_iterator First, // shadows are only calculated for high enough trackbeds continue; } + if( Material( track->SwitchExtension->m_material3 ).shadow_rank > Global.gfx_shadow_rank_cutoff ) + { + // skip if the shadow caster rank is too low for currently set threshold + continue; + } Bind_Material( track->SwitchExtension->m_material3 ); m_geometry.draw( track->SwitchExtension->Geometry3 ); break; diff --git a/openglrenderer.h b/openglrenderer.h index ffc2f8cf..a18b3c85 100644 --- a/openglrenderer.h +++ b/openglrenderer.h @@ -117,6 +117,8 @@ public: std::string const & info_stats() const override; + opengl_material const & Material( TSubModel const * Submodel ) const; + // members GLenum static const sunlight { GL_LIGHT0 }; diff --git a/particles.cpp b/particles.cpp index 9c45717f..13197dda 100644 --- a/particles.cpp +++ b/particles.cpp @@ -301,7 +301,7 @@ smoke_source::initialize( smoke_particle &Particle ) { if( m_ownertype == owner_type::vehicle ) { Particle.opacity *= m_owner.vehicle->MoverParameters->dizel_fill; - auto const enginerevolutionsfactor { 1.5f }; // high engine revolutions increase initial particle velocity + auto const enginerevolutionsfactor { 0.5f }; // high engine revolutions increase initial particle velocity switch( m_owner.vehicle->MoverParameters->EngineType ) { case TEngineType::DieselElectric: { Particle.velocity *= 1.0 + enginerevolutionsfactor * m_owner.vehicle->MoverParameters->enrot / ( m_owner.vehicle->MoverParameters->DElist[ m_owner.vehicle->MoverParameters->MainCtrlPosNo ].RPM / 60.0 ); @@ -331,7 +331,7 @@ smoke_source::update( smoke_particle &Particle, bounding_box &Boundingbox, doubl // crude smoke dispersion simulation // http://www.auburn.edu/academic/forestry_wildlife/fire/smoke_guide/smoke_dispersion.htm Particle.velocity.y += ( 0.005 * Particle.velocity.y ) * std::min( 0.f, Global.AirTemperature - 10 ) * Timedelta; // decelerate faster in cold weather - Particle.velocity.y -= ( 0.010 * Particle.velocity.y ) * Global.Overcast * Timedelta; // decelerate faster with high air humidity and/or precipitation + Particle.velocity.y -= ( 0.050 * Particle.velocity.y ) * Global.Overcast * Timedelta; // decelerate faster with high air humidity and/or precipitation Particle.velocity.y = std::max( 0.25 * ( 2.f - Global.Overcast ), Particle.velocity.y ); // put a cap on deceleration Particle.position += Particle.velocity * static_cast( Timedelta ); diff --git a/sound.cpp b/sound.cpp index 62123844..e20938aa 100644 --- a/sound.cpp +++ b/sound.cpp @@ -182,6 +182,11 @@ sound_source::deserialize_mapping( cParser &Input ) { >> soundproofing[ 3 ] >> soundproofing[ 4 ] >> soundproofing[ 5 ]; + for( auto & soundproofingelement : soundproofing ) { + if( soundproofingelement != -1.f ) { + soundproofingelement = std::sqrtf( clamp( soundproofingelement, 0.f, 1.f ) ); + } + } m_soundproofing = soundproofing; } else if( starts_with( key, "sound" ) ) { @@ -892,6 +897,20 @@ sound_source::is_combined() const { return ( ( !m_soundchunks.empty() ) && ( sound( sound_id::main ).buffer == null_handle ) ); } +// returns true if specified buffer is one of the optional bookends +bool +sound_source::is_bookend( audio::buffer_handle const Buffer ) const { + + return ( ( sound( sound_id::begin ).buffer == Buffer ) || ( sound( sound_id::end ).buffer == Buffer ) ); +} + +// returns true if the source has optional bookends +bool +sound_source::has_bookends() const { + + return ( ( sound( sound_id::begin ).buffer != null_handle ) && ( sound( sound_id::end ).buffer != null_handle ) ); +} + // returns location of the sound source in simulation region space glm::dvec3 const sound_source::location() const { diff --git a/sound.h b/sound.h index 944cd600..eb09b589 100644 --- a/sound.h +++ b/sound.h @@ -130,6 +130,12 @@ public: // returns true if the source uses sample table bool is_combined() const; + // returns true if specified buffer is one of the optional bookends + bool + is_bookend( audio::buffer_handle const Buffer ) const; + // returns true if the source has optional bookends + bool + has_bookends() const; // returns location of the sound source in simulation region space glm::dvec3 const location() const; diff --git a/version.h b/version.h index ae0dccc8..1667a188 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 21 -#define VERSION_MINOR 509 +#define VERSION_MINOR 905 #define VERSION_REVISION 0 From 55e5f75f4e67f39f9b517d8db25dc2420694de4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 11 Sep 2021 00:04:12 +0200 Subject: [PATCH 53/63] Added full analog input from Pokeys for EIMCtrlType==0 --- McZapkie/MOVER.h | 1 + McZapkie/Mover.cpp | 3 ++- Train.cpp | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 895a2c4b..cfd63bb1 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1585,6 +1585,7 @@ public: /*- zmienne dla lokomotyw z silnikami indukcyjnymi -*/ double eimic = 0; /*aktualna pozycja zintegrowanego sterowania jazda i hamowaniem*/ + double eimic_analog = 0; /*pozycja zadajnika analogowa*/ double eimic_real = 0; /*faktycznie uzywana pozycja zintegrowanego sterowania jazda i hamowaniem*/ double eim_localbrake = 0; /*nastawa hamowania dodatkowego pneumatycznego lokomotywy*/ int EIMCtrlType = 0; /*rodzaj wariantu zadajnika jazdy*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 3d6a6a69..875139f2 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -7112,7 +7112,8 @@ void TMoverParameters::CheckEIMIC(double dt) switch (EIMCtrlType) { case 0: - eimic = (LocalBrakeRatio() > 0.01 ? -LocalBrakeRatio() : (double)MainCtrlPos / (double)MainCtrlPosNo); + eimic = (LocalBrakeRatio() > 0.01 ? -LocalBrakeRatio() : + eimic_analog > 0.01 ? eimic_analog : (double)MainCtrlPos / (double)MainCtrlPosNo); if (EIMCtrlAdditionalZeros || EIMCtrlEmergency) { if (eimic > 0.001) diff --git a/Train.cpp b/Train.cpp index d662c574..473bbc8d 100644 --- a/Train.cpp +++ b/Train.cpp @@ -7094,6 +7094,7 @@ bool TTrain::Update( double const Deltatime ) && ( Global.fCalibrateIn[ 2 ][ 1 ] != 0.0 ) ) { set_master_controller( Console::AnalogCalibrateGet( 2 ) * mvOccupied->MainCtrlPosNo ); + mvOccupied->eimic_analog = Console::AnalogCalibrateGet(2); } #endif From e76bc97b5592edcde962dfa26a3d5dfd78273aac Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sun, 12 Sep 2021 01:02:14 +0200 Subject: [PATCH 54/63] diesel powered vehicle smoke transparency tweak, hardware brake handle support enhancement --- McZapkie/Mover.cpp | 2 +- Train.cpp | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 3d6a6a69..9c25184b 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -5385,7 +5385,7 @@ double TMoverParameters::TractionForce( double dt ) { / 60.0 ); } // NOTE: fake dizel_fill calculation for the sake of smoke emitter which uses this parameter to determine smoke opacity - dizel_fill = clamp( 0.2 + 0.35 * ( tmp - enrot ), 0.0, 1.0 ); + dizel_fill = clamp( 0.2 + 0.35 * ( tmp - enrot ) + 0.5 * ( std::abs( Im ) / DElist[ MainCtrlPosNo ].Imax ), 0.05, 1.0 ); } else { tmp = 0.0; diff --git a/Train.cpp b/Train.cpp index d662c574..bffdb31e 100644 --- a/Train.cpp +++ b/Train.cpp @@ -7170,7 +7170,7 @@ bool TTrain::Update( double const Deltatime ) ggBrakeCtrl.UpdateValue(b); // przesów bez zaokrąglenia mvOccupied->BrakeLevelSet(b); } - if (mvOccupied->BrakeHandle == TBrakeHandle::FVel6) // może można usunąć ograniczenie do FV4a i FVel6? + else if (mvOccupied->BrakeHandle == TBrakeHandle::FVel6) // może można usunąć ograniczenie do FV4a i FVel6? { double b = Console::AnalogCalibrateGet(0); b = b * 7.0 - 1.0; @@ -7178,14 +7178,22 @@ bool TTrain::Update( double const Deltatime ) ggBrakeCtrl.UpdateValue(b); // przesów bez zaokrąglenia mvOccupied->BrakeLevelSet(b); } + else { + double b = Console::AnalogCalibrateGet( 0 ); + b = b * ( mvOccupied->Handle->GetPos( bh_MAX ) - mvOccupied->Handle->GetPos( bh_MIN ) ) + mvOccupied->Handle->GetPos( bh_MIN ); + b = clamp( b, mvOccupied->Handle->GetPos( bh_MIN ), mvOccupied->Handle->GetPos( bh_MAX ) ); // przycięcie zmiennej do granic + ggBrakeCtrl.UpdateValue( b ); // przesów bez zaokrąglenia + mvOccupied->BrakeLevelSet( b ); + } + } + else +#endif + { // else //standardowa prodedura z kranem powiązanym z klawiaturą // ggBrakeCtrl.UpdateValue(double(mvOccupied->BrakeCtrlPos)); + ggBrakeCtrl.UpdateValue( mvOccupied->fBrakeCtrlPos ); + ggBrakeCtrl.Update(); } -#endif - // else //standardowa prodedura z kranem powiązanym z klawiaturą - // ggBrakeCtrl.UpdateValue(double(mvOccupied->BrakeCtrlPos)); - ggBrakeCtrl.UpdateValue(mvOccupied->fBrakeCtrlPos); - ggBrakeCtrl.Update(); } if( ggLocalBrake.SubModel != nullptr ) { From 0f22f24eb74342c0fa93de0689c71478d977331f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 11 Sep 2021 00:04:12 +0200 Subject: [PATCH 55/63] Added full analog input from Pokeys for EIMCtrlType==0 --- McZapkie/MOVER.h | 1 + McZapkie/Mover.cpp | 3 ++- Train.cpp | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 895a2c4b..cfd63bb1 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1585,6 +1585,7 @@ public: /*- zmienne dla lokomotyw z silnikami indukcyjnymi -*/ double eimic = 0; /*aktualna pozycja zintegrowanego sterowania jazda i hamowaniem*/ + double eimic_analog = 0; /*pozycja zadajnika analogowa*/ double eimic_real = 0; /*faktycznie uzywana pozycja zintegrowanego sterowania jazda i hamowaniem*/ double eim_localbrake = 0; /*nastawa hamowania dodatkowego pneumatycznego lokomotywy*/ int EIMCtrlType = 0; /*rodzaj wariantu zadajnika jazdy*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 9c25184b..de88ebc1 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -7112,7 +7112,8 @@ void TMoverParameters::CheckEIMIC(double dt) switch (EIMCtrlType) { case 0: - eimic = (LocalBrakeRatio() > 0.01 ? -LocalBrakeRatio() : (double)MainCtrlPos / (double)MainCtrlPosNo); + eimic = (LocalBrakeRatio() > 0.01 ? -LocalBrakeRatio() : + eimic_analog > 0.01 ? eimic_analog : (double)MainCtrlPos / (double)MainCtrlPosNo); if (EIMCtrlAdditionalZeros || EIMCtrlEmergency) { if (eimic > 0.001) diff --git a/Train.cpp b/Train.cpp index bffdb31e..125e8bc7 100644 --- a/Train.cpp +++ b/Train.cpp @@ -7094,6 +7094,7 @@ bool TTrain::Update( double const Deltatime ) && ( Global.fCalibrateIn[ 2 ][ 1 ] != 0.0 ) ) { set_master_controller( Console::AnalogCalibrateGet( 2 ) * mvOccupied->MainCtrlPosNo ); + mvOccupied->eimic_analog = Console::AnalogCalibrateGet(2); } #endif From 661a87e8f1359fb3f646a59168fc1ad9c24522f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 25 Sep 2021 20:16:34 +0200 Subject: [PATCH 56/63] Additional ED brake factor for broken vehicles --- DynObj.cpp | 2 +- McZapkie/MOVER.h | 3 ++- McZapkie/Mover.cpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DynObj.cpp b/DynObj.cpp index db80d3f3..34d31685 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3205,7 +3205,7 @@ bool TDynamicObject::Update(double dt, double dt1) if ((MoverParameters->BrakeCtrlPos == MoverParameters->Handle->GetPos(bh_EB)) && (MoverParameters->eimc[eimc_p_abed] < 0.001)) FzadED = 0; //pętla bezpieczeństwa - bez ED - auto const FzadPN = Fzad - FrED; + auto const FzadPN = Fzad - FrED * MoverParameters->MED_FrED_factor; //np = 0; // BUG: likely memory leak, allocation per inner loop, deleted only once outside // TODO: sort this shit out diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index cfd63bb1..badb39de 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1312,7 +1312,8 @@ public: bool MED_EPVC = 0; // czy korekcja sily hamowania EP, gdy nie ma dostepnego ED double MED_EPVC_Time = 7; // czas korekcji sily hamowania EP, gdy nie ma dostepnego ED bool MED_Ncor = 0; // czy korekcja sily hamowania z uwzglednieniem nacisku - double MED_MinBrakeReqED = 0; + double MED_MinBrakeReqED = 0; // minimalne zadanie sily hamowania uruchamiajace ED - ponizej tylko EP + double MED_FrED_factor = 1; // mnoznik sily hamowania ED do korekty blendingu int DCEMUED_CC { 0 }; //na którym sprzęgu sprawdzać działanie ED double DCEMUED_EP_max_Vel{ 0.0 }; //maksymalna prędkość, przy której działa EP przy włączonym ED w jednostce (dla tocznych) diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 875139f2..6d04d481 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -10533,6 +10533,7 @@ void TMoverParameters::LoadFIZ_Blending(std::string const &line) { extract_value(MED_EPVC, "MED_EPVC", line, ""); extract_value(MED_Ncor, "MED_Ncor", line, ""); extract_value(MED_MinBrakeReqED, "MED_MinBrakeReqED", line, ""); + extract_value(MED_FrED_factor, "MED_FrEDFactor", line, ""); } void TMoverParameters::LoadFIZ_DCEMUED(std::string const &line) { From f344b37b403577ec927e59d7ac20658d9ba7f277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 25 Sep 2021 20:16:34 +0200 Subject: [PATCH 57/63] Additional ED brake factor for broken vehicles --- DynObj.cpp | 2 +- McZapkie/MOVER.h | 3 ++- McZapkie/Mover.cpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DynObj.cpp b/DynObj.cpp index ced62243..a442b760 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3204,7 +3204,7 @@ bool TDynamicObject::Update(double dt, double dt1) if ((MoverParameters->BrakeCtrlPos == MoverParameters->Handle->GetPos(bh_EB)) && (MoverParameters->eimc[eimc_p_abed] < 0.001)) FzadED = 0; //pętla bezpieczeństwa - bez ED - auto const FzadPN = Fzad - FrED; + auto const FzadPN = Fzad - FrED * MoverParameters->MED_FrED_factor; //np = 0; // BUG: likely memory leak, allocation per inner loop, deleted only once outside // TODO: sort this shit out diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index cfd63bb1..badb39de 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1312,7 +1312,8 @@ public: bool MED_EPVC = 0; // czy korekcja sily hamowania EP, gdy nie ma dostepnego ED double MED_EPVC_Time = 7; // czas korekcji sily hamowania EP, gdy nie ma dostepnego ED bool MED_Ncor = 0; // czy korekcja sily hamowania z uwzglednieniem nacisku - double MED_MinBrakeReqED = 0; + double MED_MinBrakeReqED = 0; // minimalne zadanie sily hamowania uruchamiajace ED - ponizej tylko EP + double MED_FrED_factor = 1; // mnoznik sily hamowania ED do korekty blendingu int DCEMUED_CC { 0 }; //na którym sprzęgu sprawdzać działanie ED double DCEMUED_EP_max_Vel{ 0.0 }; //maksymalna prędkość, przy której działa EP przy włączonym ED w jednostce (dla tocznych) diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index de88ebc1..8839c4f3 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -10533,6 +10533,7 @@ void TMoverParameters::LoadFIZ_Blending(std::string const &line) { extract_value(MED_EPVC, "MED_EPVC", line, ""); extract_value(MED_Ncor, "MED_Ncor", line, ""); extract_value(MED_MinBrakeReqED, "MED_MinBrakeReqED", line, ""); + extract_value(MED_FrED_factor, "MED_FrEDFactor", line, ""); } void TMoverParameters::LoadFIZ_DCEMUED(std::string const &line) { From 6fba342474e81de7b5eb7af0fa0ef6a3fc8c8c38 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sun, 3 Oct 2021 15:58:08 +0200 Subject: [PATCH 58/63] second controller animation improvement, minor bug fixes --- Train.cpp | 110 +++++++++++++++++++++++++++++++++++------------- driverhints.cpp | 4 +- translation.cpp | 2 +- 3 files changed, 83 insertions(+), 33 deletions(-) diff --git a/Train.cpp b/Train.cpp index 125e8bc7..ceb2349e 100644 --- a/Train.cpp +++ b/Train.cpp @@ -1103,24 +1103,28 @@ void TTrain::OnCommand_secondcontrollerincrease( TTrain *Train, command_data con } } else { + // regular mode + // push or pushtoggle control type if( Train->ggScndCtrl.is_push() ) { - // two-state control, active while the button is down if( Command.action == GLFW_PRESS ) { // activate on press Train->mvControlled->IncScndCtrl( 1 ); } - else if( Command.action == GLFW_RELEASE ) { - // zero on release - Train->mvControlled->DecScndCtrl( 2 ); - } } + // toggle control type else { - // multi-state control if( Command.action != GLFW_RELEASE ) { - // on press or hold Train->mvControlled->IncScndCtrl( 1 ); } } + // HACK: potentially animate push or pushtoggle control + if( Train->ggScndCtrl.is_push() ) { + auto const activeposition { Train->ggScndCtrl.is_toggle() ? 1.f : 1.f }; + auto const neutralposition { Train->ggScndCtrl.is_toggle() ? 0.5f : 0.f }; + Train->ggScndCtrl.UpdateValue( + ( ( Command.action == GLFW_RELEASE ) ? neutralposition : activeposition ), + Train->dsbSwitch ); + } // potentially animate tempomat button if( ( Train->ggScndCtrlButton.is_push() ) && ( Train->mvControlled->ScndCtrlPos <= 1 ) ) { @@ -1250,30 +1254,53 @@ void TTrain::OnCommand_mucurrentindicatorothersourceactivate( TTrain *Train, com void TTrain::OnCommand_secondcontrollerdecrease( TTrain *Train, command_data const &Command ) { - if( Command.action != GLFW_RELEASE ) { - // on press or hold - if( ( Train->mvControlled->EngineType == TEngineType::DieselElectric ) - && ( true == Train->mvControlled->ShuntMode ) ) { + if( ( Train->mvControlled->EngineType == TEngineType::DieselElectric ) + && ( true == Train->mvControlled->ShuntMode ) ) { + if( Command.action != GLFW_RELEASE ) { Train->mvControlled->AnPos = clamp( Train->mvControlled->AnPos - 0.025, 0.0, 1.0 ); } - else { - Train->mvControlled->DecScndCtrl( 1 ); - } } - // potentially animate tempomat button - if( ( Train->ggScndCtrlButton.is_push() ) - && ( Train->mvControlled->ScndCtrlPos <= 1 ) ) { - if( Train->m_controlmapper.contains( "tempomatoff_sw:" ) ) { - Train->ggScndCtrlOffButton.UpdateValue( - ( ( Command.action == GLFW_RELEASE ) ? 0.f : 1.f ), + else { + // regular mode + // push or pushtoggle control type + if( Train->ggScndCtrl.is_push() ) { + // basic push control can't decrease state, but pushtoggle can + if( true == Train->ggScndCtrl.is_toggle() ) { + if( Command.action == GLFW_PRESS ) { + // activate on press + Train->mvControlled->DecScndCtrl( 1 ); + } + } + } + // toggle control type + else { + if( Command.action != GLFW_RELEASE ) { + Train->mvControlled->DecScndCtrl( 1 ); + } + } + // HACK: potentially animate push or pushtoggle control + if( Train->ggScndCtrl.is_push() ) { + auto const activeposition { Train->ggScndCtrl.is_toggle() ? 0.f : 1.f }; + auto const neutralposition { Train->ggScndCtrl.is_toggle() ? 0.5f : 0.f }; + Train->ggScndCtrl.UpdateValue( + ( ( Command.action == GLFW_RELEASE ) ? neutralposition : activeposition ), Train->dsbSwitch ); } - else { - Train->ggScndCtrlButton.UpdateValue( - ( ( Command.action == GLFW_RELEASE ) ? 0.f : 1.f ), - Train->dsbSwitch ); + // potentially animate tempomat button + if( ( Train->ggScndCtrlButton.is_push() ) + && ( Train->mvControlled->ScndCtrlPos <= 1 ) ) { + if( Train->m_controlmapper.contains( "tempomatoff_sw:" ) ) { + Train->ggScndCtrlOffButton.UpdateValue( + ( ( Command.action == GLFW_RELEASE ) ? 0.f : 1.f ), + Train->dsbSwitch ); + } + else { + Train->ggScndCtrlButton.UpdateValue( + ( ( Command.action == GLFW_RELEASE ) ? 0.f : 1.f ), + Train->dsbSwitch ); + } } } } @@ -1294,9 +1321,23 @@ void TTrain::OnCommand_secondcontrollerdecreasefast( TTrain *Train, command_data void TTrain::OnCommand_secondcontrollerset( TTrain *Train, command_data const &Command ) { + auto const targetposition{ std::min( Command.param1, Train->mvControlled->ScndCtrlPosNo ) }; + // HACK: potentially animate push or pushtoggle control + if( Train->ggScndCtrl.is_push() ) { + auto const activeposition { + Train->ggScndCtrl.is_toggle() ? + ( targetposition < Train->mvControlled->ScndCtrlPos ? 0.f : + targetposition > Train->mvControlled->ScndCtrlPos ? 1.f : + Train->ggScndCtrl.GetDesiredValue() ) : // leave the control in its current position if it hits the limit + ( targetposition == 0 ? 0.f : 1.f ) }; + auto const neutralposition { Train->ggScndCtrl.is_toggle() ? 0.5f : 0.f }; + Train->ggScndCtrl.UpdateValue( + ( ( Command.action == GLFW_RELEASE ) ? neutralposition : activeposition ), + Train->dsbSwitch ); + } + // update control value if( Command.action != GLFW_RELEASE ) { // on press or hold - auto const targetposition{ std::min( Command.param1, Train->mvControlled->ScndCtrlPosNo ) }; while( ( targetposition < Train->mvControlled->ScndCtrlPos ) && ( true == Train->mvControlled->DecScndCtrl( 1 ) ) ) { // all work is done in the header @@ -7121,10 +7162,12 @@ bool TTrain::Update( double const Deltatime ) } if (ggScndCtrl.SubModel != nullptr ) { // Ra: od byte odejmowane boolean i konwertowane potem na double? - ggScndCtrl.UpdateValue( - double( mvControlled->ScndCtrlPos - - ( ( mvControlled->TrainType == dt_ET42 ) && mvControlled->DynamicBrakeFlag ) ), - dsbNastawnikBocz ); + if( false == ggScndCtrl.is_push() ) { + ggScndCtrl.UpdateValue( + double( mvControlled->ScndCtrlPos + - ( ( mvControlled->TrainType == dt_ET42 ) && mvControlled->DynamicBrakeFlag ) ), + dsbNastawnikBocz ); + } ggScndCtrl.Update(); } if( ggScndCtrlButton.SubModel != nullptr ) { @@ -8784,7 +8827,7 @@ void TTrain::set_cab_controls( int const Cab ) { // battery ggBatteryButton.PutValue( ( ggBatteryButton.type() == TGaugeType::push ? 0.5f : - mvOccupied->Battery ? 1.f : + mvOccupied->Power24vIsAvailable ? 1.f : 0.f ) ); // line breaker if( ggMainButton.SubModel != nullptr ) { // instead of single main button there can be on/off pair @@ -9073,6 +9116,13 @@ void TTrain::set_cab_controls( int const Cab ) { 1.f : 0.f ); } + // second controller + if( ggScndCtrl.is_push() ) { + ggScndCtrl.PutValue( + ggScndCtrl.is_toggle() ? + 0.5f : // pushtoggle is two-way control with neutral position in the middle + 0.f ); // push is on/off control, active while held down, due to legacy use + } // tempomat if( false == ggScndCtrlButton.is_push() ) { ggScndCtrlButton.PutValue( diff --git a/driverhints.cpp b/driverhints.cpp index 2a485092..6cb3bfdb 100644 --- a/driverhints.cpp +++ b/driverhints.cpp @@ -593,7 +593,7 @@ TController::cue_action( locale::string const Action, float const Actionparamete Action, [this](float const Parameter) -> bool { auto const &device { mvControlling->WaterPump }; - return ( ( device.start_type != start_t::manual ) || ( ( device.is_enabled == true ) && ( device.is_disabled == false ) ) ); } ); + return ( ( device.start_type != start_t::manual ) || ( ( device.is_enabled == true ) && ( device.is_disabled == false ) ) || ( device.is_active == true ) ); } ); break; } case locale::string::driver_hint_waterpumpoff: { @@ -1128,7 +1128,7 @@ TController::cue_action( locale::string const Action, float const Actionparamete Action, [this](float const Parameter) -> bool { auto const &device { mvOccupied->CompartmentLights }; - return ( ( Global.fLuminance * ConsistShade > 0.40 ) || ( device.start_type != start_t::manual ) || ( ( device.is_enabled == true ) && ( device.is_disabled == false ) ) ); } ); + return ( ( Global.fLuminance * ConsistShade > 0.40 ) || ( device.start_type != start_t::manual ) || ( ( device.is_enabled == true ) && ( device.is_disabled == false ) ) || ( device.is_active == true ) ); } ); break; } case locale::string::driver_hint_consistlightsoff: { diff --git a/translation.cpp b/translation.cpp index 0bb68458..fd37fdb7 100644 --- a/translation.cpp +++ b/translation.cpp @@ -403,7 +403,7 @@ init() { "Zalaczyc wentylatory tylnych silnikow trakcyjnych", "Wylaczyc wentylatory tylnych silnikow trakcyjnych", "Zalaczyc hamulec sprezynowy", - "Zwolnic hamules sprezynowy", + "Zwolnic hamulec sprezynowy", "Zalaczyc hamulec reczny", "Zwolnic hamulec reczny", "Ustawic nastawnik jazdy na pozycje biegu jalowego", From 62d5bbe62703fef0fbcf677a869e4cde168be06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sun, 10 Oct 2021 21:28:51 +0200 Subject: [PATCH 59/63] Added sounds for EP brake made by EP-Compact --- DynObj.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++++++ DynObj.h | 4 ++++ 2 files changed, 50 insertions(+) diff --git a/DynObj.cpp b/DynObj.cpp index 34d31685..9283d57c 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -4189,6 +4189,40 @@ void TDynamicObject::RenderSounds() { } } + // epbrake - epcompact + if (( MoverParameters->BrakeSystem == TBrakeSystem::ElectroPneumatic ) && ( MoverParameters->LocHandle )) { + auto const epbrakepressureratio{ std::max( 0.0, MoverParameters->LocHandle->GetCP() ) / std::max(1.0, MoverParameters->MaxBrakePress[0] ) }; + if ( m_lastepbrakepressure != -1.f ) { + // HACK: potentially reset playback of opening bookend sounds + if ( false == m_epbrakepressureincrease.is_playing() ) { + m_epbrakepressureincrease.stop(); + } + if ( false == m_epbrakepressuredecrease.is_playing() ) { + m_epbrakepressuredecrease.stop(); + } + // actual sound playback + auto const epquantizedratio{ static_cast( 50 * epbrakepressureratio) }; + auto const lastepbrakepressureratio { std::max( 0.f, m_lastepbrakepressure ) / std::max( 1.0, MoverParameters->MaxBrakePress[0] ) }; + auto const epquantizedratiochange { epquantizedratio - static_cast( 50 * lastepbrakepressureratio ) }; + if ( epquantizedratiochange > 0 ) { + m_epbrakepressureincrease + .pitch( + true == m_epbrakepressureincrease.is_combined() ? + epquantizedratio * 0.01f : + m_epbrakepressureincrease.m_frequencyoffset + m_epbrakepressureincrease.m_frequencyfactor * 1.f ) + .play(); + } + else if ( epquantizedratiochange < 0 ) { + m_epbrakepressuredecrease + .pitch(true == m_epbrakepressuredecrease.is_combined() ? + epquantizedratio * 0.01f : + m_epbrakepressuredecrease.m_frequencyoffset + m_epbrakepressuredecrease.m_frequencyfactor * 1.f ) + .play(); + } + } + m_lastepbrakepressure = MoverParameters->LocHandle->GetCP(); + } + // emergency brake if( MoverParameters->EmergencyValveFlow > 0.025 ) { // smooth out air flow rate @@ -5719,6 +5753,18 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co m_brakecylinderpistonrecede.owner( this ); } + else if (token == "epbrakeinc:") { + // brake cylinder pressure increase sounds + m_epbrakepressureincrease.deserialize(parser, sound_type::single); + m_epbrakepressureincrease.owner(this); + } + + else if (token == "epbrakedec:") { + // brake cylinder pressure decrease sounds + m_epbrakepressuredecrease.deserialize(parser, sound_type::single); + m_epbrakepressuredecrease.owner(this); + } + else if( token == "emergencybrake:" ) { // emergency brake sound m_emergencybrake.deserialize( parser, sound_type::single ); diff --git a/DynObj.h b/DynObj.h index 7c5283ed..df44a7ff 100644 --- a/DynObj.h +++ b/DynObj.h @@ -499,6 +499,10 @@ private: sound_source m_brakecylinderpistonrecede { sound_placement::external }; float m_lastbrakepressure { -1.f }; // helper, cached level of pressure in the brake cylinder float m_brakepressurechange { 0.f }; // recent change of pressure in the brake cylinder + sound_source m_epbrakepressureincrease{ sound_placement::external }; + sound_source m_epbrakepressuredecrease{ sound_placement::external }; + float m_lastepbrakepressure{ -1.f }; // helper, cached level of pressure in the brake cylinder + float m_epbrakepressurechange{ 0.f }; // recent change of pressure in the brake cylinder sound_source m_emergencybrake { sound_placement::engine }; double m_emergencybrakeflow{ 0.f }; sound_source sReleaser { sound_placement::external }; From ecfb5ec39fee5a73834f293eadd74b2acbc750d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sun, 17 Oct 2021 16:47:16 +0200 Subject: [PATCH 60/63] Added sounds for ep braking (coils, relays), reworked delay in ed braking --- DynObj.cpp | 40 +++++++++++++++++++++++++++++++++++----- DynObj.h | 2 ++ McZapkie/MOVER.h | 3 +++ McZapkie/Mover.cpp | 2 ++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/DynObj.cpp b/DynObj.cpp index 9283d57c..cdc43145 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3198,9 +3198,30 @@ bool TDynamicObject::Update(double dt, double dt1) MoverParameters->EpFuse ) ) { FzadED = std::min( Fzad, FmaxED ); } + /*/ELF - wdrazanie ED po powrocie na utrzymanie hamowania - do usuniecia if (MoverParameters->EIMCtrlType == 2 && MoverParameters->MainCtrlPos < 2 && MoverParameters->eimic > -0.999) { FzadED = std::min(FzadED, MED_oldFED); + } //*/ + //opoznienie wdrazania ED + if (FzadED > MED_oldFED) + { + if (MoverParameters->MED_ED_DelayTimer <= 0) { + MoverParameters->MED_ED_DelayTimer += dt1; + if (MoverParameters->MED_ED_DelayTimer > 0) { + + } + else { + FzadED = std::min(FzadED, MED_oldFED); + } + } + else + { + FzadED = std::min(FzadED, MED_oldFED); + MoverParameters->MED_ED_DelayTimer = (FrED > 0 ? + -MoverParameters->MED_ED_Delay2 : + -MoverParameters->MED_ED_Delay1); + } } if ((MoverParameters->BrakeCtrlPos == MoverParameters->Handle->GetPos(bh_EB)) && (MoverParameters->eimc[eimc_p_abed] < 0.001)) @@ -4191,7 +4212,10 @@ void TDynamicObject::RenderSounds() { // epbrake - epcompact if (( MoverParameters->BrakeSystem == TBrakeSystem::ElectroPneumatic ) && ( MoverParameters->LocHandle )) { - auto const epbrakepressureratio{ std::max( 0.0, MoverParameters->LocHandle->GetCP() ) / std::max(1.0, MoverParameters->MaxBrakePress[0] ) }; + auto const epbrakepressureratio{ std::min( std::max( 0.0, MoverParameters->LocHandle->GetCP() ) / std::max( 1.0, MoverParameters->MaxBrakePress[0] ), + m_epbrakepressurechangedectimer > -1.0f ? + std::max( MoverParameters->LocalBrakePosAEIM, MoverParameters->Hamulec->GetEDBCP() / MoverParameters->MaxBrakePress[3]) : + 1.0 ) }; if ( m_lastepbrakepressure != -1.f ) { // HACK: potentially reset playback of opening bookend sounds if ( false == m_epbrakepressureincrease.is_playing() ) { @@ -4200,27 +4224,33 @@ void TDynamicObject::RenderSounds() { if ( false == m_epbrakepressuredecrease.is_playing() ) { m_epbrakepressuredecrease.stop(); } + m_epbrakepressurechangeinctimer += dt; + m_epbrakepressurechangedectimer += dt; // actual sound playback auto const epquantizedratio{ static_cast( 50 * epbrakepressureratio) }; auto const lastepbrakepressureratio { std::max( 0.f, m_lastepbrakepressure ) / std::max( 1.0, MoverParameters->MaxBrakePress[0] ) }; auto const epquantizedratiochange { epquantizedratio - static_cast( 50 * lastepbrakepressureratio ) }; - if ( epquantizedratiochange > 0 ) { + if ( epquantizedratiochange > 0 && m_epbrakepressurechangeinctimer > 0.05f) { m_epbrakepressureincrease .pitch( true == m_epbrakepressureincrease.is_combined() ? epquantizedratio * 0.01f : m_epbrakepressureincrease.m_frequencyoffset + m_epbrakepressureincrease.m_frequencyfactor * 1.f ) .play(); + m_epbrakepressurechangeinctimer = 0; } - else if ( epquantizedratiochange < 0 ) { + else if ( epquantizedratiochange < 0 && m_epbrakepressurechangedectimer > 0.3f) { m_epbrakepressuredecrease .pitch(true == m_epbrakepressuredecrease.is_combined() ? - epquantizedratio * 0.01f : + -epquantizedratiochange * 0.01f : m_epbrakepressuredecrease.m_frequencyoffset + m_epbrakepressuredecrease.m_frequencyfactor * 1.f ) .play(); + m_epbrakepressurechangedectimer = 0; } } - m_lastepbrakepressure = MoverParameters->LocHandle->GetCP(); + if ( ( m_epbrakepressurechangeinctimer == 0 ) || ( m_epbrakepressurechangedectimer == 0 ) ) + m_lastepbrakepressure = std::min( MoverParameters->LocHandle->GetCP(), + MoverParameters->LocalBrakePosAEIM * std::max( 1.0, MoverParameters->MaxBrakePress[0] ) ); } // emergency brake diff --git a/DynObj.h b/DynObj.h index df44a7ff..49cd960c 100644 --- a/DynObj.h +++ b/DynObj.h @@ -503,6 +503,8 @@ private: sound_source m_epbrakepressuredecrease{ sound_placement::external }; float m_lastepbrakepressure{ -1.f }; // helper, cached level of pressure in the brake cylinder float m_epbrakepressurechange{ 0.f }; // recent change of pressure in the brake cylinder + float m_epbrakepressurechangeinctimer{ 0.f }; // last time of change of pressure in the brake cylinder - increase + float m_epbrakepressurechangedectimer{ 0.f }; // last time of change of pressure in the brake cylinder - decrease sound_source m_emergencybrake { sound_placement::engine }; double m_emergencybrakeflow{ 0.f }; sound_source sReleaser { sound_placement::external }; diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index badb39de..1daac212 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1314,6 +1314,8 @@ public: bool MED_Ncor = 0; // czy korekcja sily hamowania z uwzglednieniem nacisku double MED_MinBrakeReqED = 0; // minimalne zadanie sily hamowania uruchamiajace ED - ponizej tylko EP double MED_FrED_factor = 1; // mnoznik sily hamowania ED do korekty blendingu + double MED_ED_Delay1 = 0; // opoznienie wdrazania hamowania ED (pierwszy raz) + double MED_ED_Delay2 = 0; // opoznienie zwiekszania sily hamowania ED (kolejne razy) int DCEMUED_CC { 0 }; //na którym sprzęgu sprawdzać działanie ED double DCEMUED_EP_max_Vel{ 0.0 }; //maksymalna prędkość, przy której działa EP przy włączonym ED w jednostce (dla tocznych) @@ -1602,6 +1604,7 @@ public: double eimicSpeedCtrlIntegral = 0; /*calkowany blad ustawienia predkosci*/ double NewSpeed = 0; /*nowa predkosc do zadania*/ double MED_EPVC_CurrentTime = 0; /*aktualny czas licznika czasu korekcji siły EP*/ + double MED_ED_DelayTimer = 0; /*aktualny czas licznika opoznienia hamowania ED*/ /*-zmienne dla drezyny*/ double PulseForce = 0.0; /*przylozona sila*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 8839c4f3..6313505d 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -10534,6 +10534,8 @@ void TMoverParameters::LoadFIZ_Blending(std::string const &line) { extract_value(MED_Ncor, "MED_Ncor", line, ""); extract_value(MED_MinBrakeReqED, "MED_MinBrakeReqED", line, ""); extract_value(MED_FrED_factor, "MED_FrEDFactor", line, ""); + extract_value(MED_ED_Delay1, "MED_FirstDelayED", line, ""); + extract_value(MED_ED_Delay2, "MED_ScndDelayED", line, ""); } void TMoverParameters::LoadFIZ_DCEMUED(std::string const &line) { From 5775493b2de3f32ad9c41908155760dd898a2436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 1 Jan 2022 20:50:30 +0100 Subject: [PATCH 61/63] =?UTF-8?q?Poprawka=20asymilacji=20w=20K5P=20i=206P,?= =?UTF-8?q?=20ograniczenie=20interwencji=20automanewrowego=20w=20luzowanie?= =?UTF-8?q?=20pojazd=C3=B3w,=20zaliczanie=20pozycji=20utrzymania=20ci?= =?UTF-8?q?=C5=9Bnienia=20jako=20jazda?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Driver.cpp | 15 ++++++++++-- McZapkie/hamulce.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++-- McZapkie/hamulce.h | 10 ++++++++ driverhints.cpp | 3 ++- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index ecaf985d..f8c0eaff 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -2873,6 +2873,8 @@ bool TController::PrepareEngine() } // set up train brake if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_RP ) ) { + if ( ( !mvOccupied->Handle->Time) + || ( mvOccupied->Handle->GetCP() < mvOccupied->HighPipePress - 0.05 ) ); cue_action( locale::string::driver_hint_trainbrakerelease ); } // sync spring brake state across consist @@ -6023,16 +6025,25 @@ TController::determine_consist_state() { } // Ra: odluźnianie przeładowanych lokomotyw, ciągniętych na zimno - prowizorka... if( bp >= 0.4 ) { // wg UIC określone sztywno na 0.04 - if( AIControllFlag || Global.AITrainman ) { + if( AIControllFlag || (Global.AITrainman && mvOccupied->Vel < EU07_AI_NOMOVEMENT && !is_emu() && !is_dmu())) { if( ( BrakeCtrlPosition == gbh_RP ) // jest pozycja jazdy && ( false == TestFlag( vehicle->Hamulec->GetBrakeStatus(), b_dmg ) ) // brake isn't broken - && ( vehicle->PipePress - 5.0 > -0.1 ) // jeśli ciśnienie jak dla jazdy + && ( vehicle->PipePress - mvOccupied->Handle->GetRP() > -0.1 ) // jeśli ciśnienie jak dla jazdy && ( vehicle->Hamulec->GetCRP() > vehicle->PipePress + 0.12 ) ) { // za dużo w zbiorniku // indywidualne luzowanko vehicle->BrakeReleaser( 1 ); } } } + if (bp < 0.1) { + if ( AIControllFlag || Global.AITrainman ) { + if (( false == TestFlag( vehicle->Hamulec->GetBrakeStatus(), b_dmg ) ) // brake isn't broken + && ( vehicle->Hamulec->GetCRP() < vehicle->PipePress - 0.1 ) ) { // już nie jest za dużo w zbiorniku + // koniec indywidualnego luzowanka + vehicle->BrakeReleaser( 0 ); + } + } + } } fReady = std::max( bp, fReady ); // szukanie najbardziej zahamowanego if( ( dy = p->VectorFront().y ) != 0.0 ) { diff --git a/McZapkie/hamulce.cpp b/McZapkie/hamulce.cpp index b2ff9cd0..f0207ac8 100644 --- a/McZapkie/hamulce.cpp +++ b/McZapkie/hamulce.cpp @@ -2287,6 +2287,11 @@ double TDriverHandle::GetEP() return 0; } +double TDriverHandle::GetRP() +{ + return 0; +} + double TDriverHandle::GetSound(int i) { return 0; @@ -2562,6 +2567,11 @@ double TFV4aM::GetCP() return TP; } +double TFV4aM::GetRP() +{ + return 5.0 +TP * 0.08 + RedAdj; +} + double TFV4aM::LPP_RP(double pos) // cisnienie z zaokraglonej pozycji; { int const i_pos = 2 + std::floor( pos ); // zaokraglone w dol @@ -2698,6 +2708,11 @@ double TMHZ_EN57::GetCP() return RP; } +double TMHZ_EN57::GetRP() +{ + return 5.0 + RedAdj; +} + double TMHZ_EN57::GetEP(double pos) { if (pos < 9.5) @@ -2760,7 +2775,7 @@ double TMHZ_K5P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) } else { - TP = 0; + //TP = 0; //tu nie powinno być nic, ciśnienie zostaje jak było } if (EQ(i_bcp, 1)) //odcięcie - nie rób nic @@ -2846,6 +2861,11 @@ double TMHZ_K5P::GetCP() return CP; } +double TMHZ_K5P::GetRP() +{ + return 5.0 + TP + RedAdj; +} + void TMHZ_K5P::SetParams(bool AO, bool MO, double OverP, double, double OMP, double OPD) { AutoOvrld = AO; @@ -2890,7 +2910,7 @@ double TMHZ_6P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) } else { - TP = 0; + //TP = 0; //tu nie powinno być nic, ciśnienie zostaje jak było } if (EQ(i_bcp, 2)) //odcięcie - nie rób nic @@ -2982,6 +3002,11 @@ double TMHZ_6P::GetCP() return CP; } +double TMHZ_6P::GetRP() +{ + return 5.0 + TP + RedAdj; +} + void TMHZ_6P::SetParams(bool AO, bool MO, double OverP, double, double OMP, double OPD) { AutoOvrld = AO; @@ -3065,6 +3090,11 @@ double TM394::GetCP() return CP; } +double TM394::GetRP() +{ + return std::max(5.0, CP) + RedAdj; +} + double TM394::GetPos(int i) { return pos_table[i]; @@ -3121,6 +3151,11 @@ double TH14K1::GetCP() return CP; } +double TH14K1::GetRP() +{ + return 5.0 + RedAdj; +} + double TH14K1::GetPos(int i) { return pos_table[i]; @@ -3179,6 +3214,11 @@ double TSt113::GetCP() return CP; } +double TSt113::GetRP() +{ + return 5.0 + RedAdj; +} + double TSt113::GetEP() { return EPS; @@ -3351,6 +3391,11 @@ double TFVel6::GetCP() return CP; } +double TFVel6::GetRP() +{ + return 5.0; +} + double TFVel6::GetEP() { return EPS; @@ -3428,6 +3473,11 @@ double TFVE408::GetEP() return EPS; } +double TFVE408::GetRP() +{ + return 5.0; +} + double TFVE408::GetPos(int i) { return pos_table[i]; diff --git a/McZapkie/hamulce.h b/McZapkie/hamulce.h index b0d6eff4..4e78b920 100644 --- a/McZapkie/hamulce.h +++ b/McZapkie/hamulce.h @@ -542,6 +542,7 @@ class TDriverHandle { virtual void Init(double Press); virtual double GetCP(); virtual double GetEP(); + virtual double GetRP(); virtual void SetReductor(double nAdj); //korekcja pozycji reduktora cisnienia virtual double GetSound(int i); //pobranie glosnosci wybranego dzwieku virtual double GetPos(int i); //pobranie numeru pozycji o zadanym kodzie (funkcji) @@ -590,6 +591,7 @@ class TFV4aM : public TDriverHandle { double GetSound(int i)/*override*/; double GetPos(int i)/*override*/; double GetCP(); + double GetRP(); inline TFV4aM() : TDriverHandle() {} @@ -618,6 +620,7 @@ class TMHZ_EN57 : public TDriverHandle { double GetSound(int i)/*override*/; double GetPos(int i)/*override*/; double GetCP()/*override*/; + double GetRP()/*override*/; double GetEP(double pos); void SetParams(bool AO, bool MO, double OverP, double, double OMP, double OPD); inline TMHZ_EN57(void) : @@ -647,6 +650,7 @@ public: double GetSound(int i)/*override*/; double GetPos(int i)/*override*/; double GetCP()/*override*/; + double GetRP()/*override*/; void SetParams(bool AO, bool MO, double, double, double OMP, double OPD); /*ovveride*/ inline TMHZ_K5P(void) : @@ -676,6 +680,7 @@ public: double GetSound(int i)/*override*/; double GetPos(int i)/*override*/; double GetCP()/*override*/; + double GetRP()/*override*/; void SetParams(bool AO, bool MO, double, double, double OMP, double OPD); /*ovveride*/ inline TMHZ_6P(void) : @@ -725,6 +730,7 @@ class TM394 : public TDriverHandle { void Init(double Press)/*override*/; void SetReductor(double nAdj)/*override*/; double GetCP()/*override*/; + double GetRP()/*override*/; double GetPos(int i)/*override*/; inline TM394(void) : @@ -748,6 +754,7 @@ class TH14K1 : public TDriverHandle { void Init(double Press)/*override*/; void SetReductor(double nAdj)/*override*/; double GetCP()/*override*/; + double GetRP()/*override*/; double GetPos(int i)/*override*/; inline TH14K1(void) : @@ -767,6 +774,7 @@ class TSt113 : public TH14K1 { public: double GetPF(double i_bcp, double PP, double HP, double dt, double ep)/*override*/; double GetCP()/*override*/; + double GetRP()/*override*/; double GetEP()/*override*/; double GetPos(int i)/*override*/; void Init(double Press)/*override*/; @@ -837,6 +845,7 @@ class TFVel6 : public TDriverHandle { public: double GetPF(double i_bcp, double PP, double HP, double dt, double ep)/*override*/; double GetCP()/*override*/; + double GetRP()/*override*/; double GetEP()/*override*/; double GetPos(int i)/*override*/; double GetSound(int i)/*override*/; @@ -858,6 +867,7 @@ public: double GetPF(double i_bcp, double PP, double HP, double dt, double ep)/*override*/; double GetCP()/*override*/; double GetEP()/*override*/; + double GetRP()/*override*/; double GetPos(int i)/*override*/; double GetSound(int i)/*override*/; void Init(double Press)/*override*/; diff --git a/driverhints.cpp b/driverhints.cpp index 714082e6..57d03cb7 100644 --- a/driverhints.cpp +++ b/driverhints.cpp @@ -776,7 +776,8 @@ TController::cue_action( locale::string const Action, float const Actionparamete hint( Action, [this](float const Parameter) -> bool { - return ( is_equal( mvOccupied->fBrakeCtrlPos, mvOccupied->Handle->GetPos( bh_RP ), 0.2 ) ); } ); + return ( is_equal( mvOccupied->fBrakeCtrlPos, mvOccupied->Handle->GetPos( bh_RP ), 0.2 ) + || ( mvOccupied->Handle->Time && ( mvOccupied->Handle->GetCP() > mvOccupied->HighPipePress - 0.05) ) ); } ); // return ( BrakeCtrlPosition == gbh_RP ); } ); break; } From aeca0e25ee5d72fb7f22eae5c831bed9dd68914b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 1 Jan 2022 20:51:11 +0100 Subject: [PATCH 62/63] =?UTF-8?q?Przygotowanie=20do=20wska=C5=BAnika=20zah?= =?UTF-8?q?amowania=20EP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- McZapkie/MOVER.h | 1 + McZapkie/Mover.cpp | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 1daac212..c2c2139b 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1051,6 +1051,7 @@ public: bool Battery = false; /*Czy sa zalaczone baterie*/ start_t BatteryStart = start_t::manual; bool EpFuse = true; /*Czy sa zalavzone baterie*/ + double EpForce = 0.0; /*Poziom zadanej sily EP*/ bool Signalling = false; /*Czy jest zalaczona sygnalizacja hamowania ostatniego wagonu*/ bool Radio = false; /*Czy jest zalaczony radiotelefon*/ float NominalBatteryVoltage = 0.f; /*Winger - baterie w elektrykach*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 6313505d..0f3ac21e 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -4004,17 +4004,16 @@ bool TMoverParameters::UniversalBrakeButton(int button, int state) bool TMoverParameters::SwitchEPBrake(int state) { bool OK; - double temp; OK = false; if ((BrakeHandle == TBrakeHandle::St113) && (CabOccupied != 0)) { if (state > 0) - temp = Handle->GetEP(); // TODO: przetlumaczyc + EpForce = Handle->GetEP(); // TODO: przetlumaczyc else - temp = 0; - Hamulec->SetEPS(temp); - SendCtrlToNext("Brake", temp, CabActive); + EpForce = 0; + Hamulec->SetEPS(EpForce); + SendCtrlToNext("Brake", EpForce, CabActive); } // OK:=SetFlag(BrakeStatus,((2*State-1)*b_epused)); // SendCtrlToNext('Brake',(state*(2*BrakeCtrlPos-1)),CabActive); @@ -4572,25 +4571,25 @@ void TMoverParameters::UpdatePipePressure(double dt) && (DirActive != 0) && (EpFuse)) // tu powinien byc jeszcze bezpiecznik EP i baterie - // temp = (Handle as TFVel6).GetCP - temp = Handle->GetEP(); + EpForce = Handle->GetEP(); else - temp = 0.0; + EpForce = 0.0; DynamicBrakeEMUStatus = ( - temp > 0.001 ? + EpForce > 0.001 ? Power110vIsAvailable : true ); - double temp1 = temp; + double temp1 = EpForce; if ((DCEMUED_EP_max_Vel > 0.001) && (Vel > DCEMUED_EP_max_Vel) && (DynamicBrakeEMUStatus)) temp1 = 0; if ((DCEMUED_EP_min_Im > 0.001) && (abs(Im) > DCEMUED_EP_min_Im) && (DynamicBrakeEMUStatus)) temp1 = 0; Hamulec->SetEPS(temp1); - TUHEX_StageActual = temp; + TUHEX_StageActual = EpForce; TUHEX_Active = TUHEX_StageActual > 0; // Ra 2014-11: na tym się wysypuje, ale nie wiem, w jakich warunkach - SendCtrlToNext("Brake", temp, CabActive); + SendCtrlToNext("Brake", EpForce, CabActive); } Pipe->Act(); From d8170c932b448ba6888e702746b5ae22632062d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sat, 1 Jan 2022 20:55:52 +0100 Subject: [PATCH 63/63] =?UTF-8?q?Dodana=20lampka=20czuwaka/shp=20(2=20w=20?= =?UTF-8?q?1),=20dodany=20d=C5=BAwi=C4=99k/wskaz=C3=B3wka=20stycznika=20EP?= =?UTF-8?q?=20zale=C5=BCny=20od=20stopnia=20zahamowania,=20poszerzenie=20z?= =?UTF-8?q?akresu=20informacji=20wyj=C5=9Bciowych=20UART=20na=20potrzeby?= =?UTF-8?q?=20pulpitu=20EZT,=20poprawka=20d=C5=BAwi=C4=99ku=20hamulca=20po?= =?UTF-8?q?mocniczego,=20poprawka=20pozycjonowania=20d=C5=BAwi=C4=99k?= =?UTF-8?q?=C3=B3w=20w=20kabinach,=20dodane=20sterowanie=20procentowe=20po?= =?UTF-8?q?=20UART?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Globals.cpp | 6 +++ Train.cpp | 112 ++++++++++++++++++++++++++++++++++++---------------- Train.h | 14 ++++++- uart.cpp | 45 ++++++++++++++++----- uart.h | 2 + 5 files changed, 133 insertions(+), 46 deletions(-) diff --git a/Globals.cpp b/Globals.cpp index 1e0bf3eb..bdf751c5 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -732,6 +732,12 @@ global_settings::ConfigParse(cParser &Parser) { Parser.getTokens(1); Parser >> uart_conf.debug; } + else if (token == "uartmainpercentage") + { + Parser.getTokens(1); + Parser >> uart_conf.mainpercentage; + } + #endif #ifdef USE_EXTCAM_CAMERA else if (token == "extcam.cmd") diff --git a/Train.cpp b/Train.cpp index ceb2349e..dee4cd86 100644 --- a/Train.cpp +++ b/Train.cpp @@ -774,39 +774,50 @@ dictionary_source *TTrain::GetTrainState( dictionary_source const &Extraparamete TTrain::state_t TTrain::get_state() const { - return { - btLampkaSHP.GetValue(), - btLampkaCzuwaka.GetValue(), - btLampkaRadioStop.GetValue(), - btLampkaOpory.GetValue(), - btLampkaWylSzybki.GetValue(), - btLampkaPrzekRozn.GetValue(), - btLampkaNadmSil.GetValue(), - btLampkaStyczn.GetValue(), - btLampkaPoslizg.GetValue(), - btLampkaNadmPrzetw.GetValue(), - btLampkaPrzetwOff.GetValue(), - btLampkaNadmSpr.GetValue(), - btLampkaNadmWent.GetValue(), - btLampkaWysRozr.GetValue(), - btLampkaOgrzewanieSkladu.GetValue(), - static_cast( iCabn ), - btHaslerBrakes.GetValue(), - btHaslerCurrent.GetValue(), - mvOccupied->SecuritySystem.is_beeping(), - btLampkaHVoltageB.GetValue(), - fTachoVelocity, - static_cast( mvOccupied->Compressor ), - static_cast( mvOccupied->PipePress ), - static_cast( mvOccupied->BrakePress ), - static_cast( mvPantographUnit->PantPress ), - fHVoltage, - { fHCurrent[ ( mvControlled->TrainType & dt_EZT ) ? 0 : 1 ], fHCurrent[ 2 ], fHCurrent[ 3 ] }, - ggLVoltage.GetValue(), - mvOccupied->DistCounter, - static_cast( RadioChannel() ), - btLampkaSpringBrakeActive.GetValue(), - btLampkaNapNastHam.GetValue(), + return { + btLampkaSHP.GetValue(), + (TestFlag(mvOccupied->SecuritySystem.Status, s_aware)) + || (TestFlag(mvOccupied->SecuritySystem.Status, s_CAtest)), + btLampkaRadioStop.GetValue(), + btLampkaOpory.GetValue(), + btLampkaWylSzybki.GetValue(), + btLampkaPrzekRozn.GetValue(), + btLampkaNadmSil.GetValue(), + btLampkaStyczn.GetValue(), + btLampkaPoslizg.GetValue(), + btLampkaNadmPrzetw.GetValue(), + btLampkaPrzetwOff.GetValue(), + btLampkaNadmSpr.GetValue(), + btLampkaNadmWent.GetValue(), + btLampkaWysRozr.GetValue(), + btLampkaOgrzewanieSkladu.GetValue(), + static_cast(iCabn), + btHaslerBrakes.GetValue(), + btHaslerCurrent.GetValue(), + mvOccupied->SecuritySystem.is_beeping(), + btLampkaHVoltageB.GetValue(), + fTachoVelocity, + static_cast(mvOccupied->Compressor), + static_cast(mvOccupied->PipePress), + static_cast(mvOccupied->BrakePress), + static_cast(mvPantographUnit->PantPress), + fHVoltage, + { fHCurrent[(mvControlled->TrainType & dt_EZT) ? 0 : 1], fHCurrent[2], fHCurrent[3] }, + ggLVoltage.GetValue(), + mvOccupied->DistCounter, + static_cast(RadioChannel()), + btLampkaSpringBrakeActive.GetValue(), + btLampkaNapNastHam.GetValue(), + mvOccupied->DirActive > 0, + mvOccupied->DirActive < 0, + mvOccupied->Doors.instances[mvOccupied->CabOccupied < 0 ? side::right : side::left].open_permit, + mvOccupied->Doors.instances[mvOccupied->CabOccupied < 0 ? side::right : side::left].is_open, + mvOccupied->Doors.instances[mvOccupied->CabOccupied < 0 ? side::left : side::right].open_permit, + mvOccupied->Doors.instances[mvOccupied->CabOccupied < 0 ? side::left : side::right].is_open, + mvOccupied->Doors.step_enabled, + mvOccupied->Power24vIsAvailable, + 0, + mvOccupied->LockPipe }; } @@ -6766,11 +6777,13 @@ bool TTrain::Update( double const Deltatime ) btLampkaCzuwaka.Turn( false ); btLampkaSHP.Turn( TestFlag( mvOccupied->SecuritySystem.Status, s_active ) ); + btLampkaCzuwakaSHP.Turn( btLampkaSHP.GetValue() || btLampkaCzuwaka.GetValue() ); } else // wylaczone { btLampkaCzuwaka.Turn( false ); btLampkaSHP.Turn( false ); + btLampkaCzuwakaSHP.Turn( false ); } btLampkaWylSzybki.Turn( @@ -6960,6 +6973,7 @@ bool TTrain::Update( double const Deltatime ) // wylaczone btLampkaCzuwaka.Turn( false ); btLampkaSHP.Turn( false ); + btLampkaCzuwakaSHP.Turn( false ); btLampkaWylSzybki.Turn( false ); btLampkaWylSzybkiOff.Turn( false ); btLampkaMainBreakerReady.Turn( false ); @@ -7607,7 +7621,7 @@ TTrain::update_sounds( double const Deltatime ) { // jesli nie FV4a // upuszczanie z PG if( rsHiss ) { - fPPress = ( 4.0f * fPPress + std::max( mvOccupied->dpLocalValve, mvOccupied->dpMainValve ) ) / ( 4.0f + 1.0f ); + fPPress = ( 4.0f * fPPress + std::max( 0.0, mvOccupied->dpMainValve ) ) / ( 4.0f + 1.0f ); volume = ( fPPress > 0.0f ? 2.0 * rsHiss->m_amplitudefactor * fPPress : @@ -7622,7 +7636,7 @@ TTrain::update_sounds( double const Deltatime ) { } // napelnianie PG if( rsHissU ) { - fNPress = ( 4.0f * fNPress + Min0R( mvOccupied->dpLocalValve, mvOccupied->dpMainValve ) ) / ( 4.0f + 1.0f ); + fNPress = ( 4.0f * fNPress + Min0R( 0.0, mvOccupied->dpMainValve ) ) / ( 4.0f + 1.0f ); volume = ( fNPress < 0.0f ? -1.0 * rsHissU->m_amplitudefactor * fNPress : @@ -7958,6 +7972,7 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) } // NOTE: since radiosound is an incomplete template not using std::optional it gets a special treatment m_radiosound.owner( DynamicObject ); + CabSoundLocations.clear(); cParser parser( asFileName, cParser::buffer_FILE, DynamicObject->asBaseDir ); // NOTE: yaml-style comments are disabled until conflict in use of # is resolved @@ -8019,6 +8034,19 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) rsHuntingNoise->m_frequencyfactor /= (1 + mvOccupied->Vmax); } } + auto const nullvector{ glm::vec3() }; + std::vector>> sounds = { + dsbReverserKey, dsbNastawnikJazdy, dsbNastawnikBocz, + dsbSwitch, dsbPneumaticSwitch, + rsHiss, rsHissU, rsHissE, rsHissX, rsHissT, rsSBHiss, rsSBHissU, + rsFadeSound, rsRunningNoise, rsHuntingNoise, + dsbHasler, dsbBuzzer, dsbSlipAlarm, m_distancecounterclear, m_rainsound, m_radiostop + }; + for (auto &sound : sounds) { + if (sound.get()) { + CabSoundLocations.emplace_back(sound, sound.get()->offset()); + } + } return true; } @@ -8043,6 +8071,12 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) } } m_radiosound.offset( nullvector ); + for (auto &sound : CabSoundLocations) { + if ((sound.first.get()) + && (sound.first.get()->offset() == nullvector)) { + sound.first.get()->offset(sound.second); + } + } // reset view angles pMechViewAngle = { 0.0, 0.0 }; @@ -9188,6 +9222,7 @@ bool TTrain::initialize_button(cParser &Parser, std::string const &Label, int co { "i-trainheating:", btLampkaOgrzewanieSkladu }, { "i-security_aware:", btLampkaCzuwaka }, { "i-security_cabsignal:", btLampkaSHP }, + { "i-security_aware_cabsignal:", btLampkaCzuwakaSHP }, { "i-door_left:", btLampkaDoorLeft }, { "i-door_right:", btLampkaDoorRight }, { "i-departure_signal:", btLampkaDepartureSignal }, @@ -9652,6 +9687,13 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con gauge.Load(Parser, DynamicObject, 0.1); gauge.AssignDouble(&mvOccupied->SpringBrake.SBP); } + else if (Label == "epctrlvalue:") + { + // wskazowka sterowania sila hamulca ep + auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka + gauge.Load(Parser, DynamicObject, 0.1); + gauge.AssignDouble(&mvOccupied->EpForce); + } else if ((Label == "compressor:") || (Label == "compressorb:")) { // manometr sprezarki/zbiornika glownego diff --git a/Train.h b/Train.h index 604b4613..eaeb14f1 100644 --- a/Train.h +++ b/Train.h @@ -112,6 +112,16 @@ class TTrain { std::uint8_t radio_channel; std::uint8_t springbrake_active; std::uint8_t epbrake_enabled; + std::uint8_t dir_forward; + std::uint8_t dir_backward; + std::uint8_t doorleftallowed; + std::uint8_t doorleftopened; + std::uint8_t doorrightallowed; + std::uint8_t doorrightopened; + std::uint8_t doorstepallowed; + std::uint8_t battery; + std::uint8_t emergencybrake; + std::uint8_t lockpipe; }; struct screen_entry { @@ -671,7 +681,8 @@ public: // reszta może by?publiczna TButton btLampkaOgrzewanieSkladu; TButton btLampkaSHP; TButton btLampkaCzuwaka; // McZapkie-141102 - TButton btLampkaRezerwa; + TButton btLampkaCzuwakaSHP; + TButton btLampkaRezerwa; // youBy - jakies dodatkowe lampki TButton btLampkaNapNastHam; TButton btLampkaSprezarka; @@ -753,6 +764,7 @@ public: // reszta może by?publiczna m_rainsound; sound_source m_radiosound { sound_placement::internal, 2 * EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // cached template for radio messages std::vector>> m_radiomessages; // list of currently played radio messages + std::vector>, glm::vec3>> CabSoundLocations; // list of offsets for manually located sounds; float m_lastlocalbrakepressure { -1.f }; // helper, cached level of pressure in local brake cylinder float m_localbrakepressurechange { 0.f }; // recent change of pressure in local brake cylinder /* diff --git a/uart.cpp b/uart.cpp index 3ce34458..6005a24c 100644 --- a/uart.cpp +++ b/uart.cpp @@ -277,13 +277,28 @@ void uart_input::poll() if( true == conf.mainenable ) { // master controller - relay.post( - user_command::mastercontrollerset, - buffer[ 6 ], - 0, - GLFW_PRESS, - // TODO: pass correct entity id once the missing systems are in place - 0 ); + if (false == conf.mainpercentage) { + //old method for direct positions + relay.post( + user_command::mastercontrollerset, + buffer[6], + 0, + GLFW_PRESS, + // TODO: pass correct entity id once the missing systems are in place + 0); + } + else { + auto desiredpercent{ buffer[6] * 0.01 }; + auto desiredposition{ desiredpercent > 0.01 ? 1 + ((simulation::Train->Occupied()->MainCtrlPosNo - 1) * desiredpercent) : buffer[6] }; + relay.post( + user_command::mastercontrollerset, + desiredposition, + 0, + GLFW_PRESS, + // TODO: pass correct entity id once the missing systems are in place + 0); + simulation::Train->Occupied()->eimic_analog = desiredpercent; + } } if( true == conf.scndenable ) { // second controller @@ -357,10 +372,20 @@ void uart_input::poll() (uint8_t)( trainstate.epbrake_enabled << 0 | trainstate.ventilator_overload << 1 - | trainstate.motor_overload_threshold << 2), + | trainstate.motor_overload_threshold << 2 + | trainstate.emergencybrake << 3 + | trainstate.lockpipe << 4 + | trainstate.dir_forward << 5 + | trainstate.dir_backward << 6), //byte 3 (uint8_t)( - trainstate.coupled_hv_voltage_relays << 0), + trainstate.coupled_hv_voltage_relays << 0 + | trainstate.doorleftallowed << 1 + | trainstate.doorleftopened << 2 + | trainstate.doorrightallowed << 3 + | trainstate.doorrightopened << 4 + | trainstate.doorstepallowed << 5 + | trainstate.battery << 6), //byte 4 (uint8_t)( trainstate.train_heating << 0 @@ -412,7 +437,7 @@ void uart_input::poll() (uint8_t)trainstate.radio_channel, //byte 34-35 SPLIT_INT16(pantograph_press), - //byte 36-48 + //byte 36-47 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; diff --git a/uart.h b/uart.h index 649c29ec..16437082 100644 --- a/uart.h +++ b/uart.h @@ -39,6 +39,8 @@ public: bool localenable = true; bool debug = false; + + bool mainpercentage = false; }; // methods