From b87be915213aff68dcadfb7a5178b02cf7804460 Mon Sep 17 00:00:00 2001 From: Overside srl Date: Tue, 7 Jul 2026 09:42:23 +0200 Subject: [PATCH 1/3] fix comprenetration train-wagons --- core/biome_generator/rails.gd | 96 ++++++++++++++++++++++++++++-- tgcc/main menu/main_menu_test.tscn | 2 +- tgcc/main_scene.tscn | 3 +- 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/core/biome_generator/rails.gd b/core/biome_generator/rails.gd index 0ab7599..9e63577 100644 --- a/core/biome_generator/rails.gd +++ b/core/biome_generator/rails.gd @@ -3,6 +3,12 @@ extends Path3D const STEAM_DISTANCE_STAT: String = "stat_distance_km" const DISTANCE_KM_PER_UNIT: float = 0.01 const STATION_STOP_GROUP: StringName = &"railway_station" +const TRACK_ZERO_SPACING_FIX_DISTANCE: float = 45.0 +const TRACK_ZERO_SPACING_BLEND_DISTANCE: float = 18.0 +const TRACK_ZERO_SPACING_SEARCH_EXTRA: float = 35.0 +const TRACK_ZERO_SPACING_SEARCH_STEP: float = 0.5 +const TRACK_ZERO_SPACING_TARGET_RATIO: float = 0.97 +const TRACK_ZERO_SPACING_MIN_DISTANCE: float = 1.0 enum TrainStartMode { RANDOM_POSITION, SPECIFIED_STATION, SPECIFIED_POSITION } @@ -420,7 +426,10 @@ func train_move(delta: float) -> void: stop_multiply = 1.0 var current_speed = train_speed * stop_multiply - train_progress += current_speed * delta + var next_progress: float = train_progress + current_speed * delta + var wrapped_forward: bool = current_speed > 0.0 and next_progress >= total_length + var wrapped_back: bool = current_speed < 0.0 and next_progress < 0.0 + train_progress = wrapf(next_progress, 0.0, total_length) _track_steam_distance(abs(current_speed) * delta) if enable_stops and stop_offset.size() > 0 and has_valid_stop: @@ -432,11 +441,9 @@ func train_move(delta: float) -> void: train_progress = target_offset _execute_stop(current_speed > 0) - if train_progress > total_length: - train_progress -= total_length + if wrapped_forward: if stop_offset.size() > 0: next_stop_index = 0 - elif train_progress < 0: - train_progress += total_length + elif wrapped_back: if stop_offset.size() > 0: next_stop_index = stop_offset.size() - 1 var center_position = to_global(curve.sample_baked(train_progress, true)) @@ -659,6 +666,10 @@ func _snap_wagons_to_progress(total_length: float = 0.0) -> void: if total_length <= 0.0: return + var previous_progress: float = wrapf(train_progress, 0.0, total_length) + var previous_position: Vector3 = to_global(curve.sample_baked(previous_progress, true)) + var previous_wagon_offset: float = 0.0 + for i in range(wagon_instances.size()): var wagon: Node3D = wagon_instances[i] if not is_instance_valid(wagon): @@ -669,9 +680,17 @@ func _snap_wagons_to_progress(total_length: float = 0.0) -> void: wagon_offset = wagon_progress_offsets[i] var wagon_progress: float = train_progress - wagon_offset + var segment_spacing: float = maxf(wagon_offset - previous_wagon_offset, 0.1) var vehicle_progress: float = wrapf(wagon_progress, 0.0, total_length) var center_position: Vector3 = to_global(curve.sample_baked(vehicle_progress, true)) + var zero_spacing_fix_weight: float = _get_zero_spacing_fix_weight(previous_progress, vehicle_progress, total_length) + if zero_spacing_fix_weight > 0.0: + var target_distance: float = _get_zero_spacing_target_distance(segment_spacing) + var corrected_progress: float = _find_zero_spacing_progress(previous_progress, previous_position, segment_spacing, target_distance, total_length) + vehicle_progress = _lerp_progress_on_track(vehicle_progress, corrected_progress, zero_spacing_fix_weight, total_length) + center_position = to_global(curve.sample_baked(vehicle_progress, true)) + var prog_forward: float = wrapf(vehicle_progress + 2.0, 0.0, total_length) var forward_position: Vector3 = to_global(curve.sample_baked(prog_forward, true)) @@ -680,6 +699,73 @@ func _snap_wagons_to_progress(total_length: float = 0.0) -> void: wagon.look_at(forward_position, Vector3.UP) wagon.rotate_y(PI) + previous_progress = vehicle_progress + previous_position = center_position + previous_wagon_offset = wagon_offset + +func _get_zero_spacing_fix_weight(previous_progress: float, vehicle_progress: float, total_length: float) -> float: + var distance_to_zero: float = minf( + _get_progress_distance_to_zero(previous_progress, total_length), + _get_progress_distance_to_zero(vehicle_progress, total_length) + ) + var full_fix_distance: float = maxf(TRACK_ZERO_SPACING_FIX_DISTANCE - TRACK_ZERO_SPACING_BLEND_DISTANCE, 0.0) + return 1.0 - smoothstep(full_fix_distance, TRACK_ZERO_SPACING_FIX_DISTANCE, distance_to_zero) + +func _get_progress_distance_to_zero(progress: float, total_length: float) -> float: + var wrapped_progress: float = wrapf(progress, 0.0, total_length) + return minf(wrapped_progress, total_length - wrapped_progress) + +func _get_zero_spacing_target_distance(segment_spacing: float) -> float: + return minf(maxf(segment_spacing * TRACK_ZERO_SPACING_TARGET_RATIO, TRACK_ZERO_SPACING_MIN_DISTANCE), segment_spacing) + +func _find_zero_spacing_progress(previous_progress: float, previous_position: Vector3, segment_spacing: float, minimum_distance: float, total_length: float) -> float: + var start_distance: float = minf(minimum_distance, segment_spacing) + var best_progress: float = wrapf(previous_progress - start_distance, 0.0, total_length) + var search_limit: float = minf(maxf(segment_spacing, minimum_distance) + TRACK_ZERO_SPACING_SEARCH_EXTRA, total_length) + var search_distance: float = start_distance + var previous_search_distance: float = start_distance + + while search_distance <= search_limit: + var candidate_progress: float = wrapf(previous_progress - search_distance, 0.0, total_length) + var candidate_position: Vector3 = to_global(curve.sample_baked(candidate_progress, true)) + best_progress = candidate_progress + if previous_position.distance_to(candidate_position) >= minimum_distance: + if is_equal_approx(search_distance, start_distance): + return candidate_progress + + return _refine_zero_spacing_progress( + previous_progress, + previous_position, + previous_search_distance, + search_distance, + minimum_distance, + total_length + ) + + previous_search_distance = search_distance + search_distance += TRACK_ZERO_SPACING_SEARCH_STEP + + return best_progress + +func _refine_zero_spacing_progress(previous_progress: float, previous_position: Vector3, min_search_distance: float, max_search_distance: float, minimum_distance: float, total_length: float) -> float: + var low_distance: float = min_search_distance + var high_distance: float = max_search_distance + + for i in range(8): + var mid_distance: float = (low_distance + high_distance) * 0.5 + var mid_progress: float = wrapf(previous_progress - mid_distance, 0.0, total_length) + var mid_position: Vector3 = to_global(curve.sample_baked(mid_progress, true)) + if previous_position.distance_to(mid_position) >= minimum_distance: + high_distance = mid_distance + else: + low_distance = mid_distance + + return wrapf(previous_progress - high_distance, 0.0, total_length) + +func _lerp_progress_on_track(from_progress: float, to_progress: float, weight: float, total_length: float) -> float: + var wrapped_delta: float = wrapf(to_progress - from_progress + total_length * 0.5, 0.0, total_length) - total_length * 0.5 + return wrapf(from_progress + wrapped_delta * clampf(weight, 0.0, 1.0), 0.0, total_length) + func _get_vehicle_length(vehicle: Node3D) -> float: var bounds: AABB = _get_node_local_bounds(vehicle, vehicle) if bounds.size == Vector3.ZERO: diff --git a/tgcc/main menu/main_menu_test.tscn b/tgcc/main menu/main_menu_test.tscn index a920d1c..da80c6d 100644 --- a/tgcc/main menu/main_menu_test.tscn +++ b/tgcc/main menu/main_menu_test.tscn @@ -361,7 +361,7 @@ biome_list = Array[ExtResource("15_3blen")]([SubResource("Resource_n3osn")]) entity_pool = ExtResource("78_xg6nk") entity_spawn_probability = 0.0 max_entities_per_chunk = 0 -eye_line = 6 +eye_line = 7 lamppost_max_wire_distance = 0.0 lamppost_rail_clearance_distance = 3.0 diff --git a/tgcc/main_scene.tscn b/tgcc/main_scene.tscn index 643e005..6ff2423 100644 --- a/tgcc/main_scene.tscn +++ b/tgcc/main_scene.tscn @@ -190,7 +190,7 @@ data = PackedByteArray("//vQZAAOhpd2PgtGR7JsJPfgYSYSJ6387g9rgIGKF+EZgxxoAM4TYIWm closed = true bake_interval = 60.0 _data = { -"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, -4.372, 0, 140, 0, 0, 0, 0, 0, 0, -20.963, 0, 160, 0, 0, 0, 0, 0, 0, -40.62518, 0, 168.52754, 0, 0, 0, 0, 0, 0, -60, 0, 170, 0, 0, 0, 0, 0, 0, -80, 0, 170, 0, 0, 0, 0, 0, 0, -100, 0, 170, 0, 0, 0, 0, 0, 0, -120.42937, 0, 171.50279, 0, 0, 0, 0, 0, 0, -141.41747, 7.6293945e-06, 180.5982, 0, 0, 0, 0, 0, 0, -155.77005, 7.6293945e-06, 198.35184, 0, 0, 0, 0, 0, 0, -160.14105, 7.6293945e-06, 219.80951, 0, 0, 0, 0, 0, 0, -163.99007, 7.6293945e-06, 239.36005, 0, 0, 0, 0, 0, 0, -175.77509, 7.6293945e-06, 256.16028, 0, 0, 0, 0, 0, 0, -195.62283, 7.6293945e-06, 267.46912, 0, 0, 0, 0, 0, 0, -219.9999, 7.6293945e-06, 270.11758, 0, 0, 0, 0, 0, 0, -240, 0, 270.118, 0, 0, 0, 0, 0, 0, -260, 0, 270.118, 0, 0, 0, 0, 0, 0, -275.19666, 7.6293945e-06, 270.55228, 0, 0, 0, 0, 0, 0, -294.2925, 7.6293945e-06, 276.05307, 0, 0, 0, 0, 0, 0, -307.19006, 7.6293945e-06, 286.9095, 0, 0, 0, 0, 0, 0, -317.32443, 7.6293945e-06, 303.14682, 0, 0, 0, 0, 0, 0, -320, 0, 320, 0, 0, 0, 0, 0, 0, -320, 0, 340, 0, 0, 0, 0, 0, 0, -320, 0, 360, 0, 0, 0, 0, 0, 0, -320, 0, 380, 0, 0, 0, 0, 0, 0, -316.78876, 7.6293945e-06, 396.91193, 0, 0, 0, 0, 0, 0, -307.68207, 7.6293945e-06, 412.70596, 0, 0, 0, 0, 0, 0, -291.08273, 7.6293945e-06, 424.7491, 0, 0, 0, 0, 0, 0, -270, 0, 429.994, 0, 0, 0, 0, 0, 0, -250, 0, 429.994, 0, 0, 0, 0, 0, 0, -229.90146, 7.6293945e-06, 433.90823, 0, 0, 0, 0, 0, 0, -217.15442, 7.6293945e-06, 442.98242, 0, 0, 0, 0, 0, 0, -204.60803, 7.6293945e-06, 459.93704, 0, 0, 0, 0, 0, 0, -200, 0, 480, 0, 0, 0, 0, 0, 0, -200, 0, 500, 0, 0, 0, 0, 0, 0, -195.5338, 7.6293945e-06, 520.0368, 0, 0, 0, 0, 0, 0, -184.9173, 7.6293945e-06, 535.07776, 0, 0, 0, 0, 0, 0, -170.36423, 7.6293945e-06, 545.0799, 0, 0, 0, 0, 0, 0, -150.56184, 0, 549.8127, 0, 0, 0, 0, 0, 0, -130, 0, 550, 0, 0, 0, 0, 0, 0, -110, 0, 550, 0, 0, 0, 0, 0, 0, -90, 0, 550, 0, 0, 0, 0, 0, 0, -70, 0, 550, 0, 0, 0, 0, 0, 0, -50, 0, 550, 0, 0, 0, 0, 0, 0, -29.862535, 7.6293945e-06, 545.3371, 0, 0, 0, 0, 0, 0, -13.215244, 7.6293945e-06, 533.0626, 0, 0, 0, 0, 0, 0, -4.098665, 7.6293945e-06, 519.6814, 0, 0, 0, 0, 0, 0, -0.048213482, 7.6293945e-06, 500.107, 0, 0, 0, 0, 0, 0, 3.7920675, 7.6293945e-06, 480.64902, 0, 0, 0, 0, 0, 0, 13.513225, 7.6293945e-06, 465.9133, 0, 0, 0, 0, 0, 0, 29.76715, 7.6293945e-06, 454.41486, 0, 0, 0, 0, 0, 0, 49.711926, 7.6293945e-06, 450.12488, 0, 0, 0, 0, 0, 0, 70, 0, 450, 0, 0, 0, 0, 0, 0, 90, 0, 450, 0, 0, 0, 0, 0, 0, 110.055405, 7.6293945e-06, 445.42627, 0, 0, 0, 0, 0, 0, 126.975784, 7.6293945e-06, 432.8683, 0, 0, 0, 0, 0, 0, 136.95703, 7.6293945e-06, 417.5506, 0, 0, 0, 0, 0, 0, 140, 0, 400, 0, 0, 0, 0, 0, 0, 140, 0, 380, 0, 0, 0, 0, 0, 0, 140, 0, 360, 0, 0, 0, 0, 0, 0, 140, 0, 340, 0, 0, 0, 0, 0, 0, 140, 0, 320, 0, 0, 0, 0, 0, 0, 140, 0, 300, 0, 0, 0, 0, 0, 0, 140, 0, 280, 0, 0, 0, 0, 0, 0, 140, 0, 260, 0, 0, 0, 0, 0, 0, 140, 0, 240, 0, 0, 0, 0, 0, 0, 140, 0, 220, 0, 0, 0, 0, 0, 0, 140, 0, 200, 0, 0, 0, 0, 0, 0, 140, 0, 180, 0, 0, 0, 0, 0, 0, 140, 0, 160, 0, 0, 0, 0, 0, 0, 140, 0, 140, 0, 0, 0, 0, 0, 0, 140, 0, 120, 0, 0, 0, 0, 0, 0, 140, 0, 100, 0, 0, 0, 0, 0, 0, 140, 0, 80, 0, 0, 0, 0, 0, 0, 140, 0, 60, 0, 0, 0, 0, 0, 0, 140, 0, 40, 0, 0, 0, 0, 0, 0, 140.26848, 0, 20, 0, 0, 0, 0, 0, 0, 140, 0, 0, 0, 0, 0, 0, 0, 0, 136.2151, 7.6293945e-06, -18.930761, 0, 0, 0, 0, 0, 0, 124.47424, 7.6293945e-06, -35.771675, 0, 0, 0, 0, 0, 0, 109.556854, 7.6293945e-06, -45.911987, 0, 0, 0, 0, 0, 0, 90, 0, -50, 0, 0, 0, 0, 0, 0, 70, 0, -50, 0, 0, 0, 0, 0, 0, 50, 0, -50, 0, 0, 0, 0, 0, 0, 30.626865, 7.6293945e-06, -46.35706, 0, 0, 0, 0, 0, 0, 14.519331, 7.6293945e-06, -35.468895, 0, 0, 0, 0, 0, 0, 3.52941, 7.6293945e-06, -19.590664), +"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0.717, 0, 4.915, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, -4.372, 0, 140, 0, 0, 0, 0, 0, 0, -20.963, 0, 160, 0, 0, 0, 0, 0, 0, -40.62518, 0, 168.52754, 0, 0, 0, 0, 0, 0, -60, 0, 170, 0, 0, 0, 0, 0, 0, -80, 0, 170, 0, 0, 0, 0, 0, 0, -100, 0, 170, 0, 0, 0, 0, 0, 0, -120.42937, 0, 171.50279, 0, 0, 0, 0, 0, 0, -141.41747, 7.6293945e-06, 180.5982, 0, 0, 0, 0, 0, 0, -155.77005, 7.6293945e-06, 198.35184, 0, 0, 0, 0, 0, 0, -160.14105, 7.6293945e-06, 219.80951, 0, 0, 0, 0, 0, 0, -163.99007, 7.6293945e-06, 239.36005, 0, 0, 0, 0, 0, 0, -175.77509, 7.6293945e-06, 256.16028, 0, 0, 0, 0, 0, 0, -195.62283, 7.6293945e-06, 267.46912, 0, 0, 0, 0, 0, 0, -219.9999, 7.6293945e-06, 270.11758, 0, 0, 0, 0, 0, 0, -240, 0, 270.118, 0, 0, 0, 0, 0, 0, -260, 0, 270.118, 0, 0, 0, 0, 0, 0, -275.19666, 7.6293945e-06, 270.55228, 0, 0, 0, 0, 0, 0, -294.2925, 7.6293945e-06, 276.05307, 0, 0, 0, 0, 0, 0, -307.19006, 7.6293945e-06, 286.9095, 0, 0, 0, 0, 0, 0, -317.32443, 7.6293945e-06, 303.14682, 0, 0, 0, 0, 0, 0, -320, 0, 320, 0, 0, 0, 0, 0, 0, -320, 0, 340, 0, 0, 0, 0, 0, 0, -320, 0, 360, 0, 0, 0, 0, 0, 0, -320, 0, 380, 0, 0, 0, 0, 0, 0, -316.78876, 7.6293945e-06, 396.91193, 0, 0, 0, 0, 0, 0, -307.68207, 7.6293945e-06, 412.70596, 0, 0, 0, 0, 0, 0, -291.08273, 7.6293945e-06, 424.7491, 0, 0, 0, 0, 0, 0, -270, 0, 429.994, 0, 0, 0, 0, 0, 0, -250, 0, 429.994, 0, 0, 0, 0, 0, 0, -229.90146, 7.6293945e-06, 433.90823, 0, 0, 0, 0, 0, 0, -217.15442, 7.6293945e-06, 442.98242, 0, 0, 0, 0, 0, 0, -204.60803, 7.6293945e-06, 459.93704, 0, 0, 0, 0, 0, 0, -200, 0, 480, 0, 0, 0, 0, 0, 0, -200, 0, 500, 0, 0, 0, 0, 0, 0, -195.5338, 7.6293945e-06, 520.0368, 0, 0, 0, 0, 0, 0, -184.9173, 7.6293945e-06, 535.07776, 0, 0, 0, 0, 0, 0, -170.36423, 7.6293945e-06, 545.0799, 0, 0, 0, 0, 0, 0, -150.56184, 0, 549.8127, 0, 0, 0, 0, 0, 0, -130, 0, 550, 0, 0, 0, 0, 0, 0, -110, 0, 550, 0, 0, 0, 0, 0, 0, -90, 0, 550, 0, 0, 0, 0, 0, 0, -70, 0, 550, 0, 0, 0, 0, 0, 0, -50, 0, 550, 0, 0, 0, 0, 0, 0, -29.862535, 7.6293945e-06, 545.3371, 0, 0, 0, 0, 0, 0, -13.215244, 7.6293945e-06, 533.0626, 0, 0, 0, 0, 0, 0, -4.098665, 7.6293945e-06, 519.6814, 0, 0, 0, 0, 0, 0, -0.048213482, 7.6293945e-06, 500.107, 0, 0, 0, 0, 0, 0, 3.7920675, 7.6293945e-06, 480.64902, 0, 0, 0, 0, 0, 0, 13.513225, 7.6293945e-06, 465.9133, 0, 0, 0, 0, 0, 0, 29.76715, 7.6293945e-06, 454.41486, 0, 0, 0, 0, 0, 0, 49.711926, 7.6293945e-06, 450.12488, 0, 0, 0, 0, 0, 0, 70, 0, 450, 0, 0, 0, 0, 0, 0, 90, 0, 450, 0, 0, 0, 0, 0, 0, 110.055405, 7.6293945e-06, 445.42627, 0, 0, 0, 0, 0, 0, 126.975784, 7.6293945e-06, 432.8683, 0, 0, 0, 0, 0, 0, 136.95703, 7.6293945e-06, 417.5506, 0, 0, 0, 0, 0, 0, 140, 0, 400, 0, 0, 0, 0, 0, 0, 140, 0, 380, 0, 0, 0, 0, 0, 0, 140, 0, 360, 0, 0, 0, 0, 0, 0, 140, 0, 340, 0, 0, 0, 0, 0, 0, 140, 0, 320, 0, 0, 0, 0, 0, 0, 140, 0, 300, 0, 0, 0, 0, 0, 0, 140, 0, 280, 0, 0, 0, 0, 0, 0, 140, 0, 260, 0, 0, 0, 0, 0, 0, 140, 0, 240, 0, 0, 0, 0, 0, 0, 140, 0, 220, 0, 0, 0, 0, 0, 0, 140, 0, 200, 0, 0, 0, 0, 0, 0, 140, 0, 180, 0, 0, 0, 0, 0, 0, 140, 0, 160, 0, 0, 0, 0, 0, 0, 140, 0, 140, 0, 0, 0, 0, 0, 0, 140, 0, 120, 0, 0, 0, 0, 0, 0, 140, 0, 100, 0, 0, 0, 0, 0, 0, 140, 0, 80, 0, 0, 0, 0, 0, 0, 140, 0, 60, 0, 0, 0, 0, 0, 0, 140, 0, 40, 0, 0, 0, 0, 0, 0, 140.26848, 0, 20, 0, 0, 0, 0, 0, 0, 140, 0, 0, 0, 0, 0, 0, 0, 0, 136.2151, 7.6293945e-06, -18.930761, 0, 0, 0, 0, 0, 0, 124.47424, 7.6293945e-06, -35.771675, 0, 0, 0, 0, 0, 0, 109.556854, 7.6293945e-06, -45.911987, 0, 0, 0, 0, 0, 0, 90, 0, -50, 0, 0, 0, 0, 0, 0, 70, 0, -50, 0, 0, 0, 0, 0, 0, 50, 0, -50, 0, 0, 0, 0, 0, 0, 30.626865, 7.6293945e-06, -46.35706, 0, 0, 0, 0, 0, 0, 14.519331, 7.6293945e-06, -35.468895, 0, 0, 0, 0, 0, 0, 3.5, 0, -17.9), "tilts": PackedFloat32Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) } point_count = 93 @@ -250,6 +250,7 @@ wagon_count = 5 wagon_gap = 0.8 wagon_spacing_scale = 0.9 cameras = NodePath("../cameras") +train_start_mode = 2 sleepers_model = ExtResource("46_lbmv2") fireworks_scene = ExtResource("47_q7p65") From 6da7021a58eb07c3a9bfed71aef1fab9703eefb6 Mon Sep 17 00:00:00 2001 From: Overside srl Date: Tue, 7 Jul 2026 10:03:12 +0200 Subject: [PATCH 2/3] manage stats and achievements --- core/biome_generator/rails.gd | 35 ++++------------ core/game_menu/train_selector.gd | 1 + core/main_scene_ui/main_scene_ui.gd | 7 ++-- core/steam_manager/achievement_manager.gd | 5 +++ core/steam_manager/stats_manager.gd | 51 +++++++++++++++++++++++ tgcc/main_scene.tscn | 1 - 6 files changed, 68 insertions(+), 32 deletions(-) diff --git a/core/biome_generator/rails.gd b/core/biome_generator/rails.gd index 9e63577..b6ee959 100644 --- a/core/biome_generator/rails.gd +++ b/core/biome_generator/rails.gd @@ -1,6 +1,5 @@ extends Path3D -const STEAM_DISTANCE_STAT: String = "stat_distance_km" const DISTANCE_KM_PER_UNIT: float = 0.01 const STATION_STOP_GROUP: StringName = &"railway_station" const TRACK_ZERO_SPACING_FIX_DISTANCE: float = 45.0 @@ -102,7 +101,6 @@ var is_restarting: bool = false var tween_restart: Tween var wagon_instances: Array[Node3D] = [] var wagon_progress_offsets: Array[float] = [] -var _pending_steam_distance_km: float = 0.0 var _initial_is_inmotion: bool = true var _is_photo_mode_active: bool = false @@ -493,28 +491,7 @@ func _track_steam_distance(distance_units: float) -> void: # Always track the global distance in our save file (for UI and persistency) GameState.save_data.total_distance_km += distance_units * DISTANCE_KM_PER_UNIT - - # Steam specific logic - if not SteamManager.is_on_steam: - return - - _pending_steam_distance_km += distance_units * DISTANCE_KM_PER_UNIT - var whole_km: int = int(floor(_pending_steam_distance_km)) - if whole_km <= 0: - return - - _pending_steam_distance_km -= float(whole_km) - var total_distance_km: int = StatsManager.get_int(STEAM_DISTANCE_STAT) + whole_km - StatsManager.set_int(STEAM_DISTANCE_STAT, total_distance_km) - _check_distance_achievements(total_distance_km) - StatsManager.store() - -#Check achievements -func _check_distance_achievements(total_distance_km: int) -> void: - if total_distance_km >= 100: - AchievementManager.unlock("ACH_DISTANCE_100") - if total_distance_km >= 200: - AchievementManager.unlock("ACH_DISTANCE_200") + StatsManager.add_distance_km(distance_units * DISTANCE_KM_PER_UNIT) func _execute_stop(go_forward: bool = true) -> void: if not _find_next_valid_stop(go_forward): @@ -522,12 +499,14 @@ func _execute_stop(go_forward: bool = true) -> void: return stop_ongoing = true - stop_multiply = 0.0 - + stop_multiply = 0.0 + var current_stop_index = next_stop_index - + StatsManager.add_int("stat_stop_number", 1) + StatsManager.store() + _advance_next_stop_index(go_forward) - + if fireworks_scene != null: var marker_position = stops_position[current_stop_index] var firework_number = randi_range(5, 8) diff --git a/core/game_menu/train_selector.gd b/core/game_menu/train_selector.gd index 71dd3b1..0040001 100644 --- a/core/game_menu/train_selector.gd +++ b/core/game_menu/train_selector.gd @@ -40,6 +40,7 @@ func _on_color_selected(selected_color_picker: TrainColorPicker, material_index: for i in range(color_pickers.size()): if color_pickers[i] == selected_color_picker: _set_selected_color(i, material_index) + AchievementManager.unlock("ACH_CHANGE_TRAIN_COLOR") break func _apply_train_color(color: Color, material_index: int, color_index: int = 0) -> void: diff --git a/core/main_scene_ui/main_scene_ui.gd b/core/main_scene_ui/main_scene_ui.gd index 8bbfeb0..94a402c 100644 --- a/core/main_scene_ui/main_scene_ui.gd +++ b/core/main_scene_ui/main_scene_ui.gd @@ -113,6 +113,7 @@ func _on_time_of_day_row_on_option_changed(data: String) -> void: pass func _on_weather_row_on_option_changed(data: String) -> void: + var weather_changed: bool = true match data: "Snow": _snow = !_snow @@ -129,9 +130,10 @@ func _on_weather_row_on_option_changed(data: String) -> void: "Random": pick_random_weather() _: - pass + weather_changed = false - AchievementManager.is_unlocked("ACH_CHANGE_METEO") + if weather_changed: + AchievementManager.unlock("ACH_CHANGE_METEO") func pick_random_weather() -> void: var valid_buttons = [] @@ -214,7 +216,6 @@ func _on_photo_taken_finished() -> void: func _on_choo_choo_button_pressed() -> void: UIEvents.toot_toot.emit(true) - AchievementManager.is_unlocked("ACH_CHANGE_TRAIN_COLOR") func _set_buttons_mouse_filter(ignore: bool) -> void: var filter = Control.MOUSE_FILTER_IGNORE if ignore else Control.MOUSE_FILTER_STOP diff --git a/core/steam_manager/achievement_manager.gd b/core/steam_manager/achievement_manager.gd index abd7392..75785c6 100644 --- a/core/steam_manager/achievement_manager.gd +++ b/core/steam_manager/achievement_manager.gd @@ -6,6 +6,11 @@ extends Node const KNOWN := [ "ACH_CHANGE_METEO", "ACH_CHANGE_TRAIN_COLOR", + "ACH_DISTANCE_100", + "ACH_DISTANCE_200", + "ACH_10_PHOTOS", + "ACH_10_TOOTTOOT", + "ACH_DISTANCE_1000" ] #how it works: diff --git a/core/steam_manager/stats_manager.gd b/core/steam_manager/stats_manager.gd index e9c4916..caa164a 100644 --- a/core/steam_manager/stats_manager.gd +++ b/core/steam_manager/stats_manager.gd @@ -13,6 +13,13 @@ const KNOWN := [ "stat_photo_number", "stat_toottoot_number", ] +const TIME_PLAYED_STAT: String = "stat_time_played" +const DISTANCE_KM_STAT: String = "stat_distance_km" +const STATS_STORE_INTERVAL_SECONDS: int = 60 + +var _pending_time_played_seconds: float = 0.0 +var _pending_distance_km: float = 0.0 +var _stats_store_timer: float = 0.0 #how it works: #func _on_match_finished(score: int) -> void: @@ -25,10 +32,20 @@ const KNOWN := [ func _ready() -> void: if not SteamManager.is_on_steam: + set_process(false) return Steam.user_stats_stored.connect(_on_stats_stored) +func _process(delta: float) -> void: + _pending_time_played_seconds += delta + _stats_store_timer += delta + if _stats_store_timer >= STATS_STORE_INTERVAL_SECONDS: + _flush_periodic_stats(false) + +func _exit_tree() -> void: + _flush_periodic_stats(true) + func get_int(stat: String) -> int: if not SteamManager.is_on_steam: return 0 @@ -62,6 +79,14 @@ func add_int(stat: String, amount: int = 1) -> void: return set_int(stat, get_int(stat) + amount) +func add_distance_km(distance_km: float) -> void: + if not SteamManager.is_on_steam: + return + if distance_km <= 0.0: + return + + _pending_distance_km += distance_km + func update_avg_rate(stat: String, count_this_session: float, session_length: float) -> void: if not SteamManager.is_on_steam: return @@ -74,6 +99,32 @@ func store() -> void: if not Steam.storeStats(): push_error("storeStats failed; data saved locally") +#periodic stats incrementation; store data every 60 secs +func _flush_periodic_stats(force_store: bool) -> void: + if not SteamManager.is_on_steam: + _pending_time_played_seconds = 0.0 + _pending_distance_km = 0.0 + _stats_store_timer = 0.0 + return + + var has_changes: bool = false + + var whole_seconds: int = int(floor(_pending_time_played_seconds)) + if whole_seconds > 0: + _pending_time_played_seconds -= float(whole_seconds) + add_int(TIME_PLAYED_STAT, whole_seconds) + has_changes = true + + var whole_km: int = int(floor(_pending_distance_km)) + if whole_km > 0: + _pending_distance_km -= float(whole_km) + add_int(DISTANCE_KM_STAT, whole_km) + has_changes = true + + _stats_store_timer = 0.0 + if has_changes and (force_store or whole_seconds > 0 or whole_km > 0): + store() + #Development only: reset stats func reset_all(include_achievements: bool = false) -> void: if not SteamManager.is_on_steam: diff --git a/tgcc/main_scene.tscn b/tgcc/main_scene.tscn index 6ff2423..98a35e8 100644 --- a/tgcc/main_scene.tscn +++ b/tgcc/main_scene.tscn @@ -250,7 +250,6 @@ wagon_count = 5 wagon_gap = 0.8 wagon_spacing_scale = 0.9 cameras = NodePath("../cameras") -train_start_mode = 2 sleepers_model = ExtResource("46_lbmv2") fireworks_scene = ExtResource("47_q7p65") From 8ff14f34559dd6e733e551718d64382aa73b285a Mon Sep 17 00:00:00 2001 From: Overside srl Date: Tue, 7 Jul 2026 18:00:12 +0200 Subject: [PATCH 3/3] show snow on grass --- core/biome_generator/rails.gd | 63 +++++++++++++++++++------- core/daynight/grass_leaves.gdshader | 4 +- core/daynight/tree_leaves.gdshader | 4 +- core/daynight/weather_overlay.gdshader | 4 ++ tgcc/main menu/main_menu_test.tscn | 1 + 5 files changed, 58 insertions(+), 18 deletions(-) diff --git a/core/biome_generator/rails.gd b/core/biome_generator/rails.gd index b6ee959..b11c634 100644 --- a/core/biome_generator/rails.gd +++ b/core/biome_generator/rails.gd @@ -10,6 +10,7 @@ const TRACK_ZERO_SPACING_TARGET_RATIO: float = 0.97 const TRACK_ZERO_SPACING_MIN_DISTANCE: float = 1.0 enum TrainStartMode { RANDOM_POSITION, SPECIFIED_STATION, SPECIFIED_POSITION } +enum TrainFacingDirection { KEEP_PATH_DIRECTION, LOCOMOTIVE_LEFT, LOCOMOTIVE_RIGHT } @export_group("Train") ##train speed @@ -46,6 +47,7 @@ enum TrainStartMode { RANDOM_POSITION, SPECIFIED_STATION, SPECIFIED_POSITION } @export var train_start_delay_seconds: float = 0.0 @export var train_start_after_delay: bool = true @export var train_start_acceleration_seconds: float = 4.0 +@export_enum("keep_path_direction", "locomotive_left", "locomotive_right") var train_facing_direction: int = TrainFacingDirection.KEEP_PATH_DIRECTION @export_group("Rails Bulding") ##distance between sleepers of the rails @@ -103,6 +105,7 @@ var wagon_instances: Array[Node3D] = [] var wagon_progress_offsets: Array[float] = [] var _initial_is_inmotion: bool = true var _is_photo_mode_active: bool = false +var _train_direction_sign: float = 1.0 func _ready() -> void: randomize() @@ -144,8 +147,32 @@ func _apply_initial_train_start() -> void: train_progress = wrapf(train_start_position, 0.0, total_length) _update_next_stop_index_from_progress() + _apply_train_facing_direction(total_length) + _update_next_stop_index_from_progress() _snap_train_to_progress() +func _apply_train_facing_direction(total_length: float) -> void: + _train_direction_sign = 1.0 + if train_facing_direction == TrainFacingDirection.KEEP_PATH_DIRECTION or curve == null: + return + if total_length <= 0.0: + return + + var center_progress: float = wrapf(train_progress, 0.0, total_length) + var center_position: Vector3 = to_global(curve.sample_baked(center_progress, true)) + var forward_progress: float = wrapf(center_progress + 2.0, 0.0, total_length) + var forward_position: Vector3 = to_global(curve.sample_baked(forward_progress, true)) + var forward_direction: Vector3 = forward_position - center_position + if forward_direction.length_squared() <= 0.0001 or is_zero_approx(forward_direction.x): + return + + var desired_x_sign: float = -1.0 if train_facing_direction == TrainFacingDirection.LOCOMOTIVE_LEFT else 1.0 + if forward_direction.x * desired_x_sign < 0.0: + _train_direction_sign = -1.0 + + if not is_zero_approx(train_speed): + train_speed = absf(train_speed) * _train_direction_sign + func _apply_train_start_delay() -> void: if not _initial_is_inmotion: return @@ -207,13 +234,9 @@ func _snap_train_to_progress() -> void: train_progress = wrapf(train_progress, 0.0, total_length) var center_position: Vector3 = to_global(curve.sample_baked(train_progress, true)) - var prog_forward: float = wrapf(train_progress + 2.0, 0.0, total_length) - var forward_position: Vector3 = to_global(curve.sample_baked(prog_forward, true)) train_instance.global_position = center_position - if center_position.distance_to(forward_position) > 0.01: - train_instance.look_at(forward_position, Vector3.UP) - train_instance.rotate_y(PI) + _orient_vehicle_to_track(train_instance, train_progress, total_length) if cameras: cameras.global_position = center_position @@ -452,8 +475,7 @@ func train_move(delta: float) -> void: if center_position.distance_to(forward_position) > 0.01: train_instance.global_position = center_position - train_instance.look_at(forward_position, Vector3.UP) - train_instance.rotate_y(PI) + _orient_vehicle_to_track(train_instance, train_progress, total_length) if cameras: cameras.global_position = center_position @@ -658,7 +680,7 @@ func _snap_wagons_to_progress(total_length: float = 0.0) -> void: if i < wagon_progress_offsets.size(): wagon_offset = wagon_progress_offsets[i] - var wagon_progress: float = train_progress - wagon_offset + var wagon_progress: float = train_progress - wagon_offset * _train_direction_sign var segment_spacing: float = maxf(wagon_offset - previous_wagon_offset, 0.1) var vehicle_progress: float = wrapf(wagon_progress, 0.0, total_length) @@ -670,18 +692,27 @@ func _snap_wagons_to_progress(total_length: float = 0.0) -> void: vehicle_progress = _lerp_progress_on_track(vehicle_progress, corrected_progress, zero_spacing_fix_weight, total_length) center_position = to_global(curve.sample_baked(vehicle_progress, true)) - var prog_forward: float = wrapf(vehicle_progress + 2.0, 0.0, total_length) - var forward_position: Vector3 = to_global(curve.sample_baked(prog_forward, true)) - wagon.global_position = center_position - if center_position.distance_to(forward_position) > 0.01: - wagon.look_at(forward_position, Vector3.UP) - wagon.rotate_y(PI) + _orient_vehicle_to_track(wagon, vehicle_progress, total_length) previous_progress = vehicle_progress previous_position = center_position previous_wagon_offset = wagon_offset +func _orient_vehicle_to_track(vehicle: Node3D, progress: float, total_length: float) -> void: + if vehicle == null or curve == null or total_length <= 0.0: + return + + var center_progress: float = wrapf(progress, 0.0, total_length) + var center_position: Vector3 = to_global(curve.sample_baked(center_progress, true)) + var forward_progress: float = wrapf(center_progress + 2.0 * _train_direction_sign, 0.0, total_length) + var forward_position: Vector3 = to_global(curve.sample_baked(forward_progress, true)) + if center_position.distance_to(forward_position) <= 0.01: + return + + vehicle.look_at(forward_position, Vector3.UP) + vehicle.rotate_y(PI) + func _get_zero_spacing_fix_weight(previous_progress: float, vehicle_progress: float, total_length: float) -> float: var distance_to_zero: float = minf( _get_progress_distance_to_zero(previous_progress, total_length), @@ -699,13 +730,13 @@ func _get_zero_spacing_target_distance(segment_spacing: float) -> float: func _find_zero_spacing_progress(previous_progress: float, previous_position: Vector3, segment_spacing: float, minimum_distance: float, total_length: float) -> float: var start_distance: float = minf(minimum_distance, segment_spacing) - var best_progress: float = wrapf(previous_progress - start_distance, 0.0, total_length) + var best_progress: float = wrapf(previous_progress - start_distance * _train_direction_sign, 0.0, total_length) var search_limit: float = minf(maxf(segment_spacing, minimum_distance) + TRACK_ZERO_SPACING_SEARCH_EXTRA, total_length) var search_distance: float = start_distance var previous_search_distance: float = start_distance while search_distance <= search_limit: - var candidate_progress: float = wrapf(previous_progress - search_distance, 0.0, total_length) + var candidate_progress: float = wrapf(previous_progress - search_distance * _train_direction_sign, 0.0, total_length) var candidate_position: Vector3 = to_global(curve.sample_baked(candidate_progress, true)) best_progress = candidate_progress if previous_position.distance_to(candidate_position) >= minimum_distance: diff --git a/core/daynight/grass_leaves.gdshader b/core/daynight/grass_leaves.gdshader index 4733d32..7d933b2 100644 --- a/core/daynight/grass_leaves.gdshader +++ b/core/daynight/grass_leaves.gdshader @@ -15,6 +15,8 @@ global uniform float global_snow_melt_speed = 0.1; global uniform float global_snow_amount = 0.0; global uniform vec4 global_snow_color = vec4(0.92, 0.96, 1.0, 1.0); global uniform float global_rain_intensity; +const float GRASS_FULL_SNOW_START = 0.58; +const float GRASS_FULL_SNOW_END = 0.86; // --- PARAMETRI ESTETICI --- uniform bool billboard_enabled = true; @@ -141,7 +143,7 @@ void fragment() { float snow_mask = smoothstep(snow_start, 1.0, top_mask) * snow_amount * snow_visibility; snow_mask *= step(0.01, snow_amount); snow_mask = clamp(snow_mask, 0.0, 1.0); - float full_snow_mask = smoothstep(0.72, 1.0, snow_amount) * step(0.01, snow_visibility); + float full_snow_mask = smoothstep(GRASS_FULL_SNOW_START, GRASS_FULL_SNOW_END, snow_amount) * step(0.01, snow_visibility); float final_snow_mask = max(snow_mask, full_snow_mask); vec3 snow_base_color = max(snow_color.rgb, global_snow_color.rgb); diff --git a/core/daynight/tree_leaves.gdshader b/core/daynight/tree_leaves.gdshader index 6fc87b0..74a1078 100644 --- a/core/daynight/tree_leaves.gdshader +++ b/core/daynight/tree_leaves.gdshader @@ -13,6 +13,8 @@ global uniform float global_snow_melt_speed = 0.1; global uniform float global_snow_amount = 0.0; global uniform vec4 global_snow_color; global uniform float global_rain_intensity; +const float LEAVES_FULL_SNOW_START = 0.58; +const float LEAVES_FULL_SNOW_END = 0.86; uniform sampler2D wind_noise : filter_linear_mipmap; uniform bool billboard_enabled = true; @@ -110,7 +112,7 @@ void fragment() { float top_mask = 1.0 - shifted_uv.y; float snow_mask = smoothstep(1.0 - snow_amount * 1.35, 1.08 - snow_amount * 1.35, top_mask) * snow_visibility; snow_mask *= step(0.01, snow_amount); - float full_snow_mask = smoothstep(0.78, 1.0, snow_amount) * clamp(snow_visibility, 0.0, 1.0); + float full_snow_mask = smoothstep(LEAVES_FULL_SNOW_START, LEAVES_FULL_SNOW_END, snow_amount) * clamp(snow_visibility, 0.0, 1.0); float final_snow_mask = max(snow_mask, full_snow_mask); float snow_light = mix(0.82, 1.0, max(v_shade_factor, 0.35)); diff --git a/core/daynight/weather_overlay.gdshader b/core/daynight/weather_overlay.gdshader index 2a47e23..62444cc 100644 --- a/core/daynight/weather_overlay.gdshader +++ b/core/daynight/weather_overlay.gdshader @@ -17,6 +17,8 @@ global uniform float global_snow_cap_flatness_end = 0.96; global uniform float global_snow_cap_noise_scale = 0.22; global uniform float global_snow_cap_noise_strength = 0.18; const float SNOW_VISUAL_RESPONSE = 0.55; +const float FULL_SNOW_VISUAL_START = 0.82; +const float FULL_SNOW_VISUAL_END = 0.98; uniform bool snow_noise_enabled = true; uniform float snow_noise_scale : hint_range(0.01, 1.0) = 0.15; uniform float snow_edge_softness : hint_range(0.01, 0.5) = 0.2; @@ -156,6 +158,8 @@ void fragment() { float snow_factor = max(snow_edge * snow_coverage, flat_accumulation); float snow_opacity = clamp(snow_factor, 0.0, 1.0); snow_opacity = max(snow_opacity, flat_accumulation * mix(0.45, 0.9, snow_noise_mask)); + float full_snow_opacity = snow_edge * smoothstep(FULL_SNOW_VISUAL_START, FULL_SNOW_VISUAL_END, snow_progress); + snow_opacity = max(snow_opacity, full_snow_opacity); float shade = mix(-snow_color_variation, snow_color_variation, noise_val); vec3 snow_albedo = clamp(global_snow_color.rgb + vec3(shade), 0.0, 1.0); diff --git a/tgcc/main menu/main_menu_test.tscn b/tgcc/main menu/main_menu_test.tscn index da80c6d..67ba1aa 100644 --- a/tgcc/main menu/main_menu_test.tscn +++ b/tgcc/main menu/main_menu_test.tscn @@ -324,6 +324,7 @@ train_start_mode = 2 train_start_stop_index = 0 train_start_position = 540.0 train_start_after_delay = false +train_facing_direction = 2 sleepers_model = ExtResource("10_00nq7") fireworks_scene = ExtResource("11_aylkj")