diff --git a/.github/workflows/release-packaging.yml b/.github/workflows/release-packaging.yml index 40a4d60..8a02911 100644 --- a/.github/workflows/release-packaging.yml +++ b/.github/workflows/release-packaging.yml @@ -1,8 +1,13 @@ name: Release Packaging on: - push: workflow_dispatch: + # Ensure the build works on main + push: + branches: [main] + tags: + # Ensure the build works on each pull request + pull_request: env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} diff --git a/CHANGELOG.md b/CHANGELOG.md index fbba5a0..82e57a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Removed ### Fixed - Use a custom `EditorExportPlugin` to set build info ([#41](https://github.com/MechanicalFlower/Marble/pull/41)) +- Generate a new chunk at each lap ([#42](https://github.com/MechanicalFlower/Marble/pull/42)) ### Security ### Dependencies - Bump `actions/cache` from 3 to 4 ([#40](https://github.com/MechanicalFlower/Marble/pull/40)) diff --git a/scripts/main.gd b/scripts/main.gd index e3cde86..8b38dd4 100644 --- a/scripts/main.gd +++ b/scripts/main.gd @@ -18,9 +18,13 @@ var _mode: int = State.MODE_START var _current_marble_index := 0 var _time := 0.0 var _explosion_enabled := false -var _need_new_chunk := false var _race_has_started := false +# Variables used in explosion mode to check +# if we need to generate another chunk of the race +var _max_checkpoint_count := -1 +var _old_lap_count := 0 + # There are limited places to ensure equality among the marbles. # TODO : remove this limit var _positions := [] @@ -101,7 +105,7 @@ func _unhandled_input(event): # Debug command to generate a new race KEY_R: if _mode == State.MODE_MARBLE: - _race.call_deferred(&"generate_race", !_explosion_enabled) + _race.generate_race(!_explosion_enabled) KEY_SPACE: for marble in _marbles: @@ -211,7 +215,7 @@ func set_mode(mode): await Fade.fade_out(1, Color.BLACK, "Diamond", false, false).finished _explosion_enabled = SettingsManager.get_value(&"marbles", &"explosion_enabled") as bool - _race.call_deferred(&"generate_race", !_explosion_enabled) + _race.generate_race(!_explosion_enabled) _overlay.reset() reset_position() @@ -276,7 +280,7 @@ func _process(delta): if _time > TIME_PERIOD: if _mode == State.MODE_START: # Regenerate race - _race.call_deferred(&"generate_race", true) + _race.generate_race(true) # Reset timer _time = 0 @@ -300,14 +304,18 @@ func _process(delta): _explosion.set_emitting(true) if _ranking._first_marble: - if ( - _need_new_chunk - and (_ranking._first_marble._checkpoint_count + 3) % _race._step_count != 0 - ): - _race.generate_chunk() - _need_new_chunk = false - elif (_ranking._first_marble._checkpoint_count + 3) % _race._step_count == 0: - _need_new_chunk = true + # If a new checkpoint is crossed by the first marble + if _ranking._first_marble._checkpoint_count > _max_checkpoint_count: + # Store the max number of checkpoints crossed + _max_checkpoint_count = _ranking._first_marble._checkpoint_count + + # Compute the lap (1 lap equals to one chunk) + var lap_count: int = ceil((_max_checkpoint_count + 3) / _race._step_count) + # If one more lap was done + if lap_count > _old_lap_count: + # Generate a chunk + _race.generate_chunk() + _old_lap_count = lap_count else: _panel_timer.hide() diff --git a/scripts/race.gd b/scripts/race.gd index a3cb2f5..0239bd9 100644 --- a/scripts/race.gd +++ b/scripts/race.gd @@ -1,17 +1,25 @@ @tool - class_name Race extends Node -var PieceList = load("res://scripts/constants/piece_list.gd") -var Group = load("res://scripts/constants/groups.gd") +# Constants and resource paths +const PieceList = preload("res://scripts/constants/piece_list.gd") +const Group = preload("res://scripts/constants/groups.gd") + +@export var regenerate_race: bool: + set = generate_race +# Number of steps in the race generation var _step_count := 10 -var _previous_piece: Piece = null -var _piece_orientation = null + +var _previous_piece = null +var _previous_piece_orientation = null var _previous_rotation_index := 0 -var _curve: Curve3D +# Curve to store the race path +@onready var curve: Curve3D = Curve3D.new() + +# Reference to the path node @onready var path := get_node(^"Path") as Path3D @@ -22,79 +30,138 @@ static func umod(x: int, d: int) -> int: func _ready() -> void: - _curve = Curve3D.new() - path.set_curve(_curve) + # Initialize the curve and set it to the path + path.set_curve(curve) + # Check if explosion is enabled from settings var explosion_enabled = SettingsManager.get_value(&"marbles", &"explosion_enabled") as bool generate_race(!explosion_enabled) +# Main function to generate the race func generate_race(with_end: bool = true) -> void: - _curve.clear_points() + # Clear previous race path + curve.clear_points() + # Clear previous pieces from the scene + clear_previous_pieces() + # Place the start line + place_start_line() - for piece in get_children(): - if piece.is_in_group(Group.PIECES): - piece.queue_free() + # Generate a race chunk + generate_chunk() + + # Place the finish line if required + if with_end: + place_finish_line() + +# Function to generate a race chunk +func generate_chunk() -> void: randomize() - # Reset values + for _i in range(_step_count): + # Select a random piece and place it + var piece_index = randomize_piece_index() + place_piece(piece_index) + + +# Function to clear previous pieces from the scene +func clear_previous_pieces() -> void: _previous_piece = null - _piece_orientation = null + _previous_piece_orientation = null _previous_rotation_index = 0 - # Place the start line - place_piece(-2) + for piece in get_children(): + if piece.is_in_group(Group.PIECES): + piece.queue_free() - generate_chunk() - if with_end: - # Place the finish line - place_piece(-1) +# Function to select a random piece index +func randomize_piece_index() -> int: + return umod(randi(), len(PieceList.PIECES) - 2) -func generate_chunk(): - for step in _step_count: - # Select a random piece - var piece_index = umod(randi(), len(PieceList.PIECES) - 2) - place_piece(piece_index) +# Function to place the start line +func place_start_line() -> void: + place_piece(-2) + +# Function to place the finish line +func place_finish_line() -> void: + place_piece(-1) + +# Function to place a piece on the race path func place_piece(piece_index: int) -> void: + # Instantiate the piece var piece_data = PieceList.PIECES[piece_index] - var piece: Node = piece_data[&"resource"].instantiate() + var piece: Piece = piece_data[&"resource"].instantiate() + + # Optimize piece visibility + optimize_piece_visibility(piece) - # naive hlod + # Add the piece to the scene + add_child(piece) + + # Rotate and translate the piece + rotate_piece(piece) + translate_piece(piece) + + # Store data for next piece and positions for the race path + store_piece_data(piece, piece_data) + store_piece_positions(piece) + + +# Function to optimize piece visibility +func optimize_piece_visibility(piece: Piece) -> void: for child in piece.get_children(): if child is MeshInstance3D: child.visibility_range_end = 150 - # Add the piece to the main Node - add_child(piece) - # Rotate the piece +# Function to rotate a piece based on previous orientation +func rotate_piece(piece: Piece) -> void: + var rotation_index = calculate_rotation_index(piece) + piece.rotate_y(float(rotation_index) * PI / 2.0) + + +# Function to calculate the rotation index for a piece +func calculate_rotation_index(_piece: Piece) -> int: var rotation_index = _previous_rotation_index - if _piece_orientation == PieceList.TURN_LEFT: + if _previous_piece_orientation == PieceList.TURN_LEFT: rotation_index = (rotation_index + 1) % 4 - elif _piece_orientation == PieceList.TURN_RIGHT: + elif _previous_piece_orientation == PieceList.TURN_RIGHT: rotation_index = (rotation_index - 1) % 4 - piece.rotate_y(float(rotation_index) * PI / 2.0) + return rotation_index + - # Translate the piece +# Function to translate a piece based on previous piece position +func translate_piece(piece: Piece) -> void: + var offset = calculate_translation_offset(piece) + piece.global_translate(offset) + + +# Function to calculate the translation offset for a piece +func calculate_translation_offset(piece: Piece) -> Vector3: var offset = Vector3(-10, 25, 0) if _previous_piece != null: offset = ( _previous_piece.get_end().global_transform.origin - piece.get_begin().global_transform.origin ) - piece.global_translate(offset) + return offset - # Store data for next piece + +# Function to store data for the next piece +func store_piece_data(piece: Piece, piece_data: Dictionary) -> void: _previous_piece = piece - _piece_orientation = piece_data[&"next_piece_orientation"] - _previous_rotation_index = rotation_index + _previous_rotation_index = calculate_rotation_index(piece) + _previous_piece_orientation = piece_data[&"next_piece_orientation"] + +# Function to store positions for the race path +func store_piece_positions(piece: Piece) -> void: var positions := piece.get_node(^"Positions") as Marker3D if positions: for pos in positions.get_children(): - _curve.add_point(pos.global_position) + curve.add_point(pos.global_position)