format
Some checks failed
Some checks failed
This commit is contained in:
@@ -112,31 +112,31 @@ jobs:
|
|||||||
git diff --stat
|
git diff --stat
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Commit and push formatting changes
|
# - name: Commit and push formatting changes
|
||||||
if: steps.check-changes.outputs.has_changes == 'true'
|
# if: steps.check-changes.outputs.has_changes == 'true'
|
||||||
run: |
|
# run: |
|
||||||
echo "💾 Committing formatting changes..."
|
# echo "💾 Committing formatting changes..."
|
||||||
|
|
||||||
git config user.name "Gitea Actions"
|
# git config user.name "Gitea Actions"
|
||||||
git config user.email "actions@gitea.local"
|
# git config user.email "actions@gitea.local"
|
||||||
|
|
||||||
git add -A
|
# git add -A
|
||||||
|
|
||||||
commit_message="🎨 Auto-format GDScript code
|
# commit_message="🎨 Auto-format GDScript code
|
||||||
|
|
||||||
Automated formatting applied by tools/run_development.py
|
# Automated formatting applied by tools/run_development.py
|
||||||
|
|
||||||
🤖 Generated by Gitea Actions
|
# 🤖 Generated by Gitea Actions
|
||||||
Workflow: ${{ github.workflow }}
|
# Workflow: ${{ github.workflow }}
|
||||||
Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
# Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
|
||||||
git commit -m "$commit_message"
|
# git commit -m "$commit_message"
|
||||||
|
|
||||||
target_branch="${{ github.event.pull_request.head.ref || github.ref_name }}"
|
# target_branch="${{ github.event.pull_request.head.ref || github.ref_name }}"
|
||||||
echo "📤 Pushing changes to branch: $target_branch"
|
# echo "📤 Pushing changes to branch: $target_branch"
|
||||||
git push origin HEAD:"$target_branch"
|
# git push origin HEAD:"$target_branch"
|
||||||
|
|
||||||
echo "✅ Formatting changes pushed successfully!"
|
# echo "✅ Formatting changes pushed successfully!"
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Code Quality Check
|
name: Code Quality Check
|
||||||
|
|||||||
@@ -11,14 +11,17 @@ var language_stepper: ValueStepper = $VBoxContainer/Examples/LanguageContainer/L
|
|||||||
var difficulty_stepper: ValueStepper = $VBoxContainer/Examples/DifficultyContainer/DifficultyStepper
|
var difficulty_stepper: ValueStepper = $VBoxContainer/Examples/DifficultyContainer/DifficultyStepper
|
||||||
@onready
|
@onready
|
||||||
var resolution_stepper: ValueStepper = $VBoxContainer/Examples/ResolutionContainer/ResolutionStepper
|
var resolution_stepper: ValueStepper = $VBoxContainer/Examples/ResolutionContainer/ResolutionStepper
|
||||||
@onready var custom_stepper: ValueStepper = $VBoxContainer/Examples/CustomContainer/CustomStepper
|
@onready
|
||||||
|
var custom_stepper: ValueStepper = $VBoxContainer/Examples/CustomContainer/CustomStepper
|
||||||
|
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
DebugManager.log_info("ValueStepper example ready", "Example")
|
DebugManager.log_info("ValueStepper example ready", "Example")
|
||||||
|
|
||||||
# Setup navigation array
|
# Setup navigation array
|
||||||
navigable_steppers = [language_stepper, difficulty_stepper, resolution_stepper, custom_stepper]
|
navigable_steppers = [
|
||||||
|
language_stepper, difficulty_stepper, resolution_stepper, custom_stepper
|
||||||
|
]
|
||||||
|
|
||||||
# Connect to value change events
|
# Connect to value change events
|
||||||
for stepper in navigable_steppers:
|
for stepper in navigable_steppers:
|
||||||
@@ -52,15 +55,22 @@ func _input(event: InputEvent):
|
|||||||
|
|
||||||
|
|
||||||
func _navigate_steppers(direction: int):
|
func _navigate_steppers(direction: int):
|
||||||
current_stepper_index = ((current_stepper_index + direction) % navigable_steppers.size())
|
current_stepper_index = (
|
||||||
|
(current_stepper_index + direction) % navigable_steppers.size()
|
||||||
|
)
|
||||||
if current_stepper_index < 0:
|
if current_stepper_index < 0:
|
||||||
current_stepper_index = navigable_steppers.size() - 1
|
current_stepper_index = navigable_steppers.size() - 1
|
||||||
_update_stepper_highlighting()
|
_update_stepper_highlighting()
|
||||||
DebugManager.log_info("Stepper navigation: index " + str(current_stepper_index), "Example")
|
DebugManager.log_info(
|
||||||
|
"Stepper navigation: index " + str(current_stepper_index), "Example"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _handle_stepper_input(action: String):
|
func _handle_stepper_input(action: String):
|
||||||
if current_stepper_index >= 0 and current_stepper_index < navigable_steppers.size():
|
if (
|
||||||
|
current_stepper_index >= 0
|
||||||
|
and current_stepper_index < navigable_steppers.size()
|
||||||
|
):
|
||||||
var stepper = navigable_steppers[current_stepper_index]
|
var stepper = navigable_steppers[current_stepper_index]
|
||||||
if stepper.handle_input_action(action):
|
if stepper.handle_input_action(action):
|
||||||
AudioManager.play_ui_click()
|
AudioManager.play_ui_click()
|
||||||
@@ -73,7 +83,14 @@ func _update_stepper_highlighting():
|
|||||||
|
|
||||||
func _on_stepper_value_changed(new_value: String, new_index: int):
|
func _on_stepper_value_changed(new_value: String, new_index: int):
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Stepper value changed to: " + new_value + " (index: " + str(new_index) + ")", "Example"
|
(
|
||||||
|
"Stepper value changed to: "
|
||||||
|
+ new_value
|
||||||
|
+ " (index: "
|
||||||
|
+ str(new_index)
|
||||||
|
+ ")"
|
||||||
|
),
|
||||||
|
"Example"
|
||||||
)
|
)
|
||||||
# Handle value change in your scene
|
# Handle value change in your scene
|
||||||
# For example: apply settings, save preferences, update UI, etc.
|
# For example: apply settings, save preferences, update UI, etc.
|
||||||
|
|||||||
@@ -20,15 +20,20 @@ func _ready() -> void:
|
|||||||
|
|
||||||
# GameManager will set the gameplay mode, don't set default here
|
# GameManager will set the gameplay mode, don't set default here
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Game _ready() completed, waiting for GameManager to set gameplay mode", "Game"
|
"Game _ready() completed, waiting for GameManager to set gameplay mode",
|
||||||
|
"Game"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func set_gameplay_mode(mode: String) -> void:
|
func set_gameplay_mode(mode: String) -> void:
|
||||||
DebugManager.log_info("set_gameplay_mode called with mode: %s" % mode, "Game")
|
DebugManager.log_info(
|
||||||
|
"set_gameplay_mode called with mode: %s" % mode, "Game"
|
||||||
|
)
|
||||||
current_gameplay_mode = mode
|
current_gameplay_mode = mode
|
||||||
await load_gameplay(mode)
|
await load_gameplay(mode)
|
||||||
DebugManager.log_info("set_gameplay_mode completed for mode: %s" % mode, "Game")
|
DebugManager.log_info(
|
||||||
|
"set_gameplay_mode completed for mode: %s" % mode, "Game"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func load_gameplay(mode: String) -> void:
|
func load_gameplay(mode: String) -> void:
|
||||||
@@ -37,24 +42,35 @@ func load_gameplay(mode: String) -> void:
|
|||||||
# Clear existing gameplay and wait for removal
|
# Clear existing gameplay and wait for removal
|
||||||
var existing_children = gameplay_container.get_children()
|
var existing_children = gameplay_container.get_children()
|
||||||
if existing_children.size() > 0:
|
if existing_children.size() > 0:
|
||||||
DebugManager.log_debug("Removing %d existing children" % existing_children.size(), "Game")
|
DebugManager.log_debug(
|
||||||
|
"Removing %d existing children" % existing_children.size(), "Game"
|
||||||
|
)
|
||||||
for child in existing_children:
|
for child in existing_children:
|
||||||
DebugManager.log_debug("Removing existing child: %s" % child.name, "Game")
|
DebugManager.log_debug(
|
||||||
|
"Removing existing child: %s" % child.name, "Game"
|
||||||
|
)
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
|
|
||||||
# Wait for children to be properly removed from scene tree
|
# Wait for children to be properly removed from scene tree
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Children removal complete, container count: %d" % gameplay_container.get_child_count(),
|
(
|
||||||
|
"Children removal complete, container count: %d"
|
||||||
|
% gameplay_container.get_child_count()
|
||||||
|
),
|
||||||
"Game"
|
"Game"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Load new gameplay
|
# Load new gameplay
|
||||||
if GAMEPLAY_SCENES.has(mode):
|
if GAMEPLAY_SCENES.has(mode):
|
||||||
DebugManager.log_debug("Found scene path: %s" % GAMEPLAY_SCENES[mode], "Game")
|
DebugManager.log_debug(
|
||||||
|
"Found scene path: %s" % GAMEPLAY_SCENES[mode], "Game"
|
||||||
|
)
|
||||||
var gameplay_scene = load(GAMEPLAY_SCENES[mode])
|
var gameplay_scene = load(GAMEPLAY_SCENES[mode])
|
||||||
var gameplay_instance = gameplay_scene.instantiate()
|
var gameplay_instance = gameplay_scene.instantiate()
|
||||||
DebugManager.log_debug("Instantiated gameplay: %s" % gameplay_instance.name, "Game")
|
DebugManager.log_debug(
|
||||||
|
"Instantiated gameplay: %s" % gameplay_instance.name, "Game"
|
||||||
|
)
|
||||||
gameplay_container.add_child(gameplay_instance)
|
gameplay_container.add_child(gameplay_instance)
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
(
|
(
|
||||||
@@ -69,7 +85,9 @@ func load_gameplay(mode: String) -> void:
|
|||||||
gameplay_instance.score_changed.connect(_on_score_changed)
|
gameplay_instance.score_changed.connect(_on_score_changed)
|
||||||
DebugManager.log_debug("Connected score_changed signal", "Game")
|
DebugManager.log_debug("Connected score_changed signal", "Game")
|
||||||
else:
|
else:
|
||||||
DebugManager.log_error("Gameplay mode '%s' not found in GAMEPLAY_SCENES" % mode, "Game")
|
DebugManager.log_error(
|
||||||
|
"Gameplay mode '%s' not found in GAMEPLAY_SCENES" % mode, "Game"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func set_global_score(value: int) -> void:
|
func set_global_score(value: int) -> void:
|
||||||
@@ -102,10 +120,15 @@ func _on_back_button_pressed() -> void:
|
|||||||
if gameplay_instance and gameplay_instance.has_method("save_current_state"):
|
if gameplay_instance and gameplay_instance.has_method("save_current_state"):
|
||||||
DebugManager.log_info("Saving grid state before exit", "Game")
|
DebugManager.log_info("Saving grid state before exit", "Game")
|
||||||
# Make sure the gameplay instance is still valid and not being destroyed
|
# Make sure the gameplay instance is still valid and not being destroyed
|
||||||
if is_instance_valid(gameplay_instance) and gameplay_instance.is_inside_tree():
|
if (
|
||||||
|
is_instance_valid(gameplay_instance)
|
||||||
|
and gameplay_instance.is_inside_tree()
|
||||||
|
):
|
||||||
gameplay_instance.save_current_state()
|
gameplay_instance.save_current_state()
|
||||||
else:
|
else:
|
||||||
DebugManager.log_warn("Gameplay instance invalid, skipping grid save on exit", "Game")
|
DebugManager.log_warn(
|
||||||
|
"Gameplay instance invalid, skipping grid save on exit", "Game"
|
||||||
|
)
|
||||||
|
|
||||||
# Save the current score immediately before exiting
|
# Save the current score immediately before exiting
|
||||||
SaveManager.finish_game(global_score)
|
SaveManager.finish_game(global_score)
|
||||||
@@ -116,7 +139,10 @@ func _input(event: InputEvent) -> void:
|
|||||||
if event.is_action_pressed("ui_back"):
|
if event.is_action_pressed("ui_back"):
|
||||||
# Handle gamepad/keyboard back action - same as back button
|
# Handle gamepad/keyboard back action - same as back button
|
||||||
_on_back_button_pressed()
|
_on_back_button_pressed()
|
||||||
elif event.is_action_pressed("action_south") and Input.is_action_pressed("action_north"):
|
elif (
|
||||||
|
event.is_action_pressed("action_south")
|
||||||
|
and Input.is_action_pressed("action_north")
|
||||||
|
):
|
||||||
# Debug: Switch to clickomania when primary+secondary actions pressed together
|
# Debug: Switch to clickomania when primary+secondary actions pressed together
|
||||||
if current_gameplay_mode == "match3":
|
if current_gameplay_mode == "match3":
|
||||||
set_gameplay_mode("clickomania")
|
set_gameplay_mode("clickomania")
|
||||||
|
|||||||
@@ -49,13 +49,21 @@ func _ready() -> void:
|
|||||||
instance_id = "Match3_%d" % get_instance_id()
|
instance_id = "Match3_%d" % get_instance_id()
|
||||||
|
|
||||||
if grid_initialized:
|
if grid_initialized:
|
||||||
DebugManager.log_warn(
|
(
|
||||||
"[%s] Match3 _ready() called multiple times, skipping initialization" % instance_id,
|
DebugManager
|
||||||
"Match3"
|
. log_warn(
|
||||||
|
(
|
||||||
|
"[%s] Match3 _ready() called multiple times, skipping initialization"
|
||||||
|
% instance_id
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
DebugManager.log_debug("[%s] Match3 _ready() started" % instance_id, "Match3")
|
DebugManager.log_debug(
|
||||||
|
"[%s] Match3 _ready() started" % instance_id, "Match3"
|
||||||
|
)
|
||||||
grid_initialized = true
|
grid_initialized = true
|
||||||
|
|
||||||
# Calculate grid layout
|
# Calculate grid layout
|
||||||
@@ -64,12 +72,16 @@ func _ready() -> void:
|
|||||||
# Try to load saved state, otherwise use default
|
# Try to load saved state, otherwise use default
|
||||||
var loaded_saved_state = await load_saved_state()
|
var loaded_saved_state = await load_saved_state()
|
||||||
if not loaded_saved_state:
|
if not loaded_saved_state:
|
||||||
DebugManager.log_info("No saved state found, using default grid initialization", "Match3")
|
DebugManager.log_info(
|
||||||
|
"No saved state found, using default grid initialization", "Match3"
|
||||||
|
)
|
||||||
_initialize_grid()
|
_initialize_grid()
|
||||||
else:
|
else:
|
||||||
DebugManager.log_info("Successfully loaded saved grid state", "Match3")
|
DebugManager.log_info("Successfully loaded saved grid state", "Match3")
|
||||||
|
|
||||||
DebugManager.log_debug("Match3 _ready() completed, calling debug structure check", "Match3")
|
DebugManager.log_debug(
|
||||||
|
"Match3 _ready() completed, calling debug structure check", "Match3"
|
||||||
|
)
|
||||||
|
|
||||||
# Notify UI that grid state is loaded
|
# Notify UI that grid state is loaded
|
||||||
grid_state_loaded.emit(grid_size, tile_types)
|
grid_state_loaded.emit(grid_size, tile_types)
|
||||||
@@ -91,7 +103,8 @@ func _calculate_grid_layout():
|
|||||||
# Align grid to left side with margins
|
# Align grid to left side with margins
|
||||||
var total_grid_height = tile_size * grid_size.y
|
var total_grid_height = tile_size * grid_size.y
|
||||||
grid_offset = Vector2(
|
grid_offset = Vector2(
|
||||||
GRID_LEFT_MARGIN, (viewport_size.y - total_grid_height) / 2 + GRID_TOP_MARGIN
|
GRID_LEFT_MARGIN,
|
||||||
|
(viewport_size.y - total_grid_height) / 2 + GRID_TOP_MARGIN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -136,7 +149,9 @@ func _has_match_at(pos: Vector2i) -> bool:
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
if pos.y >= grid.size() or pos.x >= grid[pos.y].size():
|
if pos.y >= grid.size() or pos.x >= grid[pos.y].size():
|
||||||
DebugManager.log_error("Grid array bounds exceeded at (%d,%d)" % [pos.x, pos.y], "Match3")
|
DebugManager.log_error(
|
||||||
|
"Grid array bounds exceeded at (%d,%d)" % [pos.x, pos.y], "Match3"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
var tile = grid[pos.y][pos.x]
|
var tile = grid[pos.y][pos.x]
|
||||||
@@ -146,7 +161,8 @@ func _has_match_at(pos: Vector2i) -> bool:
|
|||||||
# Check if tile has required properties
|
# Check if tile has required properties
|
||||||
if not "tile_type" in tile:
|
if not "tile_type" in tile:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Tile at (%d,%d) missing tile_type property" % [pos.x, pos.y], "Match3"
|
"Tile at (%d,%d) missing tile_type property" % [pos.x, pos.y],
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -177,13 +193,18 @@ func _get_match_line(start: Vector2i, dir: Vector2i) -> Array:
|
|||||||
# Validate input parameters
|
# Validate input parameters
|
||||||
if not _is_valid_grid_position(start):
|
if not _is_valid_grid_position(start):
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid start position for match line: (%d,%d)" % [start.x, start.y], "Match3"
|
(
|
||||||
|
"Invalid start position for match line: (%d,%d)"
|
||||||
|
% [start.x, start.y]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if abs(dir.x) + abs(dir.y) != 1 or (dir.x != 0 and dir.y != 0):
|
if abs(dir.x) + abs(dir.y) != 1 or (dir.x != 0 and dir.y != 0):
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid direction vector for match line: (%d,%d)" % [dir.x, dir.y], "Match3"
|
"Invalid direction vector for match line: (%d,%d)" % [dir.x, dir.y],
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -206,7 +227,10 @@ func _get_match_line(start: Vector2i, dir: Vector2i) -> Array:
|
|||||||
var current = start + dir * offset
|
var current = start + dir * offset
|
||||||
var steps = 0
|
var steps = 0
|
||||||
# Safety limit prevents infinite loops in case of logic errors
|
# Safety limit prevents infinite loops in case of logic errors
|
||||||
while steps < grid_size.x + grid_size.y and _is_valid_grid_position(current):
|
while (
|
||||||
|
steps < grid_size.x + grid_size.y
|
||||||
|
and _is_valid_grid_position(current)
|
||||||
|
):
|
||||||
if current.y >= grid.size() or current.x >= grid[current.y].size():
|
if current.y >= grid.size() or current.x >= grid[current.y].size():
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -233,7 +257,9 @@ func _clear_matches() -> void:
|
|||||||
"""
|
"""
|
||||||
# Check grid integrity
|
# Check grid integrity
|
||||||
if not _validate_grid_integrity():
|
if not _validate_grid_integrity():
|
||||||
DebugManager.log_error("Grid integrity check failed in _clear_matches", "Match3")
|
DebugManager.log_error(
|
||||||
|
"Grid integrity check failed in _clear_matches", "Match3"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
var match_groups := []
|
var match_groups := []
|
||||||
@@ -282,7 +308,9 @@ func _clear_matches() -> void:
|
|||||||
else:
|
else:
|
||||||
match_score = match_size + max(0, match_size - 2) # n + (n-2) for n >= 4
|
match_score = match_size + max(0, match_size - 2) # n + (n-2) for n >= 4
|
||||||
total_score += match_score
|
total_score += match_score
|
||||||
print("Debug: Match of ", match_size, " gems = ", match_score, " points")
|
print(
|
||||||
|
"Debug: Match of ", match_size, " gems = ", match_score, " points"
|
||||||
|
)
|
||||||
|
|
||||||
# Remove duplicates from all matches combined
|
# Remove duplicates from all matches combined
|
||||||
var to_clear := []
|
var to_clear := []
|
||||||
@@ -308,7 +336,9 @@ func _clear_matches() -> void:
|
|||||||
|
|
||||||
# Validate tile has grid_position property
|
# Validate tile has grid_position property
|
||||||
if not "grid_position" in tile:
|
if not "grid_position" in tile:
|
||||||
DebugManager.log_warn("Tile missing grid_position during removal", "Match3")
|
DebugManager.log_warn(
|
||||||
|
"Tile missing grid_position during removal", "Match3"
|
||||||
|
)
|
||||||
tile.queue_free()
|
tile.queue_free()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -322,7 +352,10 @@ func _clear_matches() -> void:
|
|||||||
grid[tile_pos.y][tile_pos.x] = null
|
grid[tile_pos.y][tile_pos.x] = null
|
||||||
else:
|
else:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Invalid grid position during tile removal: (%d,%d)" % [tile_pos.x, tile_pos.y],
|
(
|
||||||
|
"Invalid grid position during tile removal: (%d,%d)"
|
||||||
|
% [tile_pos.x, tile_pos.y]
|
||||||
|
),
|
||||||
"Match3"
|
"Match3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -358,7 +391,9 @@ func _drop_tiles():
|
|||||||
func _fill_empty_cells():
|
func _fill_empty_cells():
|
||||||
# Safety check for grid integrity
|
# Safety check for grid integrity
|
||||||
if not _validate_grid_integrity():
|
if not _validate_grid_integrity():
|
||||||
DebugManager.log_error("Grid integrity check failed in _fill_empty_cells", "Match3")
|
DebugManager.log_error(
|
||||||
|
"Grid integrity check failed in _fill_empty_cells", "Match3"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create gem pool for current tile types
|
# Create gem pool for current tile types
|
||||||
@@ -374,14 +409,17 @@ func _fill_empty_cells():
|
|||||||
|
|
||||||
for x in range(grid_size.x):
|
for x in range(grid_size.x):
|
||||||
if x >= grid[y].size():
|
if x >= grid[y].size():
|
||||||
DebugManager.log_error("Grid column %d does not exist in row %d" % [x, y], "Match3")
|
DebugManager.log_error(
|
||||||
|
"Grid column %d does not exist in row %d" % [x, y], "Match3"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not grid[y][x]:
|
if not grid[y][x]:
|
||||||
var tile = TILE_SCENE.instantiate()
|
var tile = TILE_SCENE.instantiate()
|
||||||
if not tile:
|
if not tile:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to instantiate tile at (%d,%d)" % [x, y], "Match3"
|
"Failed to instantiate tile at (%d,%d)" % [x, y],
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -393,13 +431,17 @@ func _fill_empty_cells():
|
|||||||
if tile.has_method("set_active_gem_types"):
|
if tile.has_method("set_active_gem_types"):
|
||||||
tile.set_active_gem_types(gem_indices)
|
tile.set_active_gem_types(gem_indices)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_warn("Tile missing set_active_gem_types method", "Match3")
|
DebugManager.log_warn(
|
||||||
|
"Tile missing set_active_gem_types method", "Match3"
|
||||||
|
)
|
||||||
|
|
||||||
# Set random tile type with bounds checking
|
# Set random tile type with bounds checking
|
||||||
if tile_types > 0:
|
if tile_types > 0:
|
||||||
tile.tile_type = randi() % tile_types
|
tile.tile_type = randi() % tile_types
|
||||||
else:
|
else:
|
||||||
DebugManager.log_error("tile_types is 0, cannot set tile type", "Match3")
|
DebugManager.log_error(
|
||||||
|
"tile_types is 0, cannot set tile type", "Match3"
|
||||||
|
)
|
||||||
tile.queue_free()
|
tile.queue_free()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -408,7 +450,9 @@ func _fill_empty_cells():
|
|||||||
if tile.has_signal("tile_selected"):
|
if tile.has_signal("tile_selected"):
|
||||||
tile.tile_selected.connect(_on_tile_selected)
|
tile.tile_selected.connect(_on_tile_selected)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_warn("Tile missing tile_selected signal", "Match3")
|
DebugManager.log_warn(
|
||||||
|
"Tile missing tile_selected signal", "Match3"
|
||||||
|
)
|
||||||
|
|
||||||
tiles_created += 1
|
tiles_created += 1
|
||||||
|
|
||||||
@@ -423,12 +467,15 @@ func _fill_empty_cells():
|
|||||||
iteration += 1
|
iteration += 1
|
||||||
|
|
||||||
if iteration >= MAX_CASCADE_ITERATIONS:
|
if iteration >= MAX_CASCADE_ITERATIONS:
|
||||||
DebugManager.log_warn(
|
(
|
||||||
(
|
DebugManager
|
||||||
"Maximum cascade iterations reached (%d), stopping to prevent infinite loop"
|
. log_warn(
|
||||||
% MAX_CASCADE_ITERATIONS
|
(
|
||||||
),
|
"Maximum cascade iterations reached (%d), stopping to prevent infinite loop"
|
||||||
"Match3"
|
% MAX_CASCADE_ITERATIONS
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save grid state after cascades complete
|
# Save grid state after cascades complete
|
||||||
@@ -444,20 +491,27 @@ func regenerate_grid():
|
|||||||
or grid_size.y > MAX_GRID_SIZE
|
or grid_size.y > MAX_GRID_SIZE
|
||||||
):
|
):
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid grid size for regeneration: %dx%d" % [grid_size.x, grid_size.y], "Match3"
|
(
|
||||||
|
"Invalid grid size for regeneration: %dx%d"
|
||||||
|
% [grid_size.x, grid_size.y]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if tile_types < 3 or tile_types > MAX_TILE_TYPES:
|
if tile_types < 3 or tile_types > MAX_TILE_TYPES:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid tile types count for regeneration: %d" % tile_types, "Match3"
|
"Invalid tile types count for regeneration: %d" % tile_types,
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Use time-based seed to ensure different patterns each time
|
# Use time-based seed to ensure different patterns each time
|
||||||
var new_seed = Time.get_ticks_msec()
|
var new_seed = Time.get_ticks_msec()
|
||||||
seed(new_seed)
|
seed(new_seed)
|
||||||
DebugManager.log_debug("Regenerating grid with seed: " + str(new_seed), "Match3")
|
DebugManager.log_debug(
|
||||||
|
"Regenerating grid with seed: " + str(new_seed), "Match3"
|
||||||
|
)
|
||||||
|
|
||||||
# Safe tile cleanup with improved error handling
|
# Safe tile cleanup with improved error handling
|
||||||
var children_to_remove = []
|
var children_to_remove = []
|
||||||
@@ -477,7 +531,9 @@ func regenerate_grid():
|
|||||||
children_to_remove.append(child)
|
children_to_remove.append(child)
|
||||||
removed_count += 1
|
removed_count += 1
|
||||||
|
|
||||||
DebugManager.log_debug("Found %d tile children to remove" % removed_count, "Match3")
|
DebugManager.log_debug(
|
||||||
|
"Found %d tile children to remove" % removed_count, "Match3"
|
||||||
|
)
|
||||||
|
|
||||||
# First clear grid array references to prevent access to nodes being freed
|
# First clear grid array references to prevent access to nodes being freed
|
||||||
for y in range(grid.size()):
|
for y in range(grid.size()):
|
||||||
@@ -508,20 +564,30 @@ func regenerate_grid():
|
|||||||
func set_tile_types(new_count: int):
|
func set_tile_types(new_count: int):
|
||||||
# Input validation
|
# Input validation
|
||||||
if new_count < 3:
|
if new_count < 3:
|
||||||
DebugManager.log_error("Tile types count too low: %d (minimum 3)" % new_count, "Match3")
|
DebugManager.log_error(
|
||||||
|
"Tile types count too low: %d (minimum 3)" % new_count, "Match3"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if new_count > MAX_TILE_TYPES:
|
if new_count > MAX_TILE_TYPES:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Tile types count too high: %d (maximum %d)" % [new_count, MAX_TILE_TYPES], "Match3"
|
(
|
||||||
|
"Tile types count too high: %d (maximum %d)"
|
||||||
|
% [new_count, MAX_TILE_TYPES]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if new_count == tile_types:
|
if new_count == tile_types:
|
||||||
DebugManager.log_debug("Tile types count unchanged, skipping regeneration", "Match3")
|
DebugManager.log_debug(
|
||||||
|
"Tile types count unchanged, skipping regeneration", "Match3"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
DebugManager.log_debug("Changing tile types from %d to %d" % [tile_types, new_count], "Match3")
|
DebugManager.log_debug(
|
||||||
|
"Changing tile types from %d to %d" % [tile_types, new_count], "Match3"
|
||||||
|
)
|
||||||
tile_types = new_count
|
tile_types = new_count
|
||||||
|
|
||||||
# Regenerate grid with new tile types (gem pool is updated in regenerate_grid)
|
# Regenerate grid with new tile types (gem pool is updated in regenerate_grid)
|
||||||
@@ -551,10 +617,14 @@ func set_grid_size(new_size: Vector2i):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if new_size == grid_size:
|
if new_size == grid_size:
|
||||||
DebugManager.log_debug("Grid size unchanged, skipping regeneration", "Match3")
|
DebugManager.log_debug(
|
||||||
|
"Grid size unchanged, skipping regeneration", "Match3"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
DebugManager.log_debug("Changing grid size from %s to %s" % [grid_size, new_size], "Match3")
|
DebugManager.log_debug(
|
||||||
|
"Changing grid size from %s to %s" % [grid_size, new_size], "Match3"
|
||||||
|
)
|
||||||
grid_size = new_size
|
grid_size = new_size
|
||||||
|
|
||||||
# Regenerate grid with new size
|
# Regenerate grid with new size
|
||||||
@@ -577,13 +647,19 @@ func reset_all_visual_states() -> void:
|
|||||||
|
|
||||||
func _debug_scene_structure() -> void:
|
func _debug_scene_structure() -> void:
|
||||||
DebugManager.log_debug("=== Scene Structure Debug ===", "Match3")
|
DebugManager.log_debug("=== Scene Structure Debug ===", "Match3")
|
||||||
DebugManager.log_debug("Match3 node children count: %d" % get_child_count(), "Match3")
|
DebugManager.log_debug(
|
||||||
DebugManager.log_debug("Match3 global position: %s" % global_position, "Match3")
|
"Match3 node children count: %d" % get_child_count(), "Match3"
|
||||||
|
)
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"Match3 global position: %s" % global_position, "Match3"
|
||||||
|
)
|
||||||
DebugManager.log_debug("Match3 scale: %s" % scale, "Match3")
|
DebugManager.log_debug("Match3 scale: %s" % scale, "Match3")
|
||||||
|
|
||||||
# Check if grid is properly initialized
|
# Check if grid is properly initialized
|
||||||
if not grid or grid.size() == 0:
|
if not grid or grid.size() == 0:
|
||||||
DebugManager.log_error("Grid not initialized when debug structure called", "Match3")
|
DebugManager.log_error(
|
||||||
|
"Grid not initialized when debug structure called", "Match3"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check tiles
|
# Check tiles
|
||||||
@@ -593,23 +669,33 @@ func _debug_scene_structure() -> void:
|
|||||||
if y < grid.size() and x < grid[y].size() and grid[y][x]:
|
if y < grid.size() and x < grid[y].size() and grid[y][x]:
|
||||||
tile_count += 1
|
tile_count += 1
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Created %d tiles out of %d expected" % [tile_count, grid_size.x * grid_size.y], "Match3"
|
(
|
||||||
|
"Created %d tiles out of %d expected"
|
||||||
|
% [tile_count, grid_size.x * grid_size.y]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check first tile in detail
|
# Check first tile in detail
|
||||||
if grid.size() > 0 and grid[0].size() > 0 and grid[0][0]:
|
if grid.size() > 0 and grid[0].size() > 0 and grid[0][0]:
|
||||||
var first_tile = grid[0][0]
|
var first_tile = grid[0][0]
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"First tile global position: %s" % first_tile.global_position, "Match3"
|
"First tile global position: %s" % first_tile.global_position,
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"First tile local position: %s" % first_tile.position, "Match3"
|
||||||
)
|
)
|
||||||
DebugManager.log_debug("First tile local position: %s" % first_tile.position, "Match3")
|
|
||||||
|
|
||||||
# Check parent chain
|
# Check parent chain
|
||||||
var current_node = self
|
var current_node = self
|
||||||
var depth = 0
|
var depth = 0
|
||||||
while current_node and depth < 10:
|
while current_node and depth < 10:
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Parent level %d: %s (type: %s)" % [depth, current_node.name, current_node.get_class()],
|
(
|
||||||
|
"Parent level %d: %s (type: %s)"
|
||||||
|
% [depth, current_node.name, current_node.get_class()]
|
||||||
|
),
|
||||||
"Match3"
|
"Match3"
|
||||||
)
|
)
|
||||||
current_node = current_node.get_parent()
|
current_node = current_node.get_parent()
|
||||||
@@ -618,16 +704,27 @@ func _debug_scene_structure() -> void:
|
|||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
# Debug key to reset all visual states
|
# Debug key to reset all visual states
|
||||||
if event.is_action_pressed("action_east") and DebugManager.is_debug_enabled():
|
if (
|
||||||
|
event.is_action_pressed("action_east")
|
||||||
|
and DebugManager.is_debug_enabled()
|
||||||
|
):
|
||||||
reset_all_visual_states()
|
reset_all_visual_states()
|
||||||
return
|
return
|
||||||
|
|
||||||
if current_state == GameState.SWAPPING or current_state == GameState.PROCESSING:
|
if (
|
||||||
|
current_state == GameState.SWAPPING
|
||||||
|
or current_state == GameState.PROCESSING
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Handle action_east (B button/ESC) to deselect selected tile
|
# Handle action_east (B button/ESC) to deselect selected tile
|
||||||
if event.is_action_pressed("action_east") and current_state == GameState.SELECTING:
|
if (
|
||||||
DebugManager.log_debug("action_east pressed - deselecting current tile", "Match3")
|
event.is_action_pressed("action_east")
|
||||||
|
and current_state == GameState.SELECTING
|
||||||
|
):
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"action_east pressed - deselecting current tile", "Match3"
|
||||||
|
)
|
||||||
_deselect_tile()
|
_deselect_tile()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -652,18 +749,23 @@ func _input(event: InputEvent) -> void:
|
|||||||
func _move_cursor(direction: Vector2i) -> void:
|
func _move_cursor(direction: Vector2i) -> void:
|
||||||
# Input validation for direction vector
|
# Input validation for direction vector
|
||||||
if abs(direction.x) > 1 or abs(direction.y) > 1:
|
if abs(direction.x) > 1 or abs(direction.y) > 1:
|
||||||
DebugManager.log_error("Invalid cursor direction vector: " + str(direction), "Match3")
|
DebugManager.log_error(
|
||||||
|
"Invalid cursor direction vector: " + str(direction), "Match3"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if direction.x != 0 and direction.y != 0:
|
if direction.x != 0 and direction.y != 0:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Diagonal cursor movement not supported: " + str(direction), "Match3"
|
"Diagonal cursor movement not supported: " + str(direction),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Validate grid integrity before cursor operations
|
# Validate grid integrity before cursor operations
|
||||||
if not _validate_grid_integrity():
|
if not _validate_grid_integrity():
|
||||||
DebugManager.log_error("Grid integrity check failed in cursor movement", "Match3")
|
DebugManager.log_error(
|
||||||
|
"Grid integrity check failed in cursor movement", "Match3"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
var old_pos = cursor_position
|
var old_pos = cursor_position
|
||||||
@@ -676,19 +778,30 @@ func _move_cursor(direction: Vector2i) -> void:
|
|||||||
if new_pos != cursor_position:
|
if new_pos != cursor_position:
|
||||||
# Safe access to old tile
|
# Safe access to old tile
|
||||||
var old_tile = _safe_grid_access(old_pos)
|
var old_tile = _safe_grid_access(old_pos)
|
||||||
if old_tile and "is_selected" in old_tile and "is_highlighted" in old_tile:
|
if (
|
||||||
|
old_tile
|
||||||
|
and "is_selected" in old_tile
|
||||||
|
and "is_highlighted" in old_tile
|
||||||
|
):
|
||||||
if not old_tile.is_selected:
|
if not old_tile.is_selected:
|
||||||
old_tile.is_highlighted = false
|
old_tile.is_highlighted = false
|
||||||
|
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Cursor moved from (%d,%d) to (%d,%d)" % [old_pos.x, old_pos.y, new_pos.x, new_pos.y],
|
(
|
||||||
|
"Cursor moved from (%d,%d) to (%d,%d)"
|
||||||
|
% [old_pos.x, old_pos.y, new_pos.x, new_pos.y]
|
||||||
|
),
|
||||||
"Match3"
|
"Match3"
|
||||||
)
|
)
|
||||||
cursor_position = new_pos
|
cursor_position = new_pos
|
||||||
|
|
||||||
# Safe access to new tile
|
# Safe access to new tile
|
||||||
var new_tile = _safe_grid_access(cursor_position)
|
var new_tile = _safe_grid_access(cursor_position)
|
||||||
if new_tile and "is_selected" in new_tile and "is_highlighted" in new_tile:
|
if (
|
||||||
|
new_tile
|
||||||
|
and "is_selected" in new_tile
|
||||||
|
and "is_highlighted" in new_tile
|
||||||
|
):
|
||||||
if not new_tile.is_selected:
|
if not new_tile.is_selected:
|
||||||
new_tile.is_highlighted = true
|
new_tile.is_highlighted = true
|
||||||
|
|
||||||
@@ -708,13 +821,19 @@ func _select_tile_at_cursor() -> void:
|
|||||||
var tile = _safe_grid_access(cursor_position)
|
var tile = _safe_grid_access(cursor_position)
|
||||||
if tile:
|
if tile:
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Keyboard selection at cursor (%d,%d)" % [cursor_position.x, cursor_position.y],
|
(
|
||||||
|
"Keyboard selection at cursor (%d,%d)"
|
||||||
|
% [cursor_position.x, cursor_position.y]
|
||||||
|
),
|
||||||
"Match3"
|
"Match3"
|
||||||
)
|
)
|
||||||
_on_tile_selected(tile)
|
_on_tile_selected(tile)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"No valid tile at cursor position (%d,%d)" % [cursor_position.x, cursor_position.y],
|
(
|
||||||
|
"No valid tile at cursor position (%d,%d)"
|
||||||
|
% [cursor_position.x, cursor_position.y]
|
||||||
|
),
|
||||||
"Match3"
|
"Match3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -728,9 +847,15 @@ func _on_tile_selected(tile: Node2D) -> void:
|
|||||||
SELECTING -> SWAPPING: Different tile clicked (attempt swap)
|
SELECTING -> SWAPPING: Different tile clicked (attempt swap)
|
||||||
"""
|
"""
|
||||||
# Block tile selection during busy states
|
# Block tile selection during busy states
|
||||||
if current_state == GameState.SWAPPING or current_state == GameState.PROCESSING:
|
if (
|
||||||
|
current_state == GameState.SWAPPING
|
||||||
|
or current_state == GameState.PROCESSING
|
||||||
|
):
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Tile selection ignored - game busy (state: %s)" % [GameState.keys()[current_state]],
|
(
|
||||||
|
"Tile selection ignored - game busy (state: %s)"
|
||||||
|
% [GameState.keys()[current_state]]
|
||||||
|
),
|
||||||
"Match3"
|
"Match3"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -773,7 +898,11 @@ func _select_tile(tile: Node2D) -> void:
|
|||||||
tile.is_selected = true
|
tile.is_selected = true
|
||||||
current_state = GameState.SELECTING # State transition: WAITING -> SELECTING
|
current_state = GameState.SELECTING # State transition: WAITING -> SELECTING
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Selected tile at (%d, %d)" % [tile.grid_position.x, tile.grid_position.y], "Match3"
|
(
|
||||||
|
"Selected tile at (%d, %d)"
|
||||||
|
% [tile.grid_position.x, tile.grid_position.y]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -784,12 +913,17 @@ func _deselect_tile() -> void:
|
|||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
(
|
(
|
||||||
"Deselecting tile at (%d,%d)"
|
"Deselecting tile at (%d,%d)"
|
||||||
% [selected_tile.grid_position.x, selected_tile.grid_position.y]
|
% [
|
||||||
|
selected_tile.grid_position.x,
|
||||||
|
selected_tile.grid_position.y
|
||||||
|
]
|
||||||
),
|
),
|
||||||
"Match3"
|
"Match3"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_debug("Deselecting tile (no grid position available)", "Match3")
|
DebugManager.log_debug(
|
||||||
|
"Deselecting tile (no grid position available)", "Match3"
|
||||||
|
)
|
||||||
|
|
||||||
# Safe property access for selection state
|
# Safe property access for selection state
|
||||||
if "is_selected" in selected_tile:
|
if "is_selected" in selected_tile:
|
||||||
@@ -833,7 +967,9 @@ func _are_adjacent(tile1: Node2D, tile2: Node2D) -> bool:
|
|||||||
|
|
||||||
func _attempt_swap(tile1: Node2D, tile2: Node2D) -> void:
|
func _attempt_swap(tile1: Node2D, tile2: Node2D) -> void:
|
||||||
if not _are_adjacent(tile1, tile2):
|
if not _are_adjacent(tile1, tile2):
|
||||||
DebugManager.log_debug("Tiles are not adjacent, selecting new tile instead", "Match3")
|
DebugManager.log_debug(
|
||||||
|
"Tiles are not adjacent, selecting new tile instead", "Match3"
|
||||||
|
)
|
||||||
_deselect_tile()
|
_deselect_tile()
|
||||||
_select_tile(tile2)
|
_select_tile(tile2)
|
||||||
return
|
return
|
||||||
@@ -886,7 +1022,9 @@ func _attempt_swap(tile1: Node2D, tile2: Node2D) -> void:
|
|||||||
|
|
||||||
func _swap_tiles(tile1: Node2D, tile2: Node2D) -> void:
|
func _swap_tiles(tile1: Node2D, tile2: Node2D) -> void:
|
||||||
if not tile1 or not tile2:
|
if not tile1 or not tile2:
|
||||||
DebugManager.log_error("Cannot swap tiles - one or both tiles are null", "Match3")
|
DebugManager.log_error(
|
||||||
|
"Cannot swap tiles - one or both tiles are null", "Match3"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update grid positions
|
# Update grid positions
|
||||||
@@ -934,7 +1072,9 @@ func serialize_grid_state() -> Array:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if grid.size() == 0:
|
if grid.size() == 0:
|
||||||
DebugManager.log_error("Grid array is empty during serialization!", "Match3")
|
DebugManager.log_error(
|
||||||
|
"Grid array is empty during serialization!", "Match3"
|
||||||
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
var serialized_grid = []
|
var serialized_grid = []
|
||||||
@@ -950,7 +1090,11 @@ func serialize_grid_state() -> Array:
|
|||||||
# Only log first few for brevity
|
# Only log first few for brevity
|
||||||
if valid_tiles <= 5:
|
if valid_tiles <= 5:
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Serializing tile (%d,%d): type %d" % [x, y, grid[y][x].tile_type], "Match3"
|
(
|
||||||
|
"Serializing tile (%d,%d): type %d"
|
||||||
|
% [x, y, grid[y][x].tile_type]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
row.append(-1) # Invalid/empty tile
|
row.append(-1) # Invalid/empty tile
|
||||||
@@ -958,7 +1102,8 @@ func serialize_grid_state() -> Array:
|
|||||||
# Only log first few nulls for brevity
|
# Only log first few nulls for brevity
|
||||||
if null_tiles <= 5:
|
if null_tiles <= 5:
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Serializing tile (%d,%d): NULL/empty (-1)" % [x, y], "Match3"
|
"Serializing tile (%d,%d): NULL/empty (-1)" % [x, y],
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
serialized_grid.append(row)
|
serialized_grid.append(row)
|
||||||
|
|
||||||
@@ -1002,7 +1147,9 @@ func save_current_state():
|
|||||||
func load_saved_state() -> bool:
|
func load_saved_state() -> bool:
|
||||||
# Check if there's a saved grid state
|
# Check if there's a saved grid state
|
||||||
if not SaveManager.has_saved_grid():
|
if not SaveManager.has_saved_grid():
|
||||||
DebugManager.log_info("No saved grid state found, using default generation", "Match3")
|
DebugManager.log_info(
|
||||||
|
"No saved grid state found, using default generation", "Match3"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
var saved_state = SaveManager.get_saved_grid_state()
|
var saved_state = SaveManager.get_saved_grid_state()
|
||||||
@@ -1015,12 +1162,21 @@ func load_saved_state() -> bool:
|
|||||||
saved_gems.append(int(gem))
|
saved_gems.append(int(gem))
|
||||||
var saved_layout = saved_state.grid_layout
|
var saved_layout = saved_state.grid_layout
|
||||||
|
|
||||||
DebugManager.log_info(
|
(
|
||||||
(
|
DebugManager
|
||||||
"[%s] Loading saved grid state: size(%d,%d), %d tile types, layout_size=%d"
|
. log_info(
|
||||||
% [instance_id, saved_size.x, saved_size.y, tile_types, saved_layout.size()]
|
(
|
||||||
),
|
"[%s] Loading saved grid state: size(%d,%d), %d tile types, layout_size=%d"
|
||||||
"Match3"
|
% [
|
||||||
|
instance_id,
|
||||||
|
saved_size.x,
|
||||||
|
saved_size.y,
|
||||||
|
tile_types,
|
||||||
|
saved_layout.size()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Debug: Print first few rows of loaded layout
|
# Debug: Print first few rows of loaded layout
|
||||||
@@ -1058,7 +1214,10 @@ func load_saved_state() -> bool:
|
|||||||
# Recalculate layout if size changed
|
# Recalculate layout if size changed
|
||||||
if old_size != saved_size:
|
if old_size != saved_size:
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Grid size changed from %s to %s, recalculating layout" % [old_size, saved_size],
|
(
|
||||||
|
"Grid size changed from %s to %s, recalculating layout"
|
||||||
|
% [old_size, saved_size]
|
||||||
|
),
|
||||||
"Match3"
|
"Match3"
|
||||||
)
|
)
|
||||||
_calculate_grid_layout()
|
_calculate_grid_layout()
|
||||||
@@ -1069,7 +1228,9 @@ func load_saved_state() -> bool:
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> void:
|
func _restore_grid_from_layout(
|
||||||
|
grid_layout: Array, active_gems: Array[int]
|
||||||
|
) -> void:
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
(
|
(
|
||||||
"[%s] Starting grid restoration: layout_size=%d, active_gems=%s"
|
"[%s] Starting grid restoration: layout_size=%d, active_gems=%s"
|
||||||
@@ -1088,7 +1249,8 @@ func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> v
|
|||||||
all_tile_children.append(child)
|
all_tile_children.append(child)
|
||||||
|
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Found %d existing tile children to remove" % all_tile_children.size(), "Match3"
|
"Found %d existing tile children to remove" % all_tile_children.size(),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Remove all found tile children
|
# Remove all found tile children
|
||||||
@@ -1133,17 +1295,24 @@ func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> v
|
|||||||
if saved_tile_type >= 0 and saved_tile_type < tile_types:
|
if saved_tile_type >= 0 and saved_tile_type < tile_types:
|
||||||
tile.tile_type = saved_tile_type
|
tile.tile_type = saved_tile_type
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"✓ Restored tile (%d,%d) with saved type %d" % [x, y, saved_tile_type], "Match3"
|
(
|
||||||
|
"✓ Restored tile (%d,%d) with saved type %d"
|
||||||
|
% [x, y, saved_tile_type]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Fallback for invalid tile types
|
# Fallback for invalid tile types
|
||||||
tile.tile_type = randi() % tile_types
|
tile.tile_type = randi() % tile_types
|
||||||
DebugManager.log_error(
|
(
|
||||||
(
|
DebugManager
|
||||||
"✗ Invalid saved tile type %d at (%d,%d), using random %d"
|
. log_error(
|
||||||
% [saved_tile_type, x, y, tile.tile_type]
|
(
|
||||||
),
|
"✗ Invalid saved tile type %d at (%d,%d), using random %d"
|
||||||
"Match3"
|
% [saved_tile_type, x, y, tile.tile_type]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Connect tile signals
|
# Connect tile signals
|
||||||
@@ -1151,13 +1320,22 @@ func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> v
|
|||||||
grid[y].append(tile)
|
grid[y].append(tile)
|
||||||
|
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Completed grid restoration: %d tiles restored" % [grid_size.x * grid_size.y], "Match3"
|
(
|
||||||
|
"Completed grid restoration: %d tiles restored"
|
||||||
|
% [grid_size.x * grid_size.y]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Safety and validation helper functions
|
# Safety and validation helper functions
|
||||||
func _is_valid_grid_position(pos: Vector2i) -> bool:
|
func _is_valid_grid_position(pos: Vector2i) -> bool:
|
||||||
return pos.x >= 0 and pos.y >= 0 and pos.x < grid_size.x and pos.y < grid_size.y
|
return (
|
||||||
|
pos.x >= 0
|
||||||
|
and pos.y >= 0
|
||||||
|
and pos.x < grid_size.x
|
||||||
|
and pos.y < grid_size.y
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _validate_grid_integrity() -> bool:
|
func _validate_grid_integrity() -> bool:
|
||||||
@@ -1168,7 +1346,8 @@ func _validate_grid_integrity() -> bool:
|
|||||||
|
|
||||||
if grid.size() != grid_size.y:
|
if grid.size() != grid_size.y:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Grid height mismatch: %d vs %d" % [grid.size(), grid_size.y], "Match3"
|
"Grid height mismatch: %d vs %d" % [grid.size(), grid_size.y],
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -1179,7 +1358,11 @@ func _validate_grid_integrity() -> bool:
|
|||||||
|
|
||||||
if grid[y].size() != grid_size.x:
|
if grid[y].size() != grid_size.x:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Grid row %d width mismatch: %d vs %d" % [y, grid[y].size(), grid_size.x], "Match3"
|
(
|
||||||
|
"Grid row %d width mismatch: %d vs %d"
|
||||||
|
% [y, grid[y].size(), grid_size.x]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -1192,7 +1375,9 @@ func _safe_grid_access(pos: Vector2i) -> Node2D:
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
if pos.y >= grid.size() or pos.x >= grid[pos.y].size():
|
if pos.y >= grid.size() or pos.x >= grid[pos.y].size():
|
||||||
DebugManager.log_warn("Grid bounds exceeded: (%d,%d)" % [pos.x, pos.y], "Match3")
|
DebugManager.log_warn(
|
||||||
|
"Grid bounds exceeded: (%d,%d)" % [pos.x, pos.y], "Match3"
|
||||||
|
)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
var tile = grid[pos.y][pos.x]
|
var tile = grid[pos.y][pos.x]
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ extends RefCounted
|
|||||||
## var grid_pos = Match3InputHandler.get_grid_position_from_world(node, world_pos, offset, size)
|
## var grid_pos = Match3InputHandler.get_grid_position_from_world(node, world_pos, offset, size)
|
||||||
|
|
||||||
|
|
||||||
static func find_tile_at_position(grid: Array, grid_size: Vector2i, world_pos: Vector2) -> Node2D:
|
static func find_tile_at_position(
|
||||||
|
grid: Array, grid_size: Vector2i, world_pos: Vector2
|
||||||
|
) -> Node2D:
|
||||||
## Find the tile that contains the world position.
|
## Find the tile that contains the world position.
|
||||||
##
|
##
|
||||||
## Iterates through all tiles and checks if the world position falls within
|
## Iterates through all tiles and checks if the world position falls within
|
||||||
@@ -31,7 +33,9 @@ static func find_tile_at_position(grid: Array, grid_size: Vector2i, world_pos: V
|
|||||||
if tile and tile.has_node("Sprite2D"):
|
if tile and tile.has_node("Sprite2D"):
|
||||||
var sprite = tile.get_node("Sprite2D")
|
var sprite = tile.get_node("Sprite2D")
|
||||||
if sprite and sprite.texture:
|
if sprite and sprite.texture:
|
||||||
var sprite_bounds = get_sprite_world_bounds(tile, sprite)
|
var sprite_bounds = get_sprite_world_bounds(
|
||||||
|
tile, sprite
|
||||||
|
)
|
||||||
if is_point_inside_rect(world_pos, sprite_bounds):
|
if is_point_inside_rect(world_pos, sprite_bounds):
|
||||||
return tile
|
return tile
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ static func serialize_grid_state(grid: Array, grid_size: Vector2i) -> Array:
|
|||||||
return serialized_grid
|
return serialized_grid
|
||||||
|
|
||||||
|
|
||||||
static func get_active_gem_types_from_grid(grid: Array, tile_types: int) -> Array:
|
static func get_active_gem_types_from_grid(
|
||||||
|
grid: Array, tile_types: int
|
||||||
|
) -> Array:
|
||||||
# Get active gem types from the first available tile
|
# Get active gem types from the first available tile
|
||||||
if grid.size() > 0 and grid[0].size() > 0 and grid[0][0]:
|
if grid.size() > 0 and grid[0].size() > 0 and grid[0][0]:
|
||||||
return grid[0][0].active_gem_types.duplicate()
|
return grid[0][0].active_gem_types.duplicate()
|
||||||
@@ -132,9 +134,15 @@ static func restore_grid_from_layout(
|
|||||||
tile.tile_type = randi() % tile_types
|
tile.tile_type = randi() % tile_types
|
||||||
|
|
||||||
# Connect tile signals
|
# Connect tile signals
|
||||||
if tile.has_signal("tile_selected") and match3_node.has_method("_on_tile_selected"):
|
if (
|
||||||
|
tile.has_signal("tile_selected")
|
||||||
|
and match3_node.has_method("_on_tile_selected")
|
||||||
|
):
|
||||||
tile.tile_selected.connect(match3_node._on_tile_selected)
|
tile.tile_selected.connect(match3_node._on_tile_selected)
|
||||||
if tile.has_signal("tile_hovered") and match3_node.has_method("_on_tile_hovered"):
|
if (
|
||||||
|
tile.has_signal("tile_hovered")
|
||||||
|
and match3_node.has_method("_on_tile_hovered")
|
||||||
|
):
|
||||||
tile.tile_hovered.connect(match3_node._on_tile_hovered)
|
tile.tile_hovered.connect(match3_node._on_tile_hovered)
|
||||||
tile.tile_unhovered.connect(match3_node._on_tile_unhovered)
|
tile.tile_unhovered.connect(match3_node._on_tile_unhovered)
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ static func is_valid_grid_position(pos: Vector2i, grid_size: Vector2i) -> bool:
|
|||||||
##
|
##
|
||||||
## Returns:
|
## Returns:
|
||||||
## bool: True if position is valid, False if out of bounds
|
## bool: True if position is valid, False if out of bounds
|
||||||
return pos.x >= 0 and pos.y >= 0 and pos.x < grid_size.x and pos.y < grid_size.y
|
return (
|
||||||
|
pos.x >= 0
|
||||||
|
and pos.y >= 0
|
||||||
|
and pos.x < grid_size.x
|
||||||
|
and pos.y < grid_size.y
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
static func validate_grid_integrity(grid: Array, grid_size: Vector2i) -> bool:
|
static func validate_grid_integrity(grid: Array, grid_size: Vector2i) -> bool:
|
||||||
@@ -46,7 +51,8 @@ static func validate_grid_integrity(grid: Array, grid_size: Vector2i) -> bool:
|
|||||||
|
|
||||||
if grid.size() != grid_size.y:
|
if grid.size() != grid_size.y:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Grid height mismatch: %d vs %d" % [grid.size(), grid_size.y], "Match3"
|
"Grid height mismatch: %d vs %d" % [grid.size(), grid_size.y],
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -57,20 +63,28 @@ static func validate_grid_integrity(grid: Array, grid_size: Vector2i) -> bool:
|
|||||||
|
|
||||||
if grid[y].size() != grid_size.x:
|
if grid[y].size() != grid_size.x:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Grid row %d width mismatch: %d vs %d" % [y, grid[y].size(), grid_size.x], "Match3"
|
(
|
||||||
|
"Grid row %d width mismatch: %d vs %d"
|
||||||
|
% [y, grid[y].size(), grid_size.x]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
static func safe_grid_access(grid: Array, pos: Vector2i, grid_size: Vector2i) -> Node2D:
|
static func safe_grid_access(
|
||||||
|
grid: Array, pos: Vector2i, grid_size: Vector2i
|
||||||
|
) -> Node2D:
|
||||||
# Safe grid access with comprehensive bounds checking
|
# Safe grid access with comprehensive bounds checking
|
||||||
if not is_valid_grid_position(pos, grid_size):
|
if not is_valid_grid_position(pos, grid_size):
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if pos.y >= grid.size() or pos.x >= grid[pos.y].size():
|
if pos.y >= grid.size() or pos.x >= grid[pos.y].size():
|
||||||
DebugManager.log_warn("Grid bounds exceeded: (%d,%d)" % [pos.x, pos.y], "Match3")
|
DebugManager.log_warn(
|
||||||
|
"Grid bounds exceeded: (%d,%d)" % [pos.x, pos.y], "Match3"
|
||||||
|
)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
var tile = grid[pos.y][pos.x]
|
var tile = grid[pos.y][pos.x]
|
||||||
|
|||||||
@@ -123,7 +123,9 @@ func remove_gem_type(gem_index: int) -> bool:
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
if active_gem_types.size() <= 2: # Keep at least 2 gem types
|
if active_gem_types.size() <= 2: # Keep at least 2 gem types
|
||||||
DebugManager.log_warn("Cannot remove gem type - minimum 2 types required", "Tile")
|
DebugManager.log_warn(
|
||||||
|
"Cannot remove gem type - minimum 2 types required", "Tile"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
active_gem_types.erase(gem_index)
|
active_gem_types.erase(gem_index)
|
||||||
@@ -178,7 +180,12 @@ func _update_visual_feedback() -> void:
|
|||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
(
|
(
|
||||||
"SELECTING tile (%d,%d): target scale %.2fx, current scale %s"
|
"SELECTING tile (%d,%d): target scale %.2fx, current scale %s"
|
||||||
% [grid_position.x, grid_position.y, scale_multiplier, sprite.scale]
|
% [
|
||||||
|
grid_position.x,
|
||||||
|
grid_position.y,
|
||||||
|
scale_multiplier,
|
||||||
|
sprite.scale
|
||||||
|
]
|
||||||
),
|
),
|
||||||
"Match3"
|
"Match3"
|
||||||
)
|
)
|
||||||
@@ -186,12 +193,20 @@ func _update_visual_feedback() -> void:
|
|||||||
# Highlighted: subtle glow and larger than original board size
|
# Highlighted: subtle glow and larger than original board size
|
||||||
target_modulate = Color(1.1, 1.1, 1.1, 1.0)
|
target_modulate = Color(1.1, 1.1, 1.1, 1.0)
|
||||||
scale_multiplier = UIConstants.TILE_HIGHLIGHTED_SCALE
|
scale_multiplier = UIConstants.TILE_HIGHLIGHTED_SCALE
|
||||||
DebugManager.log_debug(
|
(
|
||||||
(
|
DebugManager
|
||||||
"HIGHLIGHTING tile (%d,%d): target scale %.2fx, current scale %s"
|
. log_debug(
|
||||||
% [grid_position.x, grid_position.y, scale_multiplier, sprite.scale]
|
(
|
||||||
),
|
"HIGHLIGHTING tile (%d,%d): target scale %.2fx, current scale %s"
|
||||||
"Match3"
|
% [
|
||||||
|
grid_position.x,
|
||||||
|
grid_position.y,
|
||||||
|
scale_multiplier,
|
||||||
|
sprite.scale
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Normal state: white and original board size
|
# Normal state: white and original board size
|
||||||
@@ -200,7 +215,12 @@ func _update_visual_feedback() -> void:
|
|||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
(
|
(
|
||||||
"NORMALIZING tile (%d,%d): target scale %.2fx, current scale %s"
|
"NORMALIZING tile (%d,%d): target scale %.2fx, current scale %s"
|
||||||
% [grid_position.x, grid_position.y, scale_multiplier, sprite.scale]
|
% [
|
||||||
|
grid_position.x,
|
||||||
|
grid_position.y,
|
||||||
|
scale_multiplier,
|
||||||
|
sprite.scale
|
||||||
|
]
|
||||||
),
|
),
|
||||||
"Match3"
|
"Match3"
|
||||||
)
|
)
|
||||||
@@ -228,7 +248,11 @@ func _update_visual_feedback() -> void:
|
|||||||
tween.tween_callback(_on_scale_animation_completed.bind(target_scale))
|
tween.tween_callback(_on_scale_animation_completed.bind(target_scale))
|
||||||
else:
|
else:
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"No scale change needed for tile (%d,%d)" % [grid_position.x, grid_position.y], "Match3"
|
(
|
||||||
|
"No scale change needed for tile (%d,%d)"
|
||||||
|
% [grid_position.x, grid_position.y]
|
||||||
|
),
|
||||||
|
"Match3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -264,7 +288,9 @@ func _input(event: InputEvent) -> void:
|
|||||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||||
# Check if the mouse click is within the tile's bounds
|
# Check if the mouse click is within the tile's bounds
|
||||||
var local_position = to_local(get_global_mouse_position())
|
var local_position = to_local(get_global_mouse_position())
|
||||||
var sprite_rect = Rect2(-TILE_SIZE / 2.0, -TILE_SIZE / 2.0, TILE_SIZE, TILE_SIZE)
|
var sprite_rect = Rect2(
|
||||||
|
-TILE_SIZE / 2.0, -TILE_SIZE / 2.0, TILE_SIZE, TILE_SIZE
|
||||||
|
)
|
||||||
|
|
||||||
if sprite_rect.has_point(local_position):
|
if sprite_rect.has_point(local_position):
|
||||||
tile_selected.emit(self)
|
tile_selected.emit(self)
|
||||||
@@ -22,7 +22,9 @@ func _setup_splash_screen_connection() -> void:
|
|||||||
# Try to find SplashScreen node
|
# Try to find SplashScreen node
|
||||||
splash_screen = get_node_or_null("SplashScreen")
|
splash_screen = get_node_or_null("SplashScreen")
|
||||||
if not splash_screen:
|
if not splash_screen:
|
||||||
DebugManager.log_warn("SplashScreen node not found, trying alternative methods", "Main")
|
DebugManager.log_warn(
|
||||||
|
"SplashScreen node not found, trying alternative methods", "Main"
|
||||||
|
)
|
||||||
# Try to find by class or group
|
# Try to find by class or group
|
||||||
var splash_nodes = get_tree().get_nodes_in_group("localizable")
|
var splash_nodes = get_tree().get_nodes_in_group("localizable")
|
||||||
for node in splash_nodes:
|
for node in splash_nodes:
|
||||||
@@ -31,11 +33,15 @@ func _setup_splash_screen_connection() -> void:
|
|||||||
break
|
break
|
||||||
|
|
||||||
if splash_screen:
|
if splash_screen:
|
||||||
DebugManager.log_debug("SplashScreen node found: %s" % splash_screen.name, "Main")
|
DebugManager.log_debug(
|
||||||
|
"SplashScreen node found: %s" % splash_screen.name, "Main"
|
||||||
|
)
|
||||||
# Try connecting to the signal if it exists
|
# Try connecting to the signal if it exists
|
||||||
if splash_screen.has_signal("confirm_pressed"):
|
if splash_screen.has_signal("confirm_pressed"):
|
||||||
splash_screen.confirm_pressed.connect(_on_confirm_pressed)
|
splash_screen.confirm_pressed.connect(_on_confirm_pressed)
|
||||||
DebugManager.log_debug("Connected to confirm_pressed signal", "Main")
|
DebugManager.log_debug(
|
||||||
|
"Connected to confirm_pressed signal", "Main"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Fallback: use input handling directly on the main scene
|
# Fallback: use input handling directly on the main scene
|
||||||
DebugManager.log_warn("Using fallback input handling", "Main")
|
DebugManager.log_warn("Using fallback input handling", "Main")
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ const YAML_SOURCES: Array[String] = [
|
|||||||
# "res://assets/sprites/sprite-sources.yaml",
|
# "res://assets/sprites/sprite-sources.yaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
@onready var scroll_container: ScrollContainer = $MarginContainer/VBoxContainer/ScrollContainer
|
@onready
|
||||||
@onready var credits_text: RichTextLabel = $MarginContainer/VBoxContainer/ScrollContainer/CreditsText
|
var scroll_container: ScrollContainer = $MarginContainer/VBoxContainer/ScrollContainer
|
||||||
|
@onready
|
||||||
|
var credits_text: RichTextLabel = $MarginContainer/VBoxContainer/ScrollContainer/CreditsText
|
||||||
@onready var back_button: Button = $MarginContainer/VBoxContainer/BackButton
|
@onready var back_button: Button = $MarginContainer/VBoxContainer/BackButton
|
||||||
|
|
||||||
|
|
||||||
@@ -40,7 +42,9 @@ func _load_yaml_file(yaml_path: String) -> Dictionary:
|
|||||||
var file := FileAccess.open(yaml_path, FileAccess.READ)
|
var file := FileAccess.open(yaml_path, FileAccess.READ)
|
||||||
|
|
||||||
if not file:
|
if not file:
|
||||||
DebugManager.log_warn("Could not open YAML file: %s" % yaml_path, "Credits")
|
DebugManager.log_warn(
|
||||||
|
"Could not open YAML file: %s" % yaml_path, "Credits"
|
||||||
|
)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
var content: String = file.get_as_text()
|
var content: String = file.get_as_text()
|
||||||
@@ -67,10 +71,18 @@ func _parse_yaml_content(yaml_content: String) -> Dictionary:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Top-level section (audio, sprites, textures, etc.)
|
# Top-level section (audio, sprites, textures, etc.)
|
||||||
if not line.begins_with(" ") and not line.begins_with("\t") and trimmed.ends_with(":"):
|
if (
|
||||||
|
not line.begins_with(" ")
|
||||||
|
and not line.begins_with("\t")
|
||||||
|
and trimmed.ends_with(":")
|
||||||
|
):
|
||||||
if current_asset and not current_asset_data.is_empty():
|
if current_asset and not current_asset_data.is_empty():
|
||||||
_store_asset_data(
|
_store_asset_data(
|
||||||
result, current_section, current_subsection, current_asset, current_asset_data
|
result,
|
||||||
|
current_section,
|
||||||
|
current_subsection,
|
||||||
|
current_asset,
|
||||||
|
current_asset_data
|
||||||
)
|
)
|
||||||
current_section = trimmed.trim_suffix(":")
|
current_section = trimmed.trim_suffix(":")
|
||||||
current_subsection = ""
|
current_subsection = ""
|
||||||
@@ -80,22 +92,37 @@ func _parse_yaml_content(yaml_content: String) -> Dictionary:
|
|||||||
result[current_section] = {}
|
result[current_section] = {}
|
||||||
|
|
||||||
# Subsection (music, sfx, characters, etc.)
|
# Subsection (music, sfx, characters, etc.)
|
||||||
elif line.begins_with(" ") and not line.begins_with(" ") and trimmed.ends_with(":"):
|
elif (
|
||||||
|
line.begins_with(" ")
|
||||||
|
and not line.begins_with(" ")
|
||||||
|
and trimmed.ends_with(":")
|
||||||
|
):
|
||||||
if current_asset and not current_asset_data.is_empty():
|
if current_asset and not current_asset_data.is_empty():
|
||||||
_store_asset_data(
|
_store_asset_data(
|
||||||
result, current_section, current_subsection, current_asset, current_asset_data
|
result,
|
||||||
|
current_section,
|
||||||
|
current_subsection,
|
||||||
|
current_asset,
|
||||||
|
current_asset_data
|
||||||
)
|
)
|
||||||
current_subsection = trimmed.trim_suffix(":")
|
current_subsection = trimmed.trim_suffix(":")
|
||||||
current_asset = ""
|
current_asset = ""
|
||||||
current_asset_data = {}
|
current_asset_data = {}
|
||||||
if current_section and not result[current_section].has(current_subsection):
|
if (
|
||||||
|
current_section
|
||||||
|
and not result[current_section].has(current_subsection)
|
||||||
|
):
|
||||||
result[current_section][current_subsection] = {}
|
result[current_section][current_subsection] = {}
|
||||||
|
|
||||||
# Asset name
|
# Asset name
|
||||||
elif trimmed.begins_with('"') and trimmed.contains('":'):
|
elif trimmed.begins_with('"') and trimmed.contains('":'):
|
||||||
if current_asset and not current_asset_data.is_empty():
|
if current_asset and not current_asset_data.is_empty():
|
||||||
_store_asset_data(
|
_store_asset_data(
|
||||||
result, current_section, current_subsection, current_asset, current_asset_data
|
result,
|
||||||
|
current_section,
|
||||||
|
current_subsection,
|
||||||
|
current_asset,
|
||||||
|
current_asset_data
|
||||||
)
|
)
|
||||||
var parts: Array = trimmed.split('"')
|
var parts: Array = trimmed.split('"')
|
||||||
current_asset = parts[1] if parts.size() > 1 else ""
|
current_asset = parts[1] if parts.size() > 1 else ""
|
||||||
@@ -106,21 +133,31 @@ func _parse_yaml_content(yaml_content: String) -> Dictionary:
|
|||||||
var parts: Array = trimmed.split(":", false, 1)
|
var parts: Array = trimmed.split(":", false, 1)
|
||||||
if parts.size() == 2:
|
if parts.size() == 2:
|
||||||
var key: String = parts[0].strip_edges()
|
var key: String = parts[0].strip_edges()
|
||||||
var value: String = parts[1].strip_edges().trim_prefix('"').trim_suffix('"')
|
var value: String = (
|
||||||
|
parts[1].strip_edges().trim_prefix('"').trim_suffix('"')
|
||||||
|
)
|
||||||
if value and value != '""':
|
if value and value != '""':
|
||||||
current_asset_data[key] = value
|
current_asset_data[key] = value
|
||||||
|
|
||||||
# Store last asset
|
# Store last asset
|
||||||
if current_asset and not current_asset_data.is_empty():
|
if current_asset and not current_asset_data.is_empty():
|
||||||
_store_asset_data(
|
_store_asset_data(
|
||||||
result, current_section, current_subsection, current_asset, current_asset_data
|
result,
|
||||||
|
current_section,
|
||||||
|
current_subsection,
|
||||||
|
current_asset,
|
||||||
|
current_asset_data
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
func _store_asset_data(
|
func _store_asset_data(
|
||||||
result: Dictionary, section: String, subsection: String, asset: String, data: Dictionary
|
result: Dictionary,
|
||||||
|
section: String,
|
||||||
|
subsection: String,
|
||||||
|
asset: String,
|
||||||
|
data: Dictionary
|
||||||
) -> void:
|
) -> void:
|
||||||
"""Store parsed asset data into result dictionary"""
|
"""Store parsed asset data into result dictionary"""
|
||||||
if not section or not asset:
|
if not section or not asset:
|
||||||
@@ -150,7 +187,9 @@ func _merge_credits_data(target: Dictionary, source: Dictionary) -> void:
|
|||||||
func _display_formatted_credits(credits_data: Dictionary) -> void:
|
func _display_formatted_credits(credits_data: Dictionary) -> void:
|
||||||
"""Generate BBCode formatted credits from parsed data"""
|
"""Generate BBCode formatted credits from parsed data"""
|
||||||
if not credits_text:
|
if not credits_text:
|
||||||
DebugManager.log_error("Credits text node is null, cannot display credits", "Credits")
|
DebugManager.log_error(
|
||||||
|
"Credits text node is null, cannot display credits", "Credits"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
var credits_bbcode: String = "[center][b][font_size=32]CREDITS[/font_size][/b][/center]\n\n"
|
var credits_bbcode: String = "[center][b][font_size=32]CREDITS[/font_size][/b][/center]\n\n"
|
||||||
@@ -227,11 +266,17 @@ func _on_back_button_pressed() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
if event.is_action_pressed("ui_back") or event.is_action_pressed("action_east"):
|
if (
|
||||||
|
event.is_action_pressed("ui_back")
|
||||||
|
or event.is_action_pressed("action_east")
|
||||||
|
):
|
||||||
_on_back_button_pressed()
|
_on_back_button_pressed()
|
||||||
elif event.is_action_pressed("move_up") or event.is_action_pressed("ui_up"):
|
elif event.is_action_pressed("move_up") or event.is_action_pressed("ui_up"):
|
||||||
_scroll_credits(-50.0)
|
_scroll_credits(-50.0)
|
||||||
elif event.is_action_pressed("move_down") or event.is_action_pressed("ui_down"):
|
elif (
|
||||||
|
event.is_action_pressed("move_down")
|
||||||
|
or event.is_action_pressed("ui_down")
|
||||||
|
):
|
||||||
_scroll_credits(50.0)
|
_scroll_credits(50.0)
|
||||||
|
|
||||||
|
|
||||||
@@ -239,4 +284,6 @@ func _scroll_credits(amount: float) -> void:
|
|||||||
"""Scroll the credits by the specified amount"""
|
"""Scroll the credits by the specified amount"""
|
||||||
var current_scroll: float = scroll_container.scroll_vertical
|
var current_scroll: float = scroll_container.scroll_vertical
|
||||||
scroll_container.scroll_vertical = int(current_scroll + amount)
|
scroll_container.scroll_vertical = int(current_scroll + amount)
|
||||||
DebugManager.log_debug("Scrolled credits to: %d" % scroll_container.scroll_vertical, "Credits")
|
DebugManager.log_debug(
|
||||||
|
"Scrolled credits to: %d" % scroll_container.scroll_vertical, "Credits"
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,12 +17,16 @@ func _find_target_scene():
|
|||||||
# Fallback: search by common node names
|
# Fallback: search by common node names
|
||||||
if not match3_scene:
|
if not match3_scene:
|
||||||
for possible_name in ["Match3", "match3", "Match3Game"]:
|
for possible_name in ["Match3", "match3", "Match3Game"]:
|
||||||
match3_scene = current_scene.find_child(possible_name, true, false)
|
match3_scene = current_scene.find_child(
|
||||||
|
possible_name, true, false
|
||||||
|
)
|
||||||
if match3_scene:
|
if match3_scene:
|
||||||
break
|
break
|
||||||
|
|
||||||
if match3_scene:
|
if match3_scene:
|
||||||
DebugManager.log_debug("Found match3 scene: " + match3_scene.name, log_category)
|
DebugManager.log_debug(
|
||||||
|
"Found match3 scene: " + match3_scene.name, log_category
|
||||||
|
)
|
||||||
_update_ui_from_scene()
|
_update_ui_from_scene()
|
||||||
_stop_search_timer()
|
_stop_search_timer()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ const MIN_GRID_SIZE := 3
|
|||||||
const MIN_TILE_TYPES := 3
|
const MIN_TILE_TYPES := 3
|
||||||
const SCENE_SEARCH_COOLDOWN := 0.5
|
const SCENE_SEARCH_COOLDOWN := 0.5
|
||||||
|
|
||||||
@export var target_script_path: String = "res://scenes/game/gameplays/Match3Gameplay.gd"
|
@export
|
||||||
|
var target_script_path: String = "res://scenes/game/gameplays/Match3Gameplay.gd"
|
||||||
@export var log_category: String = "DebugMenu"
|
@export var log_category: String = "DebugMenu"
|
||||||
|
|
||||||
var match3_scene: Node2D
|
var match3_scene: Node2D
|
||||||
@@ -16,8 +17,10 @@ var search_timer: Timer
|
|||||||
var last_scene_search_time: float = 0.0
|
var last_scene_search_time: float = 0.0
|
||||||
|
|
||||||
@onready var regenerate_button: Button = $VBoxContainer/RegenerateButton
|
@onready var regenerate_button: Button = $VBoxContainer/RegenerateButton
|
||||||
@onready var gem_types_spinbox: SpinBox = $VBoxContainer/GemTypesContainer/GemTypesSpinBox
|
@onready
|
||||||
@onready var gem_types_label: Label = $VBoxContainer/GemTypesContainer/GemTypesLabel
|
var gem_types_spinbox: SpinBox = $VBoxContainer/GemTypesContainer/GemTypesSpinBox
|
||||||
|
@onready
|
||||||
|
var gem_types_label: Label = $VBoxContainer/GemTypesContainer/GemTypesLabel
|
||||||
@onready
|
@onready
|
||||||
var grid_width_spinbox: SpinBox = $VBoxContainer/GridSizeContainer/GridWidthContainer/GridWidthSpinBox
|
var grid_width_spinbox: SpinBox = $VBoxContainer/GridSizeContainer/GridWidthContainer/GridWidthSpinBox
|
||||||
@onready
|
@onready
|
||||||
@@ -86,7 +89,9 @@ func _setup_scene_finding() -> void:
|
|||||||
|
|
||||||
# Virtual method - override in derived classes for specific finding logic
|
# Virtual method - override in derived classes for specific finding logic
|
||||||
func _find_target_scene() -> void:
|
func _find_target_scene() -> void:
|
||||||
DebugManager.log_error("_find_target_scene() not implemented in derived class", log_category)
|
DebugManager.log_error(
|
||||||
|
"_find_target_scene() not implemented in derived class", log_category
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _find_node_by_script(node: Node, script_path: String) -> Node:
|
func _find_node_by_script(node: Node, script_path: String) -> Node:
|
||||||
@@ -114,10 +119,14 @@ func _update_ui_from_scene() -> void:
|
|||||||
# Connect to grid state loaded signal if not already connected
|
# Connect to grid state loaded signal if not already connected
|
||||||
if (
|
if (
|
||||||
match3_scene.has_signal("grid_state_loaded")
|
match3_scene.has_signal("grid_state_loaded")
|
||||||
and not match3_scene.grid_state_loaded.is_connected(_on_grid_state_loaded)
|
and not match3_scene.grid_state_loaded.is_connected(
|
||||||
|
_on_grid_state_loaded
|
||||||
|
)
|
||||||
):
|
):
|
||||||
match3_scene.grid_state_loaded.connect(_on_grid_state_loaded)
|
match3_scene.grid_state_loaded.connect(_on_grid_state_loaded)
|
||||||
DebugManager.log_debug("Connected to grid_state_loaded signal", log_category)
|
DebugManager.log_debug(
|
||||||
|
"Connected to grid_state_loaded signal", log_category
|
||||||
|
)
|
||||||
|
|
||||||
# Update gem types display
|
# Update gem types display
|
||||||
if match3_scene.has_method("get") and "TILE_TYPES" in match3_scene:
|
if match3_scene.has_method("get") and "TILE_TYPES" in match3_scene:
|
||||||
@@ -135,7 +144,10 @@ func _update_ui_from_scene() -> void:
|
|||||||
|
|
||||||
func _on_grid_state_loaded(grid_size: Vector2i, tile_types: int) -> void:
|
func _on_grid_state_loaded(grid_size: Vector2i, tile_types: int) -> void:
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Grid state loaded signal received: size=%s, types=%d" % [grid_size, tile_types],
|
(
|
||||||
|
"Grid state loaded signal received: size=%s, types=%d"
|
||||||
|
% [grid_size, tile_types]
|
||||||
|
),
|
||||||
log_category
|
log_category
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -155,7 +167,10 @@ func _stop_search_timer() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _start_search_timer() -> void:
|
func _start_search_timer() -> void:
|
||||||
if search_timer and not search_timer.timeout.is_connected(_find_target_scene):
|
if (
|
||||||
|
search_timer
|
||||||
|
and not search_timer.timeout.is_connected(_find_target_scene)
|
||||||
|
):
|
||||||
search_timer.timeout.connect(_find_target_scene)
|
search_timer.timeout.connect(_find_target_scene)
|
||||||
search_timer.start()
|
search_timer.start()
|
||||||
|
|
||||||
@@ -176,7 +191,8 @@ func _refresh_current_values() -> void:
|
|||||||
# Refresh UI with current values from the scene
|
# Refresh UI with current values from the scene
|
||||||
if match3_scene:
|
if match3_scene:
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Refreshing debug menu values from current scene state", log_category
|
"Refreshing debug menu values from current scene state",
|
||||||
|
log_category
|
||||||
)
|
)
|
||||||
_update_ui_from_scene()
|
_update_ui_from_scene()
|
||||||
|
|
||||||
@@ -186,14 +202,18 @@ func _on_regenerate_pressed() -> void:
|
|||||||
_find_target_scene()
|
_find_target_scene()
|
||||||
|
|
||||||
if not match3_scene:
|
if not match3_scene:
|
||||||
DebugManager.log_error("Could not find target scene for regeneration", log_category)
|
DebugManager.log_error(
|
||||||
|
"Could not find target scene for regeneration", log_category
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if match3_scene.has_method("regenerate_grid"):
|
if match3_scene.has_method("regenerate_grid"):
|
||||||
DebugManager.log_debug("Calling regenerate_grid()", log_category)
|
DebugManager.log_debug("Calling regenerate_grid()", log_category)
|
||||||
await match3_scene.regenerate_grid()
|
await match3_scene.regenerate_grid()
|
||||||
else:
|
else:
|
||||||
DebugManager.log_error("Target scene does not have regenerate_grid method", log_category)
|
DebugManager.log_error(
|
||||||
|
"Target scene does not have regenerate_grid method", log_category
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _on_gem_types_changed(value: float) -> void:
|
func _on_gem_types_changed(value: float) -> void:
|
||||||
@@ -207,7 +227,9 @@ func _on_gem_types_changed(value: float) -> void:
|
|||||||
last_scene_search_time = current_time
|
last_scene_search_time = current_time
|
||||||
|
|
||||||
if not match3_scene:
|
if not match3_scene:
|
||||||
DebugManager.log_error("Could not find target scene for gem types change", log_category)
|
DebugManager.log_error(
|
||||||
|
"Could not find target scene for gem types change", log_category
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
var new_value: int = int(value)
|
var new_value: int = int(value)
|
||||||
@@ -221,15 +243,21 @@ func _on_gem_types_changed(value: float) -> void:
|
|||||||
log_category
|
log_category
|
||||||
)
|
)
|
||||||
# Reset to valid value
|
# Reset to valid value
|
||||||
gem_types_spinbox.value = clamp(new_value, MIN_TILE_TYPES, MAX_TILE_TYPES)
|
gem_types_spinbox.value = clamp(
|
||||||
|
new_value, MIN_TILE_TYPES, MAX_TILE_TYPES
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if match3_scene.has_method("set_tile_types"):
|
if match3_scene.has_method("set_tile_types"):
|
||||||
DebugManager.log_debug("Setting tile types to " + str(new_value), log_category)
|
DebugManager.log_debug(
|
||||||
|
"Setting tile types to " + str(new_value), log_category
|
||||||
|
)
|
||||||
await match3_scene.set_tile_types(new_value)
|
await match3_scene.set_tile_types(new_value)
|
||||||
gem_types_label.text = "Gem Types: " + str(new_value)
|
gem_types_label.text = "Gem Types: " + str(new_value)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_error("Target scene does not have set_tile_types method", log_category)
|
DebugManager.log_error(
|
||||||
|
"Target scene does not have set_tile_types method", log_category
|
||||||
|
)
|
||||||
# Fallback: try to set TILE_TYPES directly
|
# Fallback: try to set TILE_TYPES directly
|
||||||
if "TILE_TYPES" in match3_scene:
|
if "TILE_TYPES" in match3_scene:
|
||||||
match3_scene.TILE_TYPES = new_value
|
match3_scene.TILE_TYPES = new_value
|
||||||
@@ -247,7 +275,9 @@ func _on_grid_width_changed(value: float) -> void:
|
|||||||
last_scene_search_time = current_time
|
last_scene_search_time = current_time
|
||||||
|
|
||||||
if not match3_scene:
|
if not match3_scene:
|
||||||
DebugManager.log_error("Could not find target scene for grid width change", log_category)
|
DebugManager.log_error(
|
||||||
|
"Could not find target scene for grid width change", log_category
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
var new_width: int = int(value)
|
var new_width: int = int(value)
|
||||||
@@ -261,7 +291,9 @@ func _on_grid_width_changed(value: float) -> void:
|
|||||||
log_category
|
log_category
|
||||||
)
|
)
|
||||||
# Reset to valid value
|
# Reset to valid value
|
||||||
grid_width_spinbox.value = clamp(new_width, MIN_GRID_SIZE, MAX_GRID_SIZE)
|
grid_width_spinbox.value = clamp(
|
||||||
|
new_width, MIN_GRID_SIZE, MAX_GRID_SIZE
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
grid_width_label.text = "Width: " + str(new_width)
|
grid_width_label.text = "Width: " + str(new_width)
|
||||||
@@ -271,11 +303,19 @@ func _on_grid_width_changed(value: float) -> void:
|
|||||||
|
|
||||||
if match3_scene.has_method("set_grid_size"):
|
if match3_scene.has_method("set_grid_size"):
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Setting grid size to " + str(new_width) + "x" + str(current_height), log_category
|
(
|
||||||
|
"Setting grid size to "
|
||||||
|
+ str(new_width)
|
||||||
|
+ "x"
|
||||||
|
+ str(current_height)
|
||||||
|
),
|
||||||
|
log_category
|
||||||
)
|
)
|
||||||
await match3_scene.set_grid_size(Vector2i(new_width, current_height))
|
await match3_scene.set_grid_size(Vector2i(new_width, current_height))
|
||||||
else:
|
else:
|
||||||
DebugManager.log_error("Target scene does not have set_grid_size method", log_category)
|
DebugManager.log_error(
|
||||||
|
"Target scene does not have set_grid_size method", log_category
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _on_grid_height_changed(value: float) -> void:
|
func _on_grid_height_changed(value: float) -> void:
|
||||||
@@ -289,7 +329,9 @@ func _on_grid_height_changed(value: float) -> void:
|
|||||||
last_scene_search_time = current_time
|
last_scene_search_time = current_time
|
||||||
|
|
||||||
if not match3_scene:
|
if not match3_scene:
|
||||||
DebugManager.log_error("Could not find target scene for grid height change", log_category)
|
DebugManager.log_error(
|
||||||
|
"Could not find target scene for grid height change", log_category
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
var new_height: int = int(value)
|
var new_height: int = int(value)
|
||||||
@@ -303,7 +345,9 @@ func _on_grid_height_changed(value: float) -> void:
|
|||||||
log_category
|
log_category
|
||||||
)
|
)
|
||||||
# Reset to valid value
|
# Reset to valid value
|
||||||
grid_height_spinbox.value = clamp(new_height, MIN_GRID_SIZE, MAX_GRID_SIZE)
|
grid_height_spinbox.value = clamp(
|
||||||
|
new_height, MIN_GRID_SIZE, MAX_GRID_SIZE
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
grid_height_label.text = "Height: " + str(new_height)
|
grid_height_label.text = "Height: " + str(new_height)
|
||||||
@@ -313,8 +357,16 @@ func _on_grid_height_changed(value: float) -> void:
|
|||||||
|
|
||||||
if match3_scene.has_method("set_grid_size"):
|
if match3_scene.has_method("set_grid_size"):
|
||||||
DebugManager.log_debug(
|
DebugManager.log_debug(
|
||||||
"Setting grid size to " + str(current_width) + "x" + str(new_height), log_category
|
(
|
||||||
|
"Setting grid size to "
|
||||||
|
+ str(current_width)
|
||||||
|
+ "x"
|
||||||
|
+ str(new_height)
|
||||||
|
),
|
||||||
|
log_category
|
||||||
)
|
)
|
||||||
await match3_scene.set_grid_size(Vector2i(current_width, new_height))
|
await match3_scene.set_grid_size(Vector2i(current_width, new_height))
|
||||||
else:
|
else:
|
||||||
DebugManager.log_error("Target scene does not have set_grid_size method", log_category)
|
DebugManager.log_error(
|
||||||
|
"Target scene does not have set_grid_size method", log_category
|
||||||
|
)
|
||||||
|
|||||||
@@ -81,13 +81,17 @@ func _navigate_menu(direction: int) -> void:
|
|||||||
if current_menu_index < 0:
|
if current_menu_index < 0:
|
||||||
current_menu_index = menu_buttons.size() - 1
|
current_menu_index = menu_buttons.size() - 1
|
||||||
_update_visual_selection()
|
_update_visual_selection()
|
||||||
DebugManager.log_info("Menu navigation: index " + str(current_menu_index), "MainMenu")
|
DebugManager.log_info(
|
||||||
|
"Menu navigation: index " + str(current_menu_index), "MainMenu"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _activate_current_button() -> void:
|
func _activate_current_button() -> void:
|
||||||
if current_menu_index >= 0 and current_menu_index < menu_buttons.size():
|
if current_menu_index >= 0 and current_menu_index < menu_buttons.size():
|
||||||
var button: Button = menu_buttons[current_menu_index]
|
var button: Button = menu_buttons[current_menu_index]
|
||||||
DebugManager.log_info("Activating button via keyboard/gamepad: " + button.text, "MainMenu")
|
DebugManager.log_info(
|
||||||
|
"Activating button via keyboard/gamepad: " + button.text, "MainMenu"
|
||||||
|
)
|
||||||
button.pressed.emit()
|
button.pressed.emit()
|
||||||
|
|
||||||
|
|
||||||
@@ -95,7 +99,9 @@ func _update_visual_selection() -> void:
|
|||||||
for i in range(menu_buttons.size()):
|
for i in range(menu_buttons.size()):
|
||||||
var button: Button = menu_buttons[i]
|
var button: Button = menu_buttons[i]
|
||||||
if i == current_menu_index:
|
if i == current_menu_index:
|
||||||
button.scale = (original_button_scales[i] * UIConstants.BUTTON_HOVER_SCALE)
|
button.scale = (
|
||||||
|
original_button_scales[i] * UIConstants.BUTTON_HOVER_SCALE
|
||||||
|
)
|
||||||
button.modulate = Color(1.2, 1.2, 1.0)
|
button.modulate = Color(1.2, 1.2, 1.0)
|
||||||
else:
|
else:
|
||||||
button.scale = original_button_scales[i]
|
button.scale = original_button_scales[i]
|
||||||
|
|||||||
@@ -14,10 +14,13 @@ var current_control_index: int = 0
|
|||||||
var original_control_scales: Array[Vector2] = []
|
var original_control_scales: Array[Vector2] = []
|
||||||
var original_control_modulates: Array[Color] = []
|
var original_control_modulates: Array[Color] = []
|
||||||
|
|
||||||
@onready var master_slider = $SettingsContainer/MasterVolumeContainer/MasterVolumeSlider
|
@onready
|
||||||
@onready var music_slider = $SettingsContainer/MusicVolumeContainer/MusicVolumeSlider
|
var master_slider = $SettingsContainer/MasterVolumeContainer/MasterVolumeSlider
|
||||||
|
@onready
|
||||||
|
var music_slider = $SettingsContainer/MusicVolumeContainer/MusicVolumeSlider
|
||||||
@onready var sfx_slider = $SettingsContainer/SFXVolumeContainer/SFXVolumeSlider
|
@onready var sfx_slider = $SettingsContainer/SFXVolumeContainer/SFXVolumeSlider
|
||||||
@onready var language_stepper = $SettingsContainer/LanguageContainer/LanguageStepper
|
@onready
|
||||||
|
var language_stepper = $SettingsContainer/LanguageContainer/LanguageStepper
|
||||||
@onready var reset_progress_button = $ResetSettingsContainer/ResetProgressButton
|
@onready var reset_progress_button = $ResetSettingsContainer/ResetProgressButton
|
||||||
|
|
||||||
|
|
||||||
@@ -26,11 +29,15 @@ func _ready() -> void:
|
|||||||
DebugManager.log_info("SettingsMenu ready", "Settings")
|
DebugManager.log_info("SettingsMenu ready", "Settings")
|
||||||
# Language selector is initialized automatically
|
# Language selector is initialized automatically
|
||||||
|
|
||||||
var master_callback: Callable = _on_volume_slider_changed.bind("master_volume")
|
var master_callback: Callable = _on_volume_slider_changed.bind(
|
||||||
|
"master_volume"
|
||||||
|
)
|
||||||
if not master_slider.value_changed.is_connected(master_callback):
|
if not master_slider.value_changed.is_connected(master_callback):
|
||||||
master_slider.value_changed.connect(master_callback)
|
master_slider.value_changed.connect(master_callback)
|
||||||
|
|
||||||
var music_callback: Callable = _on_volume_slider_changed.bind("music_volume")
|
var music_callback: Callable = _on_volume_slider_changed.bind(
|
||||||
|
"music_volume"
|
||||||
|
)
|
||||||
if not music_slider.value_changed.is_connected(music_callback):
|
if not music_slider.value_changed.is_connected(music_callback):
|
||||||
music_slider.value_changed.connect(music_callback)
|
music_slider.value_changed.connect(music_callback)
|
||||||
|
|
||||||
@@ -57,20 +64,28 @@ func _update_controls_from_settings() -> void:
|
|||||||
func _on_volume_slider_changed(value: float, setting_key: String) -> void:
|
func _on_volume_slider_changed(value: float, setting_key: String) -> void:
|
||||||
# Input validation for volume settings
|
# Input validation for volume settings
|
||||||
if not setting_key in ["master_volume", "music_volume", "sfx_volume"]:
|
if not setting_key in ["master_volume", "music_volume", "sfx_volume"]:
|
||||||
DebugManager.log_error("Invalid volume setting key: " + str(setting_key), "Settings")
|
DebugManager.log_error(
|
||||||
|
"Invalid volume setting key: " + str(setting_key), "Settings"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if typeof(value) != TYPE_FLOAT and typeof(value) != TYPE_INT:
|
if typeof(value) != TYPE_FLOAT and typeof(value) != TYPE_INT:
|
||||||
DebugManager.log_error("Invalid volume value type: " + str(typeof(value)), "Settings")
|
DebugManager.log_error(
|
||||||
|
"Invalid volume value type: " + str(typeof(value)), "Settings"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Clamp value to valid range
|
# Clamp value to valid range
|
||||||
var clamped_value: float = clamp(float(value), 0.0, 1.0)
|
var clamped_value: float = clamp(float(value), 0.0, 1.0)
|
||||||
if clamped_value != value:
|
if clamped_value != value:
|
||||||
DebugManager.log_warn("Volume value %f clamped to %f" % [value, clamped_value], "Settings")
|
DebugManager.log_warn(
|
||||||
|
"Volume value %f clamped to %f" % [value, clamped_value], "Settings"
|
||||||
|
)
|
||||||
|
|
||||||
if not settings_manager.set_setting(setting_key, clamped_value):
|
if not settings_manager.set_setting(setting_key, clamped_value):
|
||||||
DebugManager.log_error("Failed to set volume setting: " + setting_key, "Settings")
|
DebugManager.log_error(
|
||||||
|
"Failed to set volume setting: " + setting_key, "Settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _exit_settings() -> void:
|
func _exit_settings() -> void:
|
||||||
@@ -80,8 +95,13 @@ func _exit_settings() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
if event.is_action_pressed("action_east") or event.is_action_pressed("pause_menu"):
|
if (
|
||||||
DebugManager.log_debug("Cancel/back action pressed in settings", "Settings")
|
event.is_action_pressed("action_east")
|
||||||
|
or event.is_action_pressed("pause_menu")
|
||||||
|
):
|
||||||
|
DebugManager.log_debug(
|
||||||
|
"Cancel/back action pressed in settings", "Settings"
|
||||||
|
)
|
||||||
_exit_settings()
|
_exit_settings()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
return
|
return
|
||||||
@@ -116,8 +136,12 @@ func _on_back_button_pressed() -> void:
|
|||||||
|
|
||||||
func update_text() -> void:
|
func update_text() -> void:
|
||||||
$SettingsContainer/SettingsTitle.text = tr("settings_title")
|
$SettingsContainer/SettingsTitle.text = tr("settings_title")
|
||||||
$SettingsContainer/MasterVolumeContainer/MasterVolume.text = tr("master_volume")
|
$SettingsContainer/MasterVolumeContainer/MasterVolume.text = tr(
|
||||||
$SettingsContainer/MusicVolumeContainer/MusicVolume.text = tr("music_volume")
|
"master_volume"
|
||||||
|
)
|
||||||
|
$SettingsContainer/MusicVolumeContainer/MusicVolume.text = tr(
|
||||||
|
"music_volume"
|
||||||
|
)
|
||||||
$SettingsContainer/SFXVolumeContainer/SFXVolume.text = tr("sfx_volume")
|
$SettingsContainer/SFXVolumeContainer/SFXVolume.text = tr("sfx_volume")
|
||||||
$SettingsContainer/LanguageContainer/LanguageLabel.text = tr("language")
|
$SettingsContainer/LanguageContainer/LanguageLabel.text = tr("language")
|
||||||
$BackButtonContainer/BackButton.text = tr("back")
|
$BackButtonContainer/BackButton.text = tr("back")
|
||||||
@@ -129,7 +153,9 @@ func _on_reset_setting_button_pressed() -> void:
|
|||||||
DebugManager.log_info("Resetting settings", "Settings")
|
DebugManager.log_info("Resetting settings", "Settings")
|
||||||
settings_manager.reset_settings_to_defaults()
|
settings_manager.reset_settings_to_defaults()
|
||||||
_update_controls_from_settings()
|
_update_controls_from_settings()
|
||||||
localization_manager.change_language(settings_manager.get_setting("language"))
|
localization_manager.change_language(
|
||||||
|
settings_manager.get_setting("language")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _setup_navigation_system() -> void:
|
func _setup_navigation_system() -> void:
|
||||||
@@ -156,15 +182,22 @@ func _setup_navigation_system() -> void:
|
|||||||
|
|
||||||
func _navigate_controls(direction: int) -> void:
|
func _navigate_controls(direction: int) -> void:
|
||||||
AudioManager.play_ui_click()
|
AudioManager.play_ui_click()
|
||||||
current_control_index = ((current_control_index + direction) % navigable_controls.size())
|
current_control_index = (
|
||||||
|
(current_control_index + direction) % navigable_controls.size()
|
||||||
|
)
|
||||||
if current_control_index < 0:
|
if current_control_index < 0:
|
||||||
current_control_index = navigable_controls.size() - 1
|
current_control_index = navigable_controls.size() - 1
|
||||||
_update_visual_selection()
|
_update_visual_selection()
|
||||||
DebugManager.log_info("Settings navigation: index " + str(current_control_index), "Settings")
|
DebugManager.log_info(
|
||||||
|
"Settings navigation: index " + str(current_control_index), "Settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _adjust_current_control(direction: int) -> void:
|
func _adjust_current_control(direction: int) -> void:
|
||||||
if current_control_index < 0 or current_control_index >= navigable_controls.size():
|
if (
|
||||||
|
current_control_index < 0
|
||||||
|
or current_control_index >= navigable_controls.size()
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
var control: Control = navigable_controls[current_control_index]
|
var control: Control = navigable_controls[current_control_index]
|
||||||
@@ -178,17 +211,26 @@ func _adjust_current_control(direction: int) -> void:
|
|||||||
slider.value = new_value
|
slider.value = new_value
|
||||||
AudioManager.play_ui_click()
|
AudioManager.play_ui_click()
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Slider adjusted: %s = %f" % [_get_control_name(control), new_value], "Settings"
|
(
|
||||||
|
"Slider adjusted: %s = %f"
|
||||||
|
% [_get_control_name(control), new_value]
|
||||||
|
),
|
||||||
|
"Settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle language stepper with left/right
|
# Handle language stepper with left/right
|
||||||
elif control == language_stepper:
|
elif control == language_stepper:
|
||||||
if language_stepper.handle_input_action("move_left" if direction == -1 else "move_right"):
|
if language_stepper.handle_input_action(
|
||||||
|
"move_left" if direction == -1 else "move_right"
|
||||||
|
):
|
||||||
AudioManager.play_ui_click()
|
AudioManager.play_ui_click()
|
||||||
|
|
||||||
|
|
||||||
func _activate_current_control() -> void:
|
func _activate_current_control() -> void:
|
||||||
if current_control_index < 0 or current_control_index >= navigable_controls.size():
|
if (
|
||||||
|
current_control_index < 0
|
||||||
|
or current_control_index >= navigable_controls.size()
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
var control: Control = navigable_controls[current_control_index]
|
var control: Control = navigable_controls[current_control_index]
|
||||||
@@ -196,12 +238,17 @@ func _activate_current_control() -> void:
|
|||||||
# Handle buttons
|
# Handle buttons
|
||||||
if control is Button:
|
if control is Button:
|
||||||
AudioManager.play_ui_click()
|
AudioManager.play_ui_click()
|
||||||
DebugManager.log_info("Activating button via keyboard/gamepad: " + control.text, "Settings")
|
DebugManager.log_info(
|
||||||
|
"Activating button via keyboard/gamepad: " + control.text,
|
||||||
|
"Settings"
|
||||||
|
)
|
||||||
control.pressed.emit()
|
control.pressed.emit()
|
||||||
|
|
||||||
# Handle language stepper (no action needed on activation, left/right handles it)
|
# Handle language stepper (no action needed on activation, left/right handles it)
|
||||||
elif control == language_stepper:
|
elif control == language_stepper:
|
||||||
DebugManager.log_info("Language stepper selected - use left/right to change", "Settings")
|
DebugManager.log_info(
|
||||||
|
"Language stepper selected - use left/right to change", "Settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _update_visual_selection() -> void:
|
func _update_visual_selection() -> void:
|
||||||
@@ -213,7 +260,8 @@ func _update_visual_selection() -> void:
|
|||||||
language_stepper.set_highlighted(true)
|
language_stepper.set_highlighted(true)
|
||||||
else:
|
else:
|
||||||
control.scale = (
|
control.scale = (
|
||||||
original_control_scales[i] * UIConstants.UI_CONTROL_HIGHLIGHT_SCALE
|
original_control_scales[i]
|
||||||
|
* UIConstants.UI_CONTROL_HIGHLIGHT_SCALE
|
||||||
)
|
)
|
||||||
control.modulate = Color(1.1, 1.1, 0.9)
|
control.modulate = Color(1.1, 1.1, 0.9)
|
||||||
else:
|
else:
|
||||||
@@ -237,9 +285,17 @@ func _get_control_name(control: Control) -> String:
|
|||||||
return "button"
|
return "button"
|
||||||
|
|
||||||
|
|
||||||
func _on_language_stepper_value_changed(new_value: String, new_index: float) -> void:
|
func _on_language_stepper_value_changed(
|
||||||
|
new_value: String, new_index: float
|
||||||
|
) -> void:
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Language changed via ValueStepper: " + new_value + " (index: " + str(int(new_index)) + ")",
|
(
|
||||||
|
"Language changed via ValueStepper: "
|
||||||
|
+ new_value
|
||||||
|
+ " (index: "
|
||||||
|
+ str(int(new_index))
|
||||||
|
+ ")"
|
||||||
|
),
|
||||||
"Settings"
|
"Settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -30,17 +30,25 @@ var is_highlighted: bool = false
|
|||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
DebugManager.log_info("ValueStepper ready for: " + data_source, "ValueStepper")
|
DebugManager.log_info(
|
||||||
|
"ValueStepper ready for: " + data_source, "ValueStepper"
|
||||||
|
)
|
||||||
|
|
||||||
# Store original visual properties
|
# Store original visual properties
|
||||||
original_scale = scale
|
original_scale = scale
|
||||||
original_modulate = modulate
|
original_modulate = modulate
|
||||||
|
|
||||||
# Connect button signals
|
# Connect button signals
|
||||||
if left_button and not left_button.pressed.is_connected(_on_left_button_pressed):
|
if (
|
||||||
|
left_button
|
||||||
|
and not left_button.pressed.is_connected(_on_left_button_pressed)
|
||||||
|
):
|
||||||
left_button.pressed.connect(_on_left_button_pressed)
|
left_button.pressed.connect(_on_left_button_pressed)
|
||||||
|
|
||||||
if right_button and not right_button.pressed.is_connected(_on_right_button_pressed):
|
if (
|
||||||
|
right_button
|
||||||
|
and not right_button.pressed.is_connected(_on_right_button_pressed)
|
||||||
|
):
|
||||||
right_button.pressed.connect(_on_right_button_pressed)
|
right_button.pressed.connect(_on_right_button_pressed)
|
||||||
|
|
||||||
# Initialize data
|
# Initialize data
|
||||||
@@ -58,7 +66,9 @@ func _load_data() -> void:
|
|||||||
"difficulty":
|
"difficulty":
|
||||||
_load_difficulty_data()
|
_load_difficulty_data()
|
||||||
_:
|
_:
|
||||||
DebugManager.log_warn("Unknown data_source: " + data_source, "ValueStepper")
|
DebugManager.log_warn(
|
||||||
|
"Unknown data_source: " + data_source, "ValueStepper"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _load_language_data() -> void:
|
func _load_language_data() -> void:
|
||||||
@@ -68,22 +78,30 @@ func _load_language_data() -> void:
|
|||||||
display_names.clear()
|
display_names.clear()
|
||||||
for lang_code in languages_data.languages.keys():
|
for lang_code in languages_data.languages.keys():
|
||||||
values.append(lang_code)
|
values.append(lang_code)
|
||||||
display_names.append(languages_data.languages[lang_code]["display_name"])
|
display_names.append(
|
||||||
|
languages_data.languages[lang_code]["display_name"]
|
||||||
|
)
|
||||||
|
|
||||||
# Set current index based on current language
|
# Set current index based on current language
|
||||||
var current_lang: String = SettingsManager.get_setting("language")
|
var current_lang: String = SettingsManager.get_setting("language")
|
||||||
var index: int = values.find(current_lang)
|
var index: int = values.find(current_lang)
|
||||||
current_index = max(0, index)
|
current_index = max(0, index)
|
||||||
|
|
||||||
DebugManager.log_info("Loaded %d languages" % values.size(), "ValueStepper")
|
DebugManager.log_info(
|
||||||
|
"Loaded %d languages" % values.size(), "ValueStepper"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _load_resolution_data() -> void:
|
func _load_resolution_data() -> void:
|
||||||
# Example resolution data - customize as needed
|
# Example resolution data - customize as needed
|
||||||
values = ["1920x1080", "1366x768", "1280x720", "1024x768"]
|
values = ["1920x1080", "1366x768", "1280x720", "1024x768"]
|
||||||
display_names = ["1920×1080 (Full HD)", "1366×768", "1280×720 (HD)", "1024×768"]
|
display_names = [
|
||||||
|
"1920×1080 (Full HD)", "1366×768", "1280×720 (HD)", "1024×768"
|
||||||
|
]
|
||||||
current_index = 0
|
current_index = 0
|
||||||
DebugManager.log_info("Loaded %d resolutions" % values.size(), "ValueStepper")
|
DebugManager.log_info(
|
||||||
|
"Loaded %d resolutions" % values.size(), "ValueStepper"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _load_difficulty_data() -> void:
|
func _load_difficulty_data() -> void:
|
||||||
@@ -91,12 +109,18 @@ func _load_difficulty_data() -> void:
|
|||||||
values = ["easy", "normal", "hard", "nightmare"]
|
values = ["easy", "normal", "hard", "nightmare"]
|
||||||
display_names = ["Easy", "Normal", "Hard", "Nightmare"]
|
display_names = ["Easy", "Normal", "Hard", "Nightmare"]
|
||||||
current_index = 1 # Default to "normal"
|
current_index = 1 # Default to "normal"
|
||||||
DebugManager.log_info("Loaded %d difficulty levels" % values.size(), "ValueStepper")
|
DebugManager.log_info(
|
||||||
|
"Loaded %d difficulty levels" % values.size(), "ValueStepper"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
## Updates the display text based on current selection
|
## Updates the display text based on current selection
|
||||||
func _update_display() -> void:
|
func _update_display() -> void:
|
||||||
if values.size() == 0 or current_index < 0 or current_index >= values.size():
|
if (
|
||||||
|
values.size() == 0
|
||||||
|
or current_index < 0
|
||||||
|
or current_index >= values.size()
|
||||||
|
):
|
||||||
value_display.text = "N/A"
|
value_display.text = "N/A"
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -109,7 +133,9 @@ func _update_display() -> void:
|
|||||||
## Changes the current value by the specified direction (-1 for previous, +1 for next)
|
## Changes the current value by the specified direction (-1 for previous, +1 for next)
|
||||||
func change_value(direction: int) -> void:
|
func change_value(direction: int) -> void:
|
||||||
if values.size() == 0:
|
if values.size() == 0:
|
||||||
DebugManager.log_warn("No values available for: " + data_source, "ValueStepper")
|
DebugManager.log_warn(
|
||||||
|
"No values available for: " + data_source, "ValueStepper"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
var new_index: int = (current_index + direction) % values.size()
|
var new_index: int = (current_index + direction) % values.size()
|
||||||
@@ -123,7 +149,14 @@ func change_value(direction: int) -> void:
|
|||||||
_apply_value_change(new_value, current_index)
|
_apply_value_change(new_value, current_index)
|
||||||
value_changed.emit(new_value, current_index)
|
value_changed.emit(new_value, current_index)
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Value changed to: " + new_value + " (index: " + str(current_index) + ")", "ValueStepper"
|
(
|
||||||
|
"Value changed to: "
|
||||||
|
+ new_value
|
||||||
|
+ " (index: "
|
||||||
|
+ str(current_index)
|
||||||
|
+ ")"
|
||||||
|
),
|
||||||
|
"ValueStepper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -136,10 +169,14 @@ func _apply_value_change(new_value: String, _index: int) -> void:
|
|||||||
LocalizationManager.change_language(new_value)
|
LocalizationManager.change_language(new_value)
|
||||||
"resolution":
|
"resolution":
|
||||||
# Apply resolution change logic here
|
# Apply resolution change logic here
|
||||||
DebugManager.log_info("Resolution would change to: " + new_value, "ValueStepper")
|
DebugManager.log_info(
|
||||||
|
"Resolution would change to: " + new_value, "ValueStepper"
|
||||||
|
)
|
||||||
"difficulty":
|
"difficulty":
|
||||||
# Apply difficulty change logic here
|
# Apply difficulty change logic here
|
||||||
DebugManager.log_info("Difficulty would change to: " + new_value, "ValueStepper")
|
DebugManager.log_info(
|
||||||
|
"Difficulty would change to: " + new_value, "ValueStepper"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
## Sets up custom values for the stepper
|
## Sets up custom values for the stepper
|
||||||
@@ -148,16 +185,24 @@ func setup_custom_values(
|
|||||||
) -> void:
|
) -> void:
|
||||||
values = custom_values.duplicate()
|
values = custom_values.duplicate()
|
||||||
display_names = (
|
display_names = (
|
||||||
custom_display_names.duplicate() if custom_display_names.size() > 0 else values.duplicate()
|
custom_display_names.duplicate()
|
||||||
|
if custom_display_names.size() > 0
|
||||||
|
else values.duplicate()
|
||||||
)
|
)
|
||||||
current_index = 0
|
current_index = 0
|
||||||
_update_display()
|
_update_display()
|
||||||
DebugManager.log_info("Setup custom values: " + str(values.size()) + " items", "ValueStepper")
|
DebugManager.log_info(
|
||||||
|
"Setup custom values: " + str(values.size()) + " items", "ValueStepper"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
## Gets the current value
|
## Gets the current value
|
||||||
func get_current_value() -> String:
|
func get_current_value() -> String:
|
||||||
if values.size() > 0 and current_index >= 0 and current_index < values.size():
|
if (
|
||||||
|
values.size() > 0
|
||||||
|
and current_index >= 0
|
||||||
|
and current_index < values.size()
|
||||||
|
):
|
||||||
return values[current_index]
|
return values[current_index]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ func _ready():
|
|||||||
|
|
||||||
var orig_stream = _load_stream()
|
var orig_stream = _load_stream()
|
||||||
if not orig_stream:
|
if not orig_stream:
|
||||||
DebugManager.log_error("Failed to load music stream: %s" % MUSIC_PATH, "AudioManager")
|
DebugManager.log_error(
|
||||||
|
"Failed to load music stream: %s" % MUSIC_PATH, "AudioManager"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
var stream = orig_stream.duplicate(true) as AudioStream
|
var stream = orig_stream.duplicate(true) as AudioStream
|
||||||
@@ -52,7 +54,9 @@ func _configure_stream_loop(stream: AudioStream) -> void:
|
|||||||
|
|
||||||
func _configure_audio_bus() -> void:
|
func _configure_audio_bus() -> void:
|
||||||
music_player.bus = "Music"
|
music_player.bus = "Music"
|
||||||
music_player.volume_db = linear_to_db(SettingsManager.get_setting("music_volume"))
|
music_player.volume_db = linear_to_db(
|
||||||
|
SettingsManager.get_setting("music_volume")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func update_music_volume(volume: float) -> void:
|
func update_music_volume(volume: float) -> void:
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ func _log_level_to_string(level: LogLevel) -> String:
|
|||||||
return level_strings.get(level, "UNKNOWN")
|
return level_strings.get(level, "UNKNOWN")
|
||||||
|
|
||||||
|
|
||||||
func _format_log_message(level: LogLevel, message: String, category: String = "") -> String:
|
func _format_log_message(
|
||||||
|
level: LogLevel, message: String, category: String = ""
|
||||||
|
) -> String:
|
||||||
"""Format log message with timestamp, level, category, and content"""
|
"""Format log message with timestamp, level, category, and content"""
|
||||||
var timestamp = Time.get_datetime_string_from_system()
|
var timestamp = Time.get_datetime_string_from_system()
|
||||||
var level_str = _log_level_to_string(level)
|
var level_str = _log_level_to_string(level)
|
||||||
|
|||||||
@@ -49,14 +49,17 @@ func start_game_with_mode(gameplay_mode: String) -> void:
|
|||||||
|
|
||||||
var packed_scene := load(GAME_SCENE_PATH)
|
var packed_scene := load(GAME_SCENE_PATH)
|
||||||
if not packed_scene or not packed_scene is PackedScene:
|
if not packed_scene or not packed_scene is PackedScene:
|
||||||
DebugManager.log_error("Failed to load Game scene at: %s" % GAME_SCENE_PATH, "GameManager")
|
DebugManager.log_error(
|
||||||
|
"Failed to load Game scene at: %s" % GAME_SCENE_PATH, "GameManager"
|
||||||
|
)
|
||||||
is_changing_scene = false
|
is_changing_scene = false
|
||||||
return
|
return
|
||||||
|
|
||||||
var result = get_tree().change_scene_to_packed(packed_scene)
|
var result = get_tree().change_scene_to_packed(packed_scene)
|
||||||
if result != OK:
|
if result != OK:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to change to game scene (Error code: %d)" % result, "GameManager"
|
"Failed to change to game scene (Error code: %d)" % result,
|
||||||
|
"GameManager"
|
||||||
)
|
)
|
||||||
is_changing_scene = false
|
is_changing_scene = false
|
||||||
return
|
return
|
||||||
@@ -67,22 +70,31 @@ func start_game_with_mode(gameplay_mode: String) -> void:
|
|||||||
|
|
||||||
# Validate scene was loaded successfully
|
# Validate scene was loaded successfully
|
||||||
if not get_tree().current_scene:
|
if not get_tree().current_scene:
|
||||||
DebugManager.log_error("Current scene is null after scene change", "GameManager")
|
DebugManager.log_error(
|
||||||
|
"Current scene is null after scene change", "GameManager"
|
||||||
|
)
|
||||||
is_changing_scene = false
|
is_changing_scene = false
|
||||||
return
|
return
|
||||||
|
|
||||||
# Configure game scene with requested gameplay mode
|
# Configure game scene with requested gameplay mode
|
||||||
if get_tree().current_scene.has_method("set_gameplay_mode"):
|
if get_tree().current_scene.has_method("set_gameplay_mode"):
|
||||||
DebugManager.log_info("Setting gameplay mode to: %s" % pending_gameplay_mode, "GameManager")
|
DebugManager.log_info(
|
||||||
|
"Setting gameplay mode to: %s" % pending_gameplay_mode,
|
||||||
|
"GameManager"
|
||||||
|
)
|
||||||
get_tree().current_scene.set_gameplay_mode(pending_gameplay_mode)
|
get_tree().current_scene.set_gameplay_mode(pending_gameplay_mode)
|
||||||
|
|
||||||
# Load saved score
|
# Load saved score
|
||||||
if get_tree().current_scene.has_method("set_global_score"):
|
if get_tree().current_scene.has_method("set_global_score"):
|
||||||
var saved_score = SaveManager.get_current_score()
|
var saved_score = SaveManager.get_current_score()
|
||||||
DebugManager.log_info("Loading saved score: %d" % saved_score, "GameManager")
|
DebugManager.log_info(
|
||||||
|
"Loading saved score: %d" % saved_score, "GameManager"
|
||||||
|
)
|
||||||
get_tree().current_scene.set_global_score(saved_score)
|
get_tree().current_scene.set_global_score(saved_score)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_error("Game scene does not have set_gameplay_mode method", "GameManager")
|
DebugManager.log_error(
|
||||||
|
"Game scene does not have set_gameplay_mode method", "GameManager"
|
||||||
|
)
|
||||||
|
|
||||||
is_changing_scene = false
|
is_changing_scene = false
|
||||||
|
|
||||||
@@ -91,11 +103,16 @@ func save_game() -> void:
|
|||||||
"""Save current game state and score via SaveManager"""
|
"""Save current game state and score via SaveManager"""
|
||||||
# Get current score from the active game scene
|
# Get current score from the active game scene
|
||||||
var current_score = 0
|
var current_score = 0
|
||||||
if get_tree().current_scene and get_tree().current_scene.has_method("get_global_score"):
|
if (
|
||||||
|
get_tree().current_scene
|
||||||
|
and get_tree().current_scene.has_method("get_global_score")
|
||||||
|
):
|
||||||
current_score = get_tree().current_scene.get_global_score()
|
current_score = get_tree().current_scene.get_global_score()
|
||||||
|
|
||||||
SaveManager.finish_game(current_score)
|
SaveManager.finish_game(current_score)
|
||||||
DebugManager.log_info("Game saved with score: %d" % current_score, "GameManager")
|
DebugManager.log_info(
|
||||||
|
"Game saved with score: %d" % current_score, "GameManager"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func show_credits() -> void:
|
func show_credits() -> void:
|
||||||
@@ -103,7 +120,8 @@ func show_credits() -> void:
|
|||||||
# Prevent concurrent scene changes
|
# Prevent concurrent scene changes
|
||||||
if is_changing_scene:
|
if is_changing_scene:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Scene change already in progress, ignoring show credits request", "GameManager"
|
"Scene change already in progress, ignoring show credits request",
|
||||||
|
"GameManager"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -113,7 +131,8 @@ func show_credits() -> void:
|
|||||||
var packed_scene := load(CREDITS_SCENE_PATH)
|
var packed_scene := load(CREDITS_SCENE_PATH)
|
||||||
if not packed_scene or not packed_scene is PackedScene:
|
if not packed_scene or not packed_scene is PackedScene:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to load Credits scene at: %s" % CREDITS_SCENE_PATH, "GameManager"
|
"Failed to load Credits scene at: %s" % CREDITS_SCENE_PATH,
|
||||||
|
"GameManager"
|
||||||
)
|
)
|
||||||
is_changing_scene = false
|
is_changing_scene = false
|
||||||
return
|
return
|
||||||
@@ -121,7 +140,8 @@ func show_credits() -> void:
|
|||||||
var result = get_tree().change_scene_to_packed(packed_scene)
|
var result = get_tree().change_scene_to_packed(packed_scene)
|
||||||
if result != OK:
|
if result != OK:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to change to credits scene (Error code: %d)" % result, "GameManager"
|
"Failed to change to credits scene (Error code: %d)" % result,
|
||||||
|
"GameManager"
|
||||||
)
|
)
|
||||||
is_changing_scene = false
|
is_changing_scene = false
|
||||||
return
|
return
|
||||||
@@ -137,8 +157,12 @@ func exit_to_main_menu() -> void:
|
|||||||
"""Exit to main menu with race condition protection"""
|
"""Exit to main menu with race condition protection"""
|
||||||
# Prevent concurrent scene changes
|
# Prevent concurrent scene changes
|
||||||
if is_changing_scene:
|
if is_changing_scene:
|
||||||
DebugManager.log_warn(
|
(
|
||||||
"Scene change already in progress, ignoring exit to main menu request", "GameManager"
|
DebugManager
|
||||||
|
. log_warn(
|
||||||
|
"Scene change already in progress, ignoring exit to main menu request",
|
||||||
|
"GameManager"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -147,14 +171,17 @@ func exit_to_main_menu() -> void:
|
|||||||
|
|
||||||
var packed_scene := load(MAIN_SCENE_PATH)
|
var packed_scene := load(MAIN_SCENE_PATH)
|
||||||
if not packed_scene or not packed_scene is PackedScene:
|
if not packed_scene or not packed_scene is PackedScene:
|
||||||
DebugManager.log_error("Failed to load Main scene at: %s" % MAIN_SCENE_PATH, "GameManager")
|
DebugManager.log_error(
|
||||||
|
"Failed to load Main scene at: %s" % MAIN_SCENE_PATH, "GameManager"
|
||||||
|
)
|
||||||
is_changing_scene = false
|
is_changing_scene = false
|
||||||
return
|
return
|
||||||
|
|
||||||
var result = get_tree().change_scene_to_packed(packed_scene)
|
var result = get_tree().change_scene_to_packed(packed_scene)
|
||||||
if result != OK:
|
if result != OK:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to change to main scene (Error code: %d)" % result, "GameManager"
|
"Failed to change to main scene (Error code: %d)" % result,
|
||||||
|
"GameManager"
|
||||||
)
|
)
|
||||||
is_changing_scene = false
|
is_changing_scene = false
|
||||||
return
|
return
|
||||||
@@ -170,25 +197,33 @@ func _validate_game_mode_request(gameplay_mode: String) -> bool:
|
|||||||
"""Validate gameplay mode request with combined checks"""
|
"""Validate gameplay mode request with combined checks"""
|
||||||
# Input validation
|
# Input validation
|
||||||
if not gameplay_mode or gameplay_mode.is_empty():
|
if not gameplay_mode or gameplay_mode.is_empty():
|
||||||
DebugManager.log_error("Empty or null gameplay mode provided", "GameManager")
|
DebugManager.log_error(
|
||||||
|
"Empty or null gameplay mode provided", "GameManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if not gameplay_mode is String:
|
if not gameplay_mode is String:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid gameplay mode type: " + str(typeof(gameplay_mode)), "GameManager"
|
"Invalid gameplay mode type: " + str(typeof(gameplay_mode)),
|
||||||
|
"GameManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Prevent concurrent scene changes (race condition protection)
|
# Prevent concurrent scene changes (race condition protection)
|
||||||
if is_changing_scene:
|
if is_changing_scene:
|
||||||
DebugManager.log_warn("Scene change already in progress, ignoring request", "GameManager")
|
DebugManager.log_warn(
|
||||||
|
"Scene change already in progress, ignoring request", "GameManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Validate gameplay mode
|
# Validate gameplay mode
|
||||||
var valid_modes = ["match3", "clickomania"]
|
var valid_modes = ["match3", "clickomania"]
|
||||||
if not gameplay_mode in valid_modes:
|
if not gameplay_mode in valid_modes:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid gameplay mode: '%s'. Valid modes: %s" % [gameplay_mode, str(valid_modes)],
|
(
|
||||||
|
"Invalid gameplay mode: '%s'. Valid modes: %s"
|
||||||
|
% [gameplay_mode, str(valid_modes)]
|
||||||
|
),
|
||||||
"GameManager"
|
"GameManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ func save_game() -> bool:
|
|||||||
"""Save current game data with race condition protection and error handling"""
|
"""Save current game data with race condition protection and error handling"""
|
||||||
# Prevent concurrent saves
|
# Prevent concurrent saves
|
||||||
if _save_in_progress:
|
if _save_in_progress:
|
||||||
DebugManager.log_warn("Save already in progress, skipping", "SaveManager")
|
DebugManager.log_warn(
|
||||||
|
"Save already in progress, skipping", "SaveManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
_save_in_progress = true
|
_save_in_progress = true
|
||||||
@@ -61,10 +63,13 @@ func _perform_save() -> bool:
|
|||||||
# Calculate checksum excluding _checksum field
|
# Calculate checksum excluding _checksum field
|
||||||
save_data["_checksum"] = _calculate_checksum(save_data)
|
save_data["_checksum"] = _calculate_checksum(save_data)
|
||||||
|
|
||||||
var save_file: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE)
|
var save_file: FileAccess = FileAccess.open(
|
||||||
|
SAVE_FILE_PATH, FileAccess.WRITE
|
||||||
|
)
|
||||||
if save_file == null:
|
if save_file == null:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to open save file for writing: %s" % SAVE_FILE_PATH, "SaveManager"
|
"Failed to open save file for writing: %s" % SAVE_FILE_PATH,
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -72,7 +77,9 @@ func _perform_save() -> bool:
|
|||||||
|
|
||||||
# Validate JSON creation
|
# Validate JSON creation
|
||||||
if json_string.is_empty():
|
if json_string.is_empty():
|
||||||
DebugManager.log_error("Failed to serialize save data to JSON", "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Failed to serialize save data to JSON", "SaveManager"
|
||||||
|
)
|
||||||
save_file.close()
|
save_file.close()
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -92,7 +99,9 @@ func _perform_save() -> bool:
|
|||||||
func load_game() -> void:
|
func load_game() -> void:
|
||||||
"""Load game data from disk with comprehensive validation and error recovery"""
|
"""Load game data from disk with comprehensive validation and error recovery"""
|
||||||
if not FileAccess.file_exists(SAVE_FILE_PATH):
|
if not FileAccess.file_exists(SAVE_FILE_PATH):
|
||||||
DebugManager.log_info("No save file found, using defaults", "SaveManager")
|
DebugManager.log_info(
|
||||||
|
"No save file found, using defaults", "SaveManager"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Reset restore flag
|
# Reset restore flag
|
||||||
@@ -111,7 +120,8 @@ func _load_and_parse_save_file() -> Variant:
|
|||||||
var save_file: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ)
|
var save_file: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ)
|
||||||
if save_file == null:
|
if save_file == null:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to open save file for reading: %s" % SAVE_FILE_PATH, "SaveManager"
|
"Failed to open save file for reading: %s" % SAVE_FILE_PATH,
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
@@ -119,7 +129,11 @@ func _load_and_parse_save_file() -> Variant:
|
|||||||
var file_size: int = save_file.get_length()
|
var file_size: int = save_file.get_length()
|
||||||
if file_size > MAX_FILE_SIZE:
|
if file_size > MAX_FILE_SIZE:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Save file too large: %d bytes (max %d)" % [file_size, MAX_FILE_SIZE], "SaveManager"
|
(
|
||||||
|
"Save file too large: %d bytes (max %d)"
|
||||||
|
% [file_size, MAX_FILE_SIZE]
|
||||||
|
),
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
save_file.close()
|
save_file.close()
|
||||||
return null
|
return null
|
||||||
@@ -128,21 +142,26 @@ func _load_and_parse_save_file() -> Variant:
|
|||||||
save_file.close()
|
save_file.close()
|
||||||
|
|
||||||
if not json_string is String:
|
if not json_string is String:
|
||||||
DebugManager.log_error("Save file contains invalid data type", "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Save file contains invalid data type", "SaveManager"
|
||||||
|
)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
var json: JSON = JSON.new()
|
var json: JSON = JSON.new()
|
||||||
var parse_result: Error = json.parse(json_string)
|
var parse_result: Error = json.parse(json_string)
|
||||||
if parse_result != OK:
|
if parse_result != OK:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to parse save file JSON: %s" % json.error_string, "SaveManager"
|
"Failed to parse save file JSON: %s" % json.error_string,
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
_handle_load_failure("JSON parse failed")
|
_handle_load_failure("JSON parse failed")
|
||||||
return null
|
return null
|
||||||
|
|
||||||
var loaded_data: Variant = json.data
|
var loaded_data: Variant = json.data
|
||||||
if not loaded_data is Dictionary:
|
if not loaded_data is Dictionary:
|
||||||
DebugManager.log_error("Save file root is not a dictionary", "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Save file root is not a dictionary", "SaveManager"
|
||||||
|
)
|
||||||
_handle_load_failure("Invalid data format")
|
_handle_load_failure("Invalid data format")
|
||||||
return null
|
return null
|
||||||
|
|
||||||
@@ -154,7 +173,8 @@ func _process_loaded_data(loaded_data: Variant) -> void:
|
|||||||
# Validate checksum first
|
# Validate checksum first
|
||||||
if not _validate_checksum(loaded_data):
|
if not _validate_checksum(loaded_data):
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Save file checksum validation failed - possible tampering", "SaveManager"
|
"Save file checksum validation failed - possible tampering",
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
_handle_load_failure("Checksum validation failed")
|
_handle_load_failure("Checksum validation failed")
|
||||||
return
|
return
|
||||||
@@ -162,14 +182,17 @@ func _process_loaded_data(loaded_data: Variant) -> void:
|
|||||||
# Handle version migration
|
# Handle version migration
|
||||||
var migrated_data: Variant = _handle_version_migration(loaded_data)
|
var migrated_data: Variant = _handle_version_migration(loaded_data)
|
||||||
if migrated_data == null:
|
if migrated_data == null:
|
||||||
DebugManager.log_error("Save file version migration failed", "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Save file version migration failed", "SaveManager"
|
||||||
|
)
|
||||||
_handle_load_failure("Migration failed")
|
_handle_load_failure("Migration failed")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Validate and fix loaded data
|
# Validate and fix loaded data
|
||||||
if not _validate_and_fix_save_data(migrated_data):
|
if not _validate_and_fix_save_data(migrated_data):
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Save file failed validation after migration, using defaults", "SaveManager"
|
"Save file failed validation after migration, using defaults",
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
_handle_load_failure("Validation failed")
|
_handle_load_failure("Validation failed")
|
||||||
return
|
return
|
||||||
@@ -184,7 +207,8 @@ func _handle_load_failure(reason: String) -> void:
|
|||||||
var backup_restored = _restore_backup_if_exists()
|
var backup_restored = _restore_backup_if_exists()
|
||||||
if not backup_restored:
|
if not backup_restored:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"%s and backup restore failed, using defaults" % reason, "SaveManager"
|
"%s and backup restore failed, using defaults" % reason,
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
@@ -199,11 +223,14 @@ func _handle_load_failure(reason: String) -> void:
|
|||||||
func update_current_score(score: int) -> void:
|
func update_current_score(score: int) -> void:
|
||||||
# Input validation
|
# Input validation
|
||||||
if score < 0:
|
if score < 0:
|
||||||
DebugManager.log_warn("Negative score rejected: %d" % score, "SaveManager")
|
DebugManager.log_warn(
|
||||||
|
"Negative score rejected: %d" % score, "SaveManager"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
if score > MAX_SCORE:
|
if score > MAX_SCORE:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Score too high, capping at maximum: %d -> %d" % [score, MAX_SCORE], "SaveManager"
|
"Score too high, capping at maximum: %d -> %d" % [score, MAX_SCORE],
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
score = MAX_SCORE
|
score = MAX_SCORE
|
||||||
|
|
||||||
@@ -219,23 +246,33 @@ func start_new_game() -> void:
|
|||||||
# Clear saved grid state
|
# Clear saved grid state
|
||||||
game_data.grid_state.grid_layout = []
|
game_data.grid_state.grid_layout = []
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Started new game #%d (cleared grid state)" % game_data.games_played, "SaveManager"
|
"Started new game #%d (cleared grid state)" % game_data.games_played,
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func finish_game(final_score: int) -> void:
|
func finish_game(final_score: int) -> void:
|
||||||
# Input validation
|
# Input validation
|
||||||
if final_score < 0:
|
if final_score < 0:
|
||||||
DebugManager.log_warn("Negative final score rejected: %d" % final_score, "SaveManager")
|
DebugManager.log_warn(
|
||||||
|
"Negative final score rejected: %d" % final_score, "SaveManager"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
if final_score > MAX_SCORE:
|
if final_score > MAX_SCORE:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Final score too high, capping: %d -> %d" % [final_score, MAX_SCORE], "SaveManager"
|
(
|
||||||
|
"Final score too high, capping: %d -> %d"
|
||||||
|
% [final_score, MAX_SCORE]
|
||||||
|
),
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
final_score = MAX_SCORE
|
final_score = MAX_SCORE
|
||||||
|
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Finishing game with score: %d (previous: %d)" % [final_score, game_data.current_score],
|
(
|
||||||
|
"Finishing game with score: %d (previous: %d)"
|
||||||
|
% [final_score, game_data.current_score]
|
||||||
|
),
|
||||||
"SaveManager"
|
"SaveManager"
|
||||||
)
|
)
|
||||||
game_data.current_score = final_score
|
game_data.current_score = final_score
|
||||||
@@ -250,7 +287,9 @@ func finish_game(final_score: int) -> void:
|
|||||||
|
|
||||||
if final_score > game_data.high_score:
|
if final_score > game_data.high_score:
|
||||||
game_data.high_score = final_score
|
game_data.high_score = final_score
|
||||||
DebugManager.log_info("New high score achieved: %d" % final_score, "SaveManager")
|
DebugManager.log_info(
|
||||||
|
"New high score achieved: %d" % final_score, "SaveManager"
|
||||||
|
)
|
||||||
save_game()
|
save_game()
|
||||||
|
|
||||||
|
|
||||||
@@ -271,11 +310,18 @@ func get_total_score() -> int:
|
|||||||
|
|
||||||
|
|
||||||
func save_grid_state(
|
func save_grid_state(
|
||||||
grid_size: Vector2i, tile_types_count: int, active_gem_types: Array, grid_layout: Array
|
grid_size: Vector2i,
|
||||||
|
tile_types_count: int,
|
||||||
|
active_gem_types: Array,
|
||||||
|
grid_layout: Array
|
||||||
) -> void:
|
) -> void:
|
||||||
# Input validation
|
# Input validation
|
||||||
if not _validate_grid_parameters(grid_size, tile_types_count, active_gem_types, grid_layout):
|
if not _validate_grid_parameters(
|
||||||
DebugManager.log_error("Grid state validation failed, not saving", "SaveManager")
|
grid_size, tile_types_count, active_gem_types, grid_layout
|
||||||
|
):
|
||||||
|
DebugManager.log_error(
|
||||||
|
"Grid state validation failed, not saving", "SaveManager"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
@@ -338,10 +384,13 @@ func reset_all_progress() -> bool:
|
|||||||
if FileAccess.file_exists(SAVE_FILE_PATH):
|
if FileAccess.file_exists(SAVE_FILE_PATH):
|
||||||
var error: Error = DirAccess.remove_absolute(SAVE_FILE_PATH)
|
var error: Error = DirAccess.remove_absolute(SAVE_FILE_PATH)
|
||||||
if error == OK:
|
if error == OK:
|
||||||
DebugManager.log_info("Main save file deleted successfully", "SaveManager")
|
DebugManager.log_info(
|
||||||
|
"Main save file deleted successfully", "SaveManager"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to delete main save file: error %d" % error, "SaveManager"
|
"Failed to delete main save file: error %d" % error,
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete backup file
|
# Delete backup file
|
||||||
@@ -349,21 +398,27 @@ func reset_all_progress() -> bool:
|
|||||||
if FileAccess.file_exists(backup_path):
|
if FileAccess.file_exists(backup_path):
|
||||||
var error: Error = DirAccess.remove_absolute(backup_path)
|
var error: Error = DirAccess.remove_absolute(backup_path)
|
||||||
if error == OK:
|
if error == OK:
|
||||||
DebugManager.log_info("Backup save file deleted successfully", "SaveManager")
|
DebugManager.log_info(
|
||||||
|
"Backup save file deleted successfully", "SaveManager"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to delete backup save file: error %d" % error, "SaveManager"
|
"Failed to delete backup save file: error %d" % error,
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Progress reset completed - all scores and save data cleared", "SaveManager"
|
"Progress reset completed - all scores and save data cleared",
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Clear restore flag
|
# Clear restore flag
|
||||||
_restore_in_progress = false
|
_restore_in_progress = false
|
||||||
|
|
||||||
# Create fresh save file with default data
|
# Create fresh save file with default data
|
||||||
DebugManager.log_info("Creating fresh save file with default data", "SaveManager")
|
DebugManager.log_info(
|
||||||
|
"Creating fresh save file with default data", "SaveManager"
|
||||||
|
)
|
||||||
save_game()
|
save_game()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -395,11 +450,17 @@ func _validate_save_data(data: Dictionary) -> bool:
|
|||||||
func _validate_required_fields(data: Dictionary) -> bool:
|
func _validate_required_fields(data: Dictionary) -> bool:
|
||||||
"""Validate that all required fields exist"""
|
"""Validate that all required fields exist"""
|
||||||
var required_fields: Array[String] = [
|
var required_fields: Array[String] = [
|
||||||
"high_score", "current_score", "games_played", "total_score", "grid_state"
|
"high_score",
|
||||||
|
"current_score",
|
||||||
|
"games_played",
|
||||||
|
"total_score",
|
||||||
|
"grid_state"
|
||||||
]
|
]
|
||||||
for field in required_fields:
|
for field in required_fields:
|
||||||
if not data.has(field):
|
if not data.has(field):
|
||||||
DebugManager.log_error("Missing required field: %s" % field, "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Missing required field: %s" % field, "SaveManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -409,7 +470,9 @@ func _validate_score_fields(data: Dictionary) -> bool:
|
|||||||
var score_fields = ["high_score", "current_score", "total_score"]
|
var score_fields = ["high_score", "current_score", "total_score"]
|
||||||
for field in score_fields:
|
for field in score_fields:
|
||||||
if not _is_valid_score(data.get(field, 0)):
|
if not _is_valid_score(data.get(field, 0)):
|
||||||
DebugManager.log_error("Invalid %s validation failed" % field, "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Invalid %s validation failed" % field, "SaveManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -419,7 +482,10 @@ func _validate_games_played_field(data: Dictionary) -> bool:
|
|||||||
var games_played: Variant = data.get("games_played", 0)
|
var games_played: Variant = data.get("games_played", 0)
|
||||||
if not (games_played is int or games_played is float):
|
if not (games_played is int or games_played is float):
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid games_played type: %s (type: %s)" % [str(games_played), typeof(games_played)],
|
(
|
||||||
|
"Invalid games_played type: %s (type: %s)"
|
||||||
|
% [str(games_played), typeof(games_played)]
|
||||||
|
),
|
||||||
"SaveManager"
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
@@ -427,14 +493,18 @@ func _validate_games_played_field(data: Dictionary) -> bool:
|
|||||||
# Check for NaN/Infinity in games_played if it's a float
|
# Check for NaN/Infinity in games_played if it's a float
|
||||||
if games_played is float and (is_nan(games_played) or is_inf(games_played)):
|
if games_played is float and (is_nan(games_played) or is_inf(games_played)):
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid games_played float value: %s" % str(games_played), "SaveManager"
|
"Invalid games_played float value: %s" % str(games_played),
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
var games_played_int: int = int(games_played)
|
var games_played_int: int = int(games_played)
|
||||||
if games_played_int < 0 or games_played_int > MAX_GAMES_PLAYED:
|
if games_played_int < 0 or games_played_int > MAX_GAMES_PLAYED:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid games_played value: %d (range: 0-%d)" % [games_played_int, MAX_GAMES_PLAYED],
|
(
|
||||||
|
"Invalid games_played value: %d (range: 0-%d)"
|
||||||
|
% [games_played_int, MAX_GAMES_PLAYED]
|
||||||
|
),
|
||||||
"SaveManager"
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
@@ -447,16 +517,23 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
|||||||
Permissive validation that fixes issues instead of rejecting data entirely.
|
Permissive validation that fixes issues instead of rejecting data entirely.
|
||||||
Used during migration to preserve as much user data as possible.
|
Used during migration to preserve as much user data as possible.
|
||||||
"""
|
"""
|
||||||
DebugManager.log_info("Running permissive validation with auto-fix", "SaveManager")
|
DebugManager.log_info(
|
||||||
|
"Running permissive validation with auto-fix", "SaveManager"
|
||||||
|
)
|
||||||
|
|
||||||
# Ensure all required fields exist, create defaults if missing
|
# Ensure all required fields exist, create defaults if missing
|
||||||
var required_fields: Array[String] = [
|
var required_fields: Array[String] = [
|
||||||
"high_score", "current_score", "games_played", "total_score", "grid_state"
|
"high_score",
|
||||||
|
"current_score",
|
||||||
|
"games_played",
|
||||||
|
"total_score",
|
||||||
|
"grid_state"
|
||||||
]
|
]
|
||||||
for field in required_fields:
|
for field in required_fields:
|
||||||
if not data.has(field):
|
if not data.has(field):
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Missing required field '%s', adding default value" % field, "SaveManager"
|
"Missing required field '%s', adding default value" % field,
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
match field:
|
match field:
|
||||||
"high_score", "current_score", "total_score":
|
"high_score", "current_score", "total_score":
|
||||||
@@ -475,15 +552,21 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
|||||||
for field in ["high_score", "current_score", "total_score"]:
|
for field in ["high_score", "current_score", "total_score"]:
|
||||||
var value: Variant = data.get(field, 0)
|
var value: Variant = data.get(field, 0)
|
||||||
if not (value is int or value is float):
|
if not (value is int or value is float):
|
||||||
DebugManager.log_warn("Invalid type for %s, converting to 0" % field, "SaveManager")
|
DebugManager.log_warn(
|
||||||
|
"Invalid type for %s, converting to 0" % field, "SaveManager"
|
||||||
|
)
|
||||||
data[field] = 0
|
data[field] = 0
|
||||||
else:
|
else:
|
||||||
var numeric_value: int = int(value)
|
var numeric_value: int = int(value)
|
||||||
if numeric_value < 0:
|
if numeric_value < 0:
|
||||||
DebugManager.log_warn("Negative %s fixed to 0" % field, "SaveManager")
|
DebugManager.log_warn(
|
||||||
|
"Negative %s fixed to 0" % field, "SaveManager"
|
||||||
|
)
|
||||||
data[field] = 0
|
data[field] = 0
|
||||||
elif numeric_value > MAX_SCORE:
|
elif numeric_value > MAX_SCORE:
|
||||||
DebugManager.log_warn("%s too high, clamped to maximum" % field, "SaveManager")
|
DebugManager.log_warn(
|
||||||
|
"%s too high, clamped to maximum" % field, "SaveManager"
|
||||||
|
)
|
||||||
data[field] = MAX_SCORE
|
data[field] = MAX_SCORE
|
||||||
else:
|
else:
|
||||||
data[field] = numeric_value
|
data[field] = numeric_value
|
||||||
@@ -491,7 +574,9 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
|||||||
# Fix games_played
|
# Fix games_played
|
||||||
var games_played: Variant = data.get("games_played", 0)
|
var games_played: Variant = data.get("games_played", 0)
|
||||||
if not (games_played is int or games_played is float):
|
if not (games_played is int or games_played is float):
|
||||||
DebugManager.log_warn("Invalid games_played type, converting to 0", "SaveManager")
|
DebugManager.log_warn(
|
||||||
|
"Invalid games_played type, converting to 0", "SaveManager"
|
||||||
|
)
|
||||||
data["games_played"] = 0
|
data["games_played"] = 0
|
||||||
else:
|
else:
|
||||||
var games_played_int: int = int(games_played)
|
var games_played_int: int = int(games_played)
|
||||||
@@ -505,7 +590,9 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
|||||||
# Fix grid_state - ensure it exists and has basic structure
|
# Fix grid_state - ensure it exists and has basic structure
|
||||||
var grid_state: Variant = data.get("grid_state", {})
|
var grid_state: Variant = data.get("grid_state", {})
|
||||||
if not grid_state is Dictionary:
|
if not grid_state is Dictionary:
|
||||||
DebugManager.log_warn("Invalid grid_state, creating default", "SaveManager")
|
DebugManager.log_warn(
|
||||||
|
"Invalid grid_state, creating default", "SaveManager"
|
||||||
|
)
|
||||||
data["grid_state"] = {
|
data["grid_state"] = {
|
||||||
"grid_size": {"x": 8, "y": 8},
|
"grid_size": {"x": 8, "y": 8},
|
||||||
"tile_types_count": 5,
|
"tile_types_count": 5,
|
||||||
@@ -514,24 +601,48 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
|||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# Fix grid_state fields if they're missing or invalid
|
# Fix grid_state fields if they're missing or invalid
|
||||||
if not grid_state.has("grid_size") or not grid_state.grid_size is Dictionary:
|
if (
|
||||||
DebugManager.log_warn("Invalid grid_size, using default", "SaveManager")
|
not grid_state.has("grid_size")
|
||||||
|
or not grid_state.grid_size is Dictionary
|
||||||
|
):
|
||||||
|
DebugManager.log_warn(
|
||||||
|
"Invalid grid_size, using default", "SaveManager"
|
||||||
|
)
|
||||||
grid_state["grid_size"] = {"x": 8, "y": 8}
|
grid_state["grid_size"] = {"x": 8, "y": 8}
|
||||||
|
|
||||||
if not grid_state.has("tile_types_count") or not grid_state.tile_types_count is int:
|
if (
|
||||||
DebugManager.log_warn("Invalid tile_types_count, using default", "SaveManager")
|
not grid_state.has("tile_types_count")
|
||||||
|
or not grid_state.tile_types_count is int
|
||||||
|
):
|
||||||
|
DebugManager.log_warn(
|
||||||
|
"Invalid tile_types_count, using default", "SaveManager"
|
||||||
|
)
|
||||||
grid_state["tile_types_count"] = 5
|
grid_state["tile_types_count"] = 5
|
||||||
|
|
||||||
if not grid_state.has("active_gem_types") or not grid_state.active_gem_types is Array:
|
if (
|
||||||
DebugManager.log_warn("Invalid active_gem_types, using default", "SaveManager")
|
not grid_state.has("active_gem_types")
|
||||||
|
or not grid_state.active_gem_types is Array
|
||||||
|
):
|
||||||
|
DebugManager.log_warn(
|
||||||
|
"Invalid active_gem_types, using default", "SaveManager"
|
||||||
|
)
|
||||||
grid_state["active_gem_types"] = [0, 1, 2, 3, 4]
|
grid_state["active_gem_types"] = [0, 1, 2, 3, 4]
|
||||||
|
|
||||||
if not grid_state.has("grid_layout") or not grid_state.grid_layout is Array:
|
if (
|
||||||
DebugManager.log_warn("Invalid grid_layout, clearing saved grid", "SaveManager")
|
not grid_state.has("grid_layout")
|
||||||
|
or not grid_state.grid_layout is Array
|
||||||
|
):
|
||||||
|
DebugManager.log_warn(
|
||||||
|
"Invalid grid_layout, clearing saved grid", "SaveManager"
|
||||||
|
)
|
||||||
grid_state["grid_layout"] = []
|
grid_state["grid_layout"] = []
|
||||||
|
|
||||||
DebugManager.log_info(
|
(
|
||||||
"Permissive validation completed - data has been fixed and will be loaded", "SaveManager"
|
DebugManager
|
||||||
|
. log_info(
|
||||||
|
"Permissive validation completed - data has been fixed and will be loaded",
|
||||||
|
"SaveManager"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -569,7 +680,10 @@ func _validate_grid_size(grid_state: Dictionary) -> Dictionary:
|
|||||||
"""Validate grid size and return validation result with dimensions"""
|
"""Validate grid size and return validation result with dimensions"""
|
||||||
var result = {"valid": false, "width": 0, "height": 0}
|
var result = {"valid": false, "width": 0, "height": 0}
|
||||||
|
|
||||||
if not grid_state.has("grid_size") or not grid_state.grid_size is Dictionary:
|
if (
|
||||||
|
not grid_state.has("grid_size")
|
||||||
|
or not grid_state.grid_size is Dictionary
|
||||||
|
):
|
||||||
DebugManager.log_error("Invalid grid_size in save data", "SaveManager")
|
DebugManager.log_error("Invalid grid_size in save data", "SaveManager")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -581,8 +695,15 @@ func _validate_grid_size(grid_state: Dictionary) -> Dictionary:
|
|||||||
var height: Variant = size.y
|
var height: Variant = size.y
|
||||||
if not width is int or not height is int:
|
if not width is int or not height is int:
|
||||||
return result
|
return result
|
||||||
if width < 3 or height < 3 or width > MAX_GRID_SIZE or height > MAX_GRID_SIZE:
|
if (
|
||||||
DebugManager.log_error("Grid size out of bounds: %dx%d" % [width, height], "SaveManager")
|
width < 3
|
||||||
|
or height < 3
|
||||||
|
or width > MAX_GRID_SIZE
|
||||||
|
or height > MAX_GRID_SIZE
|
||||||
|
):
|
||||||
|
DebugManager.log_error(
|
||||||
|
"Grid size out of bounds: %dx%d" % [width, height], "SaveManager"
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
result.valid = true
|
result.valid = true
|
||||||
@@ -595,16 +716,22 @@ func _validate_tile_types(grid_state: Dictionary) -> int:
|
|||||||
"""Validate tile types count and return it, or -1 if invalid"""
|
"""Validate tile types count and return it, or -1 if invalid"""
|
||||||
var tile_types: Variant = grid_state.get("tile_types_count", 0)
|
var tile_types: Variant = grid_state.get("tile_types_count", 0)
|
||||||
if not tile_types is int or tile_types < 3 or tile_types > MAX_TILE_TYPES:
|
if not tile_types is int or tile_types < 3 or tile_types > MAX_TILE_TYPES:
|
||||||
DebugManager.log_error("Invalid tile_types_count: %s" % str(tile_types), "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Invalid tile_types_count: %s" % str(tile_types), "SaveManager"
|
||||||
|
)
|
||||||
return -1
|
return -1
|
||||||
return tile_types
|
return tile_types
|
||||||
|
|
||||||
|
|
||||||
func _validate_active_gem_types(grid_state: Dictionary, tile_types: int) -> bool:
|
func _validate_active_gem_types(
|
||||||
|
grid_state: Dictionary, tile_types: int
|
||||||
|
) -> bool:
|
||||||
"""Validate active gem types array"""
|
"""Validate active gem types array"""
|
||||||
var active_gems: Variant = grid_state.get("active_gem_types", [])
|
var active_gems: Variant = grid_state.get("active_gem_types", [])
|
||||||
if not active_gems is Array:
|
if not active_gems is Array:
|
||||||
DebugManager.log_error("active_gem_types is not an array", "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"active_gem_types is not an array", "SaveManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# If active_gem_types exists, validate its contents
|
# If active_gem_types exists, validate its contents
|
||||||
@@ -613,12 +740,17 @@ func _validate_active_gem_types(grid_state: Dictionary, tile_types: int) -> bool
|
|||||||
var gem_type: Variant = active_gems[i]
|
var gem_type: Variant = active_gems[i]
|
||||||
if not gem_type is int:
|
if not gem_type is int:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"active_gem_types[%d] is not an integer: %s" % [i, str(gem_type)], "SaveManager"
|
(
|
||||||
|
"active_gem_types[%d] is not an integer: %s"
|
||||||
|
% [i, str(gem_type)]
|
||||||
|
),
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
if gem_type < 0 or gem_type >= tile_types:
|
if gem_type < 0 or gem_type >= tile_types:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"active_gem_types[%d] out of range: %d" % [i, gem_type], "SaveManager"
|
"active_gem_types[%d] out of range: %d" % [i, gem_type],
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
@@ -629,7 +761,10 @@ func _validate_grid_layout(
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
if layout.size() != expected_height:
|
if layout.size() != expected_height:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Grid layout height mismatch: %d vs %d" % [layout.size(), expected_height],
|
(
|
||||||
|
"Grid layout height mismatch: %d vs %d"
|
||||||
|
% [layout.size(), expected_height]
|
||||||
|
),
|
||||||
"SaveManager"
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
@@ -637,11 +772,16 @@ func _validate_grid_layout(
|
|||||||
for y in range(layout.size()):
|
for y in range(layout.size()):
|
||||||
var row: Variant = layout[y]
|
var row: Variant = layout[y]
|
||||||
if not row is Array:
|
if not row is Array:
|
||||||
DebugManager.log_error("Grid layout row %d is not an array" % y, "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Grid layout row %d is not an array" % y, "SaveManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
if row.size() != expected_width:
|
if row.size() != expected_width:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Grid layout row %d width mismatch: %d vs %d" % [y, row.size(), expected_width],
|
(
|
||||||
|
"Grid layout row %d width mismatch: %d vs %d"
|
||||||
|
% [y, row.size(), expected_width]
|
||||||
|
),
|
||||||
"SaveManager"
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
@@ -650,13 +790,20 @@ func _validate_grid_layout(
|
|||||||
var tile_type: Variant = row[x]
|
var tile_type: Variant = row[x]
|
||||||
if not tile_type is int:
|
if not tile_type is int:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Grid tile [%d][%d] is not an integer: %s" % [y, x, str(tile_type)],
|
(
|
||||||
|
"Grid tile [%d][%d] is not an integer: %s"
|
||||||
|
% [y, x, str(tile_type)]
|
||||||
|
),
|
||||||
"SaveManager"
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
if tile_type < -1 or tile_type >= max_tile_type:
|
if tile_type < -1 or tile_type >= max_tile_type:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Grid tile [%d][%d] type out of range: %d" % [y, x, tile_type], "SaveManager"
|
(
|
||||||
|
"Grid tile [%d][%d] type out of range: %d"
|
||||||
|
% [y, x, tile_type]
|
||||||
|
),
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -664,7 +811,10 @@ func _validate_grid_layout(
|
|||||||
|
|
||||||
|
|
||||||
func _validate_grid_parameters(
|
func _validate_grid_parameters(
|
||||||
grid_size: Vector2i, tile_types_count: int, active_gem_types: Array, grid_layout: Array
|
grid_size: Vector2i,
|
||||||
|
tile_types_count: int,
|
||||||
|
active_gem_types: Array,
|
||||||
|
grid_layout: Array
|
||||||
) -> bool:
|
) -> bool:
|
||||||
# Validate grid size
|
# Validate grid size
|
||||||
if (
|
if (
|
||||||
@@ -685,7 +835,10 @@ func _validate_grid_parameters(
|
|||||||
# Validate tile types count
|
# Validate tile types count
|
||||||
if tile_types_count < 3 or tile_types_count > MAX_TILE_TYPES:
|
if tile_types_count < 3 or tile_types_count > MAX_TILE_TYPES:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid tile types count: %d (min 3, max %d)" % [tile_types_count, MAX_TILE_TYPES],
|
(
|
||||||
|
"Invalid tile types count: %d (min 3, max %d)"
|
||||||
|
% [tile_types_count, MAX_TILE_TYPES]
|
||||||
|
),
|
||||||
"SaveManager"
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
@@ -702,14 +855,20 @@ func _validate_grid_parameters(
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
# Validate grid layout
|
# Validate grid layout
|
||||||
return _validate_grid_layout(grid_layout, grid_size.x, grid_size.y, tile_types_count)
|
return _validate_grid_layout(
|
||||||
|
grid_layout, grid_size.x, grid_size.y, tile_types_count
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _is_valid_score(score: Variant) -> bool:
|
func _is_valid_score(score: Variant) -> bool:
|
||||||
# Accept both int and float, but convert to int for validation
|
# Accept both int and float, but convert to int for validation
|
||||||
if not (score is int or score is float):
|
if not (score is int or score is float):
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Score is not a number: %s (type: %s)" % [str(score), typeof(score)], "SaveManager"
|
(
|
||||||
|
"Score is not a number: %s (type: %s)"
|
||||||
|
% [str(score), typeof(score)]
|
||||||
|
),
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -717,13 +876,16 @@ func _is_valid_score(score: Variant) -> bool:
|
|||||||
if score is float:
|
if score is float:
|
||||||
if is_nan(score) or is_inf(score):
|
if is_nan(score) or is_inf(score):
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Score contains invalid float value (NaN/Inf): %s" % str(score), "SaveManager"
|
"Score contains invalid float value (NaN/Inf): %s" % str(score),
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
var score_int = int(score)
|
var score_int = int(score)
|
||||||
if score_int < 0 or score_int > MAX_SCORE:
|
if score_int < 0 or score_int > MAX_SCORE:
|
||||||
DebugManager.log_error("Score out of bounds: %d" % score_int, "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Score out of bounds: %d" % score_int, "SaveManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -737,12 +899,16 @@ func _merge_validated_data(loaded_data: Dictionary) -> void:
|
|||||||
|
|
||||||
# Games played should always be an integer
|
# Games played should always be an integer
|
||||||
if loaded_data.has("games_played"):
|
if loaded_data.has("games_played"):
|
||||||
game_data["games_played"] = _safe_get_numeric_value(loaded_data, "games_played", 0)
|
game_data["games_played"] = _safe_get_numeric_value(
|
||||||
|
loaded_data, "games_played", 0
|
||||||
|
)
|
||||||
|
|
||||||
# Merge grid state carefully
|
# Merge grid state carefully
|
||||||
var loaded_grid: Variant = loaded_data.get("grid_state", {})
|
var loaded_grid: Variant = loaded_data.get("grid_state", {})
|
||||||
if loaded_grid is Dictionary:
|
if loaded_grid is Dictionary:
|
||||||
for grid_key in ["grid_size", "tile_types_count", "active_gem_types", "grid_layout"]:
|
for grid_key in [
|
||||||
|
"grid_size", "tile_types_count", "active_gem_types", "grid_layout"
|
||||||
|
]:
|
||||||
if loaded_grid.has(grid_key):
|
if loaded_grid.has(grid_key):
|
||||||
game_data.grid_state[grid_key] = loaded_grid[grid_key]
|
game_data.grid_state[grid_key] = loaded_grid[grid_key]
|
||||||
|
|
||||||
@@ -844,15 +1010,22 @@ func _validate_checksum(data: Dictionary) -> bool:
|
|||||||
# Try to be more lenient with existing saves to prevent data loss
|
# Try to be more lenient with existing saves to prevent data loss
|
||||||
var data_version: Variant = data.get("_version", 0)
|
var data_version: Variant = data.get("_version", 0)
|
||||||
if data_version <= 1:
|
if data_version <= 1:
|
||||||
DebugManager.log_warn(
|
(
|
||||||
(
|
DebugManager
|
||||||
"Checksum mismatch in v%d save file - may be due to JSON serialization issue "
|
. log_warn(
|
||||||
+ (
|
(
|
||||||
"(stored: %s, calculated: %s)"
|
"Checksum mismatch in v%d save file - may be due to JSON serialization issue "
|
||||||
% [data_version, stored_checksum, calculated_checksum]
|
+ (
|
||||||
)
|
"(stored: %s, calculated: %s)"
|
||||||
),
|
% [
|
||||||
"SaveManager"
|
data_version,
|
||||||
|
stored_checksum,
|
||||||
|
calculated_checksum
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"SaveManager"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
(
|
(
|
||||||
DebugManager
|
DebugManager
|
||||||
@@ -876,7 +1049,9 @@ func _validate_checksum(data: Dictionary) -> bool:
|
|||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
|
|
||||||
func _safe_get_numeric_value(data: Dictionary, key: String, default_value: float) -> int:
|
func _safe_get_numeric_value(
|
||||||
|
data: Dictionary, key: String, default_value: float
|
||||||
|
) -> int:
|
||||||
"""Safely extract and convert numeric values with comprehensive validation"""
|
"""Safely extract and convert numeric values with comprehensive validation"""
|
||||||
var value: Variant = data.get(key, default_value)
|
var value: Variant = data.get(key, default_value)
|
||||||
|
|
||||||
@@ -910,13 +1085,15 @@ func _safe_get_numeric_value(data: Dictionary, key: String, default_value: float
|
|||||||
if key in ["high_score", "current_score", "total_score"]:
|
if key in ["high_score", "current_score", "total_score"]:
|
||||||
if int_value < 0 or int_value > MAX_SCORE:
|
if int_value < 0 or int_value > MAX_SCORE:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Score %s out of bounds: %d, using default" % [key, int_value], "SaveManager"
|
"Score %s out of bounds: %d, using default" % [key, int_value],
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return int(default_value)
|
return int(default_value)
|
||||||
elif key == "games_played":
|
elif key == "games_played":
|
||||||
if int_value < 0 or int_value > MAX_GAMES_PLAYED:
|
if int_value < 0 or int_value > MAX_GAMES_PLAYED:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Games played out of bounds: %d, using default" % int_value, "SaveManager"
|
"Games played out of bounds: %d, using default" % int_value,
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return int(default_value)
|
return int(default_value)
|
||||||
|
|
||||||
@@ -930,7 +1107,8 @@ func _handle_version_migration(data: Dictionary) -> Variant:
|
|||||||
if data_version == SAVE_FORMAT_VERSION:
|
if data_version == SAVE_FORMAT_VERSION:
|
||||||
# Current version, no migration needed
|
# Current version, no migration needed
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Save file is current version (%d)" % SAVE_FORMAT_VERSION, "SaveManager"
|
"Save file is current version (%d)" % SAVE_FORMAT_VERSION,
|
||||||
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
if data_version > SAVE_FORMAT_VERSION:
|
if data_version > SAVE_FORMAT_VERSION:
|
||||||
@@ -945,7 +1123,10 @@ func _handle_version_migration(data: Dictionary) -> Variant:
|
|||||||
return null
|
return null
|
||||||
# Older version - migrate
|
# Older version - migrate
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Migrating save data from version %d to %d" % [data_version, SAVE_FORMAT_VERSION],
|
(
|
||||||
|
"Migrating save data from version %d to %d"
|
||||||
|
% [data_version, SAVE_FORMAT_VERSION]
|
||||||
|
),
|
||||||
"SaveManager"
|
"SaveManager"
|
||||||
)
|
)
|
||||||
return _migrate_save_data(data, data_version)
|
return _migrate_save_data(data, data_version)
|
||||||
@@ -960,7 +1141,9 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary:
|
|||||||
# Add new fields that didn't exist in version 0
|
# Add new fields that didn't exist in version 0
|
||||||
if not migrated_data.has("total_score"):
|
if not migrated_data.has("total_score"):
|
||||||
migrated_data["total_score"] = 0
|
migrated_data["total_score"] = 0
|
||||||
DebugManager.log_info("Added total_score field during migration", "SaveManager")
|
DebugManager.log_info(
|
||||||
|
"Added total_score field during migration", "SaveManager"
|
||||||
|
)
|
||||||
|
|
||||||
if not migrated_data.has("grid_state"):
|
if not migrated_data.has("grid_state"):
|
||||||
migrated_data["grid_state"] = {
|
migrated_data["grid_state"] = {
|
||||||
@@ -969,7 +1152,9 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary:
|
|||||||
"active_gem_types": [0, 1, 2, 3, 4],
|
"active_gem_types": [0, 1, 2, 3, 4],
|
||||||
"grid_layout": []
|
"grid_layout": []
|
||||||
}
|
}
|
||||||
DebugManager.log_info("Added grid_state structure during migration", "SaveManager")
|
DebugManager.log_info(
|
||||||
|
"Added grid_state structure during migration", "SaveManager"
|
||||||
|
)
|
||||||
|
|
||||||
# Ensure all numeric values are within bounds after migration
|
# Ensure all numeric values are within bounds after migration
|
||||||
for score_key in ["high_score", "current_score", "total_score"]:
|
for score_key in ["high_score", "current_score", "total_score"]:
|
||||||
@@ -981,11 +1166,17 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary:
|
|||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
(
|
(
|
||||||
"Clamping %s during migration: %d -> %d"
|
"Clamping %s during migration: %d -> %d"
|
||||||
% [score_key, int_score, clamp(int_score, 0, MAX_SCORE)]
|
% [
|
||||||
|
score_key,
|
||||||
|
int_score,
|
||||||
|
clamp(int_score, 0, MAX_SCORE)
|
||||||
|
]
|
||||||
),
|
),
|
||||||
"SaveManager"
|
"SaveManager"
|
||||||
)
|
)
|
||||||
migrated_data[score_key] = clamp(int_score, 0, MAX_SCORE)
|
migrated_data[score_key] = clamp(
|
||||||
|
int_score, 0, MAX_SCORE
|
||||||
|
)
|
||||||
|
|
||||||
# Future migrations would go here
|
# Future migrations would go here
|
||||||
# if from_version < 2:
|
# if from_version < 2:
|
||||||
@@ -997,7 +1188,9 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary:
|
|||||||
# Recalculate checksum after migration
|
# Recalculate checksum after migration
|
||||||
migrated_data["_checksum"] = _calculate_checksum(migrated_data)
|
migrated_data["_checksum"] = _calculate_checksum(migrated_data)
|
||||||
|
|
||||||
DebugManager.log_info("Save data migration completed successfully", "SaveManager")
|
DebugManager.log_info(
|
||||||
|
"Save data migration completed successfully", "SaveManager"
|
||||||
|
)
|
||||||
return migrated_data
|
return migrated_data
|
||||||
|
|
||||||
|
|
||||||
@@ -1005,7 +1198,9 @@ func _create_backup() -> void:
|
|||||||
# Create backup of current save file
|
# Create backup of current save file
|
||||||
if FileAccess.file_exists(SAVE_FILE_PATH):
|
if FileAccess.file_exists(SAVE_FILE_PATH):
|
||||||
var backup_path: String = SAVE_FILE_PATH + ".backup"
|
var backup_path: String = SAVE_FILE_PATH + ".backup"
|
||||||
var original: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ)
|
var original: FileAccess = FileAccess.open(
|
||||||
|
SAVE_FILE_PATH, FileAccess.READ
|
||||||
|
)
|
||||||
var backup: FileAccess = FileAccess.open(backup_path, FileAccess.WRITE)
|
var backup: FileAccess = FileAccess.open(backup_path, FileAccess.WRITE)
|
||||||
if original and backup:
|
if original and backup:
|
||||||
backup.store_var(original.get_var())
|
backup.store_var(original.get_var())
|
||||||
@@ -1017,7 +1212,9 @@ func _create_backup() -> void:
|
|||||||
func _restore_backup_if_exists() -> bool:
|
func _restore_backup_if_exists() -> bool:
|
||||||
var backup_path: String = SAVE_FILE_PATH + ".backup"
|
var backup_path: String = SAVE_FILE_PATH + ".backup"
|
||||||
if not FileAccess.file_exists(backup_path):
|
if not FileAccess.file_exists(backup_path):
|
||||||
DebugManager.log_warn("No backup file found for recovery", "SaveManager")
|
DebugManager.log_warn(
|
||||||
|
"No backup file found for recovery", "SaveManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
DebugManager.log_info("Attempting to restore from backup", "SaveManager")
|
DebugManager.log_info("Attempting to restore from backup", "SaveManager")
|
||||||
@@ -1025,12 +1222,16 @@ func _restore_backup_if_exists() -> bool:
|
|||||||
# Validate backup file size before attempting restore
|
# Validate backup file size before attempting restore
|
||||||
var backup_file: FileAccess = FileAccess.open(backup_path, FileAccess.READ)
|
var backup_file: FileAccess = FileAccess.open(backup_path, FileAccess.READ)
|
||||||
if backup_file == null:
|
if backup_file == null:
|
||||||
DebugManager.log_error("Failed to open backup file for reading", "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Failed to open backup file for reading", "SaveManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
var backup_size: int = backup_file.get_length()
|
var backup_size: int = backup_file.get_length()
|
||||||
if backup_size > MAX_FILE_SIZE:
|
if backup_size > MAX_FILE_SIZE:
|
||||||
DebugManager.log_error("Backup file too large: %d bytes" % backup_size, "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Backup file too large: %d bytes" % backup_size, "SaveManager"
|
||||||
|
)
|
||||||
backup_file.close()
|
backup_file.close()
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -1045,13 +1246,17 @@ func _restore_backup_if_exists() -> bool:
|
|||||||
# Create new save file from backup
|
# Create new save file from backup
|
||||||
var original: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE)
|
var original: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE)
|
||||||
if original == null:
|
if original == null:
|
||||||
DebugManager.log_error("Failed to create new save file from backup", "SaveManager")
|
DebugManager.log_error(
|
||||||
|
"Failed to create new save file from backup", "SaveManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
original.store_var(backup_data)
|
original.store_var(backup_data)
|
||||||
original.close()
|
original.close()
|
||||||
|
|
||||||
DebugManager.log_info("Backup restored successfully to main save file", "SaveManager")
|
DebugManager.log_info(
|
||||||
|
"Backup restored successfully to main save file", "SaveManager"
|
||||||
|
)
|
||||||
# Note: The restored file will be loaded on the next game restart
|
# Note: The restored file will be loaded on the next game restart
|
||||||
# We don't recursively load here to prevent infinite loops
|
# We don't recursively load here to prevent infinite loops
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ const MAX_SETTING_STRING_LENGTH = 10 # Max length for string settings like lang
|
|||||||
var settings: Dictionary = {}
|
var settings: Dictionary = {}
|
||||||
|
|
||||||
var default_settings: Dictionary = {
|
var default_settings: Dictionary = {
|
||||||
"master_volume": 0.50, "music_volume": 0.40, "sfx_volume": 0.50, "language": "en"
|
"master_volume": 0.50,
|
||||||
|
"music_volume": 0.40,
|
||||||
|
"sfx_volume": 0.50,
|
||||||
|
"language": "en"
|
||||||
}
|
}
|
||||||
|
|
||||||
var languages_data: Dictionary = {}
|
var languages_data: Dictionary = {}
|
||||||
@@ -32,7 +35,9 @@ func load_settings() -> void:
|
|||||||
|
|
||||||
if load_result == OK:
|
if load_result == OK:
|
||||||
for key in default_settings.keys():
|
for key in default_settings.keys():
|
||||||
var loaded_value = config.get_value("settings", key, default_settings[key])
|
var loaded_value = config.get_value(
|
||||||
|
"settings", key, default_settings[key]
|
||||||
|
)
|
||||||
# Validate loaded settings before applying
|
# Validate loaded settings before applying
|
||||||
if _validate_setting_value(key, loaded_value):
|
if _validate_setting_value(key, loaded_value):
|
||||||
settings[key] = loaded_value
|
settings[key] = loaded_value
|
||||||
@@ -45,10 +50,15 @@ func load_settings() -> void:
|
|||||||
"SettingsManager"
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
settings[key] = default_settings[key]
|
settings[key] = default_settings[key]
|
||||||
DebugManager.log_info("Settings loaded: " + str(settings), "SettingsManager")
|
DebugManager.log_info(
|
||||||
|
"Settings loaded: " + str(settings), "SettingsManager"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"No settings file found (Error code: %d), using defaults" % load_result,
|
(
|
||||||
|
"No settings file found (Error code: %d), using defaults"
|
||||||
|
% load_result
|
||||||
|
),
|
||||||
"SettingsManager"
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
settings = default_settings.duplicate()
|
settings = default_settings.duplicate()
|
||||||
@@ -58,7 +68,9 @@ func load_settings() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _apply_all_settings():
|
func _apply_all_settings():
|
||||||
DebugManager.log_info("Applying settings: " + str(settings), "SettingsManager")
|
DebugManager.log_info(
|
||||||
|
"Applying settings: " + str(settings), "SettingsManager"
|
||||||
|
)
|
||||||
|
|
||||||
# Apply language setting
|
# Apply language setting
|
||||||
if "language" in settings:
|
if "language" in settings:
|
||||||
@@ -70,24 +82,33 @@ func _apply_all_settings():
|
|||||||
var sfx_bus = AudioServer.get_bus_index("SFX")
|
var sfx_bus = AudioServer.get_bus_index("SFX")
|
||||||
|
|
||||||
if master_bus >= 0 and "master_volume" in settings:
|
if master_bus >= 0 and "master_volume" in settings:
|
||||||
AudioServer.set_bus_volume_db(master_bus, linear_to_db(settings["master_volume"]))
|
AudioServer.set_bus_volume_db(
|
||||||
|
master_bus, linear_to_db(settings["master_volume"])
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Master audio bus not found or master_volume setting missing", "SettingsManager"
|
"Master audio bus not found or master_volume setting missing",
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
if music_bus >= 0 and "music_volume" in settings:
|
if music_bus >= 0 and "music_volume" in settings:
|
||||||
AudioServer.set_bus_volume_db(music_bus, linear_to_db(settings["music_volume"]))
|
AudioServer.set_bus_volume_db(
|
||||||
|
music_bus, linear_to_db(settings["music_volume"])
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Music audio bus not found or music_volume setting missing", "SettingsManager"
|
"Music audio bus not found or music_volume setting missing",
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
if sfx_bus >= 0 and "sfx_volume" in settings:
|
if sfx_bus >= 0 and "sfx_volume" in settings:
|
||||||
AudioServer.set_bus_volume_db(sfx_bus, linear_to_db(settings["sfx_volume"]))
|
AudioServer.set_bus_volume_db(
|
||||||
|
sfx_bus, linear_to_db(settings["sfx_volume"])
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"SFX audio bus not found or sfx_volume setting missing", "SettingsManager"
|
"SFX audio bus not found or sfx_volume setting missing",
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -99,7 +120,8 @@ func save_settings():
|
|||||||
var save_result = config.save(SETTINGS_FILE)
|
var save_result = config.save(SETTINGS_FILE)
|
||||||
if save_result != OK:
|
if save_result != OK:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Failed to save settings (Error code: %d)" % save_result, "SettingsManager"
|
"Failed to save settings (Error code: %d)" % save_result,
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -119,7 +141,8 @@ func set_setting(key: String, value) -> bool:
|
|||||||
# Validate value type and range based on key
|
# Validate value type and range based on key
|
||||||
if not _validate_setting_value(key, value):
|
if not _validate_setting_value(key, value):
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Invalid value for setting '%s': %s" % [key, str(value)], "SettingsManager"
|
"Invalid value for setting '%s': %s" % [key, str(value)],
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -157,7 +180,8 @@ func _validate_volume_setting(key: String, value) -> bool:
|
|||||||
# Check for NaN and infinity
|
# Check for NaN and infinity
|
||||||
if is_nan(float_value) or is_inf(float_value):
|
if is_nan(float_value) or is_inf(float_value):
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Invalid float value for %s: %s" % [key, str(value)], "SettingsManager"
|
"Invalid float value for %s: %s" % [key, str(value)],
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
# Range validation
|
# Range validation
|
||||||
@@ -170,7 +194,8 @@ func _validate_language_setting(value) -> bool:
|
|||||||
# Prevent extremely long strings
|
# Prevent extremely long strings
|
||||||
if value.length() > MAX_SETTING_STRING_LENGTH:
|
if value.length() > MAX_SETTING_STRING_LENGTH:
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Language code too long: %d characters" % value.length(), "SettingsManager"
|
"Language code too long: %d characters" % value.length(),
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
# Check for valid characters (alphanumeric and common separators only)
|
# Check for valid characters (alphanumeric and common separators only)
|
||||||
@@ -178,11 +203,15 @@ func _validate_language_setting(value) -> bool:
|
|||||||
regex.compile("^[a-zA-Z0-9_-]+$")
|
regex.compile("^[a-zA-Z0-9_-]+$")
|
||||||
if not regex.search(value):
|
if not regex.search(value):
|
||||||
DebugManager.log_warn(
|
DebugManager.log_warn(
|
||||||
"Language code contains invalid characters: %s" % value, "SettingsManager"
|
"Language code contains invalid characters: %s" % value,
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
# Check if language is supported
|
# Check if language is supported
|
||||||
if languages_data.has("languages") and languages_data.languages is Dictionary:
|
if (
|
||||||
|
languages_data.has("languages")
|
||||||
|
and languages_data.languages is Dictionary
|
||||||
|
):
|
||||||
return value in languages_data.languages
|
return value in languages_data.languages
|
||||||
# Fallback to basic validation if languages not loaded
|
# Fallback to basic validation if languages not loaded
|
||||||
return value in ["en", "ru"]
|
return value in ["en", "ru"]
|
||||||
@@ -192,7 +221,9 @@ func _validate_default_setting(key: String, value) -> bool:
|
|||||||
# Default validation: accept if type matches default setting type
|
# Default validation: accept if type matches default setting type
|
||||||
var default_value = default_settings.get(key)
|
var default_value = default_settings.get(key)
|
||||||
if default_value == null:
|
if default_value == null:
|
||||||
DebugManager.log_warn("Unknown setting key in validation: %s" % key, "SettingsManager")
|
DebugManager.log_warn(
|
||||||
|
"Unknown setting key in validation: %s" % key, "SettingsManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
return typeof(value) == typeof(default_value)
|
return typeof(value) == typeof(default_value)
|
||||||
|
|
||||||
@@ -210,7 +241,9 @@ func _apply_setting_side_effect(key: String, value) -> void:
|
|||||||
AudioManager.update_music_volume(value)
|
AudioManager.update_music_volume(value)
|
||||||
"sfx_volume":
|
"sfx_volume":
|
||||||
if AudioServer.get_bus_index("SFX") >= 0:
|
if AudioServer.get_bus_index("SFX") >= 0:
|
||||||
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("SFX"), linear_to_db(value))
|
AudioServer.set_bus_volume_db(
|
||||||
|
AudioServer.get_bus_index("SFX"), linear_to_db(value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func load_languages():
|
func load_languages():
|
||||||
@@ -230,7 +263,11 @@ func load_languages():
|
|||||||
|
|
||||||
languages_data = parsed_data
|
languages_data = parsed_data
|
||||||
DebugManager.log_info(
|
DebugManager.log_info(
|
||||||
"Languages loaded successfully: " + str(languages_data.languages.keys()), "SettingsManager"
|
(
|
||||||
|
"Languages loaded successfully: "
|
||||||
|
+ str(languages_data.languages.keys())
|
||||||
|
),
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -239,7 +276,8 @@ func _load_languages_file() -> String:
|
|||||||
if not file:
|
if not file:
|
||||||
var error_code = FileAccess.get_open_error()
|
var error_code = FileAccess.get_open_error()
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Could not open languages.json (Error code: %d)" % error_code, "SettingsManager"
|
"Could not open languages.json (Error code: %d)" % error_code,
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -247,14 +285,19 @@ func _load_languages_file() -> String:
|
|||||||
var file_size = file.get_length()
|
var file_size = file.get_length()
|
||||||
if file_size > MAX_JSON_FILE_SIZE:
|
if file_size > MAX_JSON_FILE_SIZE:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Languages.json file too large: %d bytes (max %d)" % [file_size, MAX_JSON_FILE_SIZE],
|
(
|
||||||
|
"Languages.json file too large: %d bytes (max %d)"
|
||||||
|
% [file_size, MAX_JSON_FILE_SIZE]
|
||||||
|
),
|
||||||
"SettingsManager"
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
file.close()
|
file.close()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if file_size == 0:
|
if file_size == 0:
|
||||||
DebugManager.log_error("Languages.json file is empty", "SettingsManager")
|
DebugManager.log_error(
|
||||||
|
"Languages.json file is empty", "SettingsManager"
|
||||||
|
)
|
||||||
file.close()
|
file.close()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -264,7 +307,8 @@ func _load_languages_file() -> String:
|
|||||||
|
|
||||||
if file_error != OK:
|
if file_error != OK:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Error reading languages.json (Error code: %d)" % file_error, "SettingsManager"
|
"Error reading languages.json (Error code: %d)" % file_error,
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -274,27 +318,36 @@ func _load_languages_file() -> String:
|
|||||||
func _parse_languages_json(json_string: String) -> Dictionary:
|
func _parse_languages_json(json_string: String) -> Dictionary:
|
||||||
# Validate the JSON string is not empty
|
# Validate the JSON string is not empty
|
||||||
if json_string.is_empty():
|
if json_string.is_empty():
|
||||||
DebugManager.log_error("Languages.json contains empty content", "SettingsManager")
|
DebugManager.log_error(
|
||||||
|
"Languages.json contains empty content", "SettingsManager"
|
||||||
|
)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
var json = JSON.new()
|
var json = JSON.new()
|
||||||
var parse_result = json.parse(json_string)
|
var parse_result = json.parse(json_string)
|
||||||
if parse_result != OK:
|
if parse_result != OK:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"JSON parsing failed at line %d: %s" % [json.error_line, json.error_string],
|
(
|
||||||
|
"JSON parsing failed at line %d: %s"
|
||||||
|
% [json.error_line, json.error_string]
|
||||||
|
),
|
||||||
"SettingsManager"
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
if not json.data or not json.data is Dictionary:
|
if not json.data or not json.data is Dictionary:
|
||||||
DebugManager.log_error("Invalid JSON data structure in languages.json", "SettingsManager")
|
DebugManager.log_error(
|
||||||
|
"Invalid JSON data structure in languages.json", "SettingsManager"
|
||||||
|
)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
return json.data
|
return json.data
|
||||||
|
|
||||||
|
|
||||||
func _load_default_languages_with_fallback(reason: String):
|
func _load_default_languages_with_fallback(reason: String):
|
||||||
DebugManager.log_warn("Loading default languages due to: " + reason, "SettingsManager")
|
DebugManager.log_warn(
|
||||||
|
"Loading default languages due to: " + reason, "SettingsManager"
|
||||||
|
)
|
||||||
_load_default_languages()
|
_load_default_languages()
|
||||||
|
|
||||||
|
|
||||||
@@ -302,9 +355,14 @@ func _load_default_languages():
|
|||||||
# Fallback language data when JSON file fails to load
|
# Fallback language data when JSON file fails to load
|
||||||
languages_data = {
|
languages_data = {
|
||||||
"languages":
|
"languages":
|
||||||
{"en": {"name": "English", "flag": "🇺🇸"}, "ru": {"name": "Русский", "flag": "🇷🇺"}}
|
{
|
||||||
|
"en": {"name": "English", "flag": "🇺🇸"},
|
||||||
|
"ru": {"name": "Русский", "flag": "🇷🇺"}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DebugManager.log_info("Default languages loaded as fallback", "SettingsManager")
|
DebugManager.log_info(
|
||||||
|
"Default languages loaded as fallback", "SettingsManager"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func get_languages_data():
|
func get_languages_data():
|
||||||
@@ -312,15 +370,21 @@ func get_languages_data():
|
|||||||
|
|
||||||
|
|
||||||
func reset_settings_to_defaults() -> void:
|
func reset_settings_to_defaults() -> void:
|
||||||
DebugManager.log_info("Resetting all settings to defaults", "SettingsManager")
|
DebugManager.log_info(
|
||||||
|
"Resetting all settings to defaults", "SettingsManager"
|
||||||
|
)
|
||||||
for key in default_settings.keys():
|
for key in default_settings.keys():
|
||||||
settings[key] = default_settings[key]
|
settings[key] = default_settings[key]
|
||||||
_apply_setting_side_effect(key, settings[key])
|
_apply_setting_side_effect(key, settings[key])
|
||||||
var save_success = save_settings()
|
var save_success = save_settings()
|
||||||
if save_success:
|
if save_success:
|
||||||
DebugManager.log_info("Settings reset completed successfully", "SettingsManager")
|
DebugManager.log_info(
|
||||||
|
"Settings reset completed successfully", "SettingsManager"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
DebugManager.log_error("Failed to save reset settings", "SettingsManager")
|
DebugManager.log_error(
|
||||||
|
"Failed to save reset settings", "SettingsManager"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _validate_languages_structure(data: Dictionary) -> bool:
|
func _validate_languages_structure(data: Dictionary) -> bool:
|
||||||
@@ -344,16 +408,22 @@ func _validate_languages_structure(data: Dictionary) -> bool:
|
|||||||
func _validate_languages_root_structure(data: Dictionary) -> bool:
|
func _validate_languages_root_structure(data: Dictionary) -> bool:
|
||||||
"""Validate the root structure of languages data"""
|
"""Validate the root structure of languages data"""
|
||||||
if not data.has("languages"):
|
if not data.has("languages"):
|
||||||
DebugManager.log_error("Languages.json missing 'languages' key", "SettingsManager")
|
DebugManager.log_error(
|
||||||
|
"Languages.json missing 'languages' key", "SettingsManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
var languages = data["languages"]
|
var languages = data["languages"]
|
||||||
if not languages is Dictionary:
|
if not languages is Dictionary:
|
||||||
DebugManager.log_error("'languages' is not a dictionary", "SettingsManager")
|
DebugManager.log_error(
|
||||||
|
"'languages' is not a dictionary", "SettingsManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if languages.is_empty():
|
if languages.is_empty():
|
||||||
DebugManager.log_error("Languages dictionary is empty", "SettingsManager")
|
DebugManager.log_error(
|
||||||
|
"Languages dictionary is empty", "SettingsManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -367,28 +437,35 @@ func _validate_individual_languages(languages: Dictionary) -> bool:
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
func _validate_single_language_entry(lang_code: Variant, lang_data: Variant) -> bool:
|
func _validate_single_language_entry(
|
||||||
|
lang_code: Variant, lang_data: Variant
|
||||||
|
) -> bool:
|
||||||
"""Validate a single language entry"""
|
"""Validate a single language entry"""
|
||||||
if not lang_code is String:
|
if not lang_code is String:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Language code is not a string: %s" % str(lang_code), "SettingsManager"
|
"Language code is not a string: %s" % str(lang_code),
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if lang_code.length() > MAX_SETTING_STRING_LENGTH:
|
if lang_code.length() > MAX_SETTING_STRING_LENGTH:
|
||||||
DebugManager.log_error("Language code too long: %s" % lang_code, "SettingsManager")
|
DebugManager.log_error(
|
||||||
|
"Language code too long: %s" % lang_code, "SettingsManager"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if not lang_data is Dictionary:
|
if not lang_data is Dictionary:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Language data for '%s' is not a dictionary" % lang_code, "SettingsManager"
|
"Language data for '%s' is not a dictionary" % lang_code,
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Validate required fields in language data
|
# Validate required fields in language data
|
||||||
if not lang_data.has("name") or not lang_data["name"] is String:
|
if not lang_data.has("name") or not lang_data["name"] is String:
|
||||||
DebugManager.log_error(
|
DebugManager.log_error(
|
||||||
"Language '%s' missing valid 'name' field" % lang_code, "SettingsManager"
|
"Language '%s' missing valid 'name' field" % lang_code,
|
||||||
|
"SettingsManager"
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|||||||
@@ -69,12 +69,17 @@ func test_basic_functionality():
|
|||||||
|
|
||||||
# Test that AudioManager has expected methods
|
# Test that AudioManager has expected methods
|
||||||
var expected_methods = ["update_music_volume", "play_ui_click"]
|
var expected_methods = ["update_music_volume", "play_ui_click"]
|
||||||
TestHelperClass.assert_has_methods(audio_manager, expected_methods, "AudioManager methods")
|
TestHelperClass.assert_has_methods(
|
||||||
|
audio_manager, expected_methods, "AudioManager methods"
|
||||||
|
)
|
||||||
|
|
||||||
# Test that AudioManager has expected constants
|
# Test that AudioManager has expected constants
|
||||||
TestHelperClass.assert_true("MUSIC_PATH" in audio_manager, "MUSIC_PATH constant exists")
|
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"UI_CLICK_SOUND_PATH" in audio_manager, "UI_CLICK_SOUND_PATH constant exists"
|
"MUSIC_PATH" in audio_manager, "MUSIC_PATH constant exists"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
"UI_CLICK_SOUND_PATH" in audio_manager,
|
||||||
|
"UI_CLICK_SOUND_PATH constant exists"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -85,9 +90,12 @@ func test_audio_constants():
|
|||||||
var music_path = audio_manager.MUSIC_PATH
|
var music_path = audio_manager.MUSIC_PATH
|
||||||
var click_path = audio_manager.UI_CLICK_SOUND_PATH
|
var click_path = audio_manager.UI_CLICK_SOUND_PATH
|
||||||
|
|
||||||
TestHelperClass.assert_true(music_path.begins_with("res://"), "Music path uses res:// protocol")
|
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
click_path.begins_with("res://"), "Click sound path uses res:// protocol"
|
music_path.begins_with("res://"), "Music path uses res:// protocol"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
click_path.begins_with("res://"),
|
||||||
|
"Click sound path uses res:// protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test file extensions
|
# Test file extensions
|
||||||
@@ -101,11 +109,17 @@ func test_audio_constants():
|
|||||||
if click_path.ends_with(ext):
|
if click_path.ends_with(ext):
|
||||||
click_has_valid_ext = true
|
click_has_valid_ext = true
|
||||||
|
|
||||||
TestHelperClass.assert_true(music_has_valid_ext, "Music file has valid audio extension")
|
TestHelperClass.assert_true(
|
||||||
TestHelperClass.assert_true(click_has_valid_ext, "Click sound has valid audio extension")
|
music_has_valid_ext, "Music file has valid audio extension"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
click_has_valid_ext, "Click sound has valid audio extension"
|
||||||
|
)
|
||||||
|
|
||||||
# Test that audio files exist
|
# Test that audio files exist
|
||||||
TestHelperClass.assert_true(ResourceLoader.exists(music_path), "Music file exists at path")
|
TestHelperClass.assert_true(
|
||||||
|
ResourceLoader.exists(music_path), "Music file exists at path"
|
||||||
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
ResourceLoader.exists(click_path), "Click sound file exists at path"
|
ResourceLoader.exists(click_path), "Click sound file exists at path"
|
||||||
)
|
)
|
||||||
@@ -115,9 +129,12 @@ func test_audio_player_initialization():
|
|||||||
TestHelperClass.print_step("Audio Player Initialization")
|
TestHelperClass.print_step("Audio Player Initialization")
|
||||||
|
|
||||||
# Test music player initialization
|
# Test music player initialization
|
||||||
TestHelperClass.assert_not_null(audio_manager.music_player, "Music player is initialized")
|
TestHelperClass.assert_not_null(
|
||||||
|
audio_manager.music_player, "Music player is initialized"
|
||||||
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
audio_manager.music_player is AudioStreamPlayer, "Music player is AudioStreamPlayer type"
|
audio_manager.music_player is AudioStreamPlayer,
|
||||||
|
"Music player is AudioStreamPlayer type"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
audio_manager.music_player.get_parent() == audio_manager,
|
audio_manager.music_player.get_parent() == audio_manager,
|
||||||
@@ -125,7 +142,9 @@ func test_audio_player_initialization():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Test UI click player initialization
|
# Test UI click player initialization
|
||||||
TestHelperClass.assert_not_null(audio_manager.ui_click_player, "UI click player is initialized")
|
TestHelperClass.assert_not_null(
|
||||||
|
audio_manager.ui_click_player, "UI click player is initialized"
|
||||||
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
audio_manager.ui_click_player is AudioStreamPlayer,
|
audio_manager.ui_click_player is AudioStreamPlayer,
|
||||||
"UI click player is AudioStreamPlayer type"
|
"UI click player is AudioStreamPlayer type"
|
||||||
@@ -137,10 +156,14 @@ func test_audio_player_initialization():
|
|||||||
|
|
||||||
# Test audio bus assignment
|
# Test audio bus assignment
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"Music", audio_manager.music_player.bus, "Music player assigned to Music bus"
|
"Music",
|
||||||
|
audio_manager.music_player.bus,
|
||||||
|
"Music player assigned to Music bus"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"SFX", audio_manager.ui_click_player.bus, "UI click player assigned to SFX bus"
|
"SFX",
|
||||||
|
audio_manager.ui_click_player.bus,
|
||||||
|
"UI click player assigned to SFX bus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -148,26 +171,38 @@ func test_stream_loading_and_validation():
|
|||||||
TestHelperClass.print_step("Stream Loading and Validation")
|
TestHelperClass.print_step("Stream Loading and Validation")
|
||||||
|
|
||||||
# Test music stream loading
|
# Test music stream loading
|
||||||
TestHelperClass.assert_not_null(audio_manager.music_player.stream, "Music stream is loaded")
|
TestHelperClass.assert_not_null(
|
||||||
|
audio_manager.music_player.stream, "Music stream is loaded"
|
||||||
|
)
|
||||||
if audio_manager.music_player.stream:
|
if audio_manager.music_player.stream:
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
audio_manager.music_player.stream is AudioStream, "Music stream is AudioStream type"
|
audio_manager.music_player.stream is AudioStream,
|
||||||
|
"Music stream is AudioStream type"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test click stream loading
|
# Test click stream loading
|
||||||
TestHelperClass.assert_not_null(audio_manager.click_stream, "Click stream is loaded")
|
TestHelperClass.assert_not_null(
|
||||||
|
audio_manager.click_stream, "Click stream is loaded"
|
||||||
|
)
|
||||||
if audio_manager.click_stream:
|
if audio_manager.click_stream:
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
audio_manager.click_stream is AudioStream, "Click stream is AudioStream type"
|
audio_manager.click_stream is AudioStream,
|
||||||
|
"Click stream is AudioStream type"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test stream resource loading directly
|
# Test stream resource loading directly
|
||||||
var loaded_music = load(audio_manager.MUSIC_PATH)
|
var loaded_music = load(audio_manager.MUSIC_PATH)
|
||||||
TestHelperClass.assert_not_null(loaded_music, "Music resource loads successfully")
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_true(loaded_music is AudioStream, "Loaded music is AudioStream type")
|
loaded_music, "Music resource loads successfully"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
loaded_music is AudioStream, "Loaded music is AudioStream type"
|
||||||
|
)
|
||||||
|
|
||||||
var loaded_click = load(audio_manager.UI_CLICK_SOUND_PATH)
|
var loaded_click = load(audio_manager.UI_CLICK_SOUND_PATH)
|
||||||
TestHelperClass.assert_not_null(loaded_click, "Click resource loads successfully")
|
TestHelperClass.assert_not_null(
|
||||||
|
loaded_click, "Click resource loads successfully"
|
||||||
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
loaded_click is AudioStream, "Loaded click sound is AudioStream type"
|
loaded_click is AudioStream, "Loaded click sound is AudioStream type"
|
||||||
)
|
)
|
||||||
@@ -186,7 +221,9 @@ func test_audio_bus_configuration():
|
|||||||
# Test player bus assignments match actual AudioServer buses
|
# Test player bus assignments match actual AudioServer buses
|
||||||
if music_bus_index >= 0:
|
if music_bus_index >= 0:
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"Music", audio_manager.music_player.bus, "Music player correctly assigned to Music bus"
|
"Music",
|
||||||
|
audio_manager.music_player.bus,
|
||||||
|
"Music player correctly assigned to Music bus"
|
||||||
)
|
)
|
||||||
|
|
||||||
if sfx_bus_index >= 0:
|
if sfx_bus_index >= 0:
|
||||||
@@ -208,20 +245,27 @@ func test_volume_management():
|
|||||||
# Test volume update to valid range
|
# Test volume update to valid range
|
||||||
audio_manager.update_music_volume(0.5)
|
audio_manager.update_music_volume(0.5)
|
||||||
TestHelperClass.assert_float_equal(
|
TestHelperClass.assert_float_equal(
|
||||||
linear_to_db(0.5), audio_manager.music_player.volume_db, 0.001, "Music volume set correctly"
|
linear_to_db(0.5),
|
||||||
|
audio_manager.music_player.volume_db,
|
||||||
|
0.001,
|
||||||
|
"Music volume set correctly"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test volume update to zero (should stop music)
|
# Test volume update to zero (should stop music)
|
||||||
audio_manager.update_music_volume(0.0)
|
audio_manager.update_music_volume(0.0)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
linear_to_db(0.0), audio_manager.music_player.volume_db, "Zero volume set correctly"
|
linear_to_db(0.0),
|
||||||
|
audio_manager.music_player.volume_db,
|
||||||
|
"Zero volume set correctly"
|
||||||
)
|
)
|
||||||
# Note: We don't test playing state as it depends on initialization conditions
|
# Note: We don't test playing state as it depends on initialization conditions
|
||||||
|
|
||||||
# Test volume update to maximum
|
# Test volume update to maximum
|
||||||
audio_manager.update_music_volume(1.0)
|
audio_manager.update_music_volume(1.0)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
linear_to_db(1.0), audio_manager.music_player.volume_db, "Maximum volume set correctly"
|
linear_to_db(1.0),
|
||||||
|
audio_manager.music_player.volume_db,
|
||||||
|
"Maximum volume set correctly"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test volume range validation
|
# Test volume range validation
|
||||||
@@ -248,7 +292,8 @@ func test_music_playback_control():
|
|||||||
audio_manager.music_player, "Music player exists for playback testing"
|
audio_manager.music_player, "Music player exists for playback testing"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_not_null(
|
TestHelperClass.assert_not_null(
|
||||||
audio_manager.music_player.stream, "Music player has stream for playback testing"
|
audio_manager.music_player.stream,
|
||||||
|
"Music player has stream for playback testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test playback state management
|
# Test playback state management
|
||||||
@@ -279,8 +324,12 @@ func test_ui_sound_effects():
|
|||||||
TestHelperClass.print_step("UI Sound Effects")
|
TestHelperClass.print_step("UI Sound Effects")
|
||||||
|
|
||||||
# Test UI click functionality
|
# Test UI click functionality
|
||||||
TestHelperClass.assert_not_null(audio_manager.ui_click_player, "UI click player exists")
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_not_null(audio_manager.click_stream, "Click stream is loaded")
|
audio_manager.ui_click_player, "UI click player exists"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_not_null(
|
||||||
|
audio_manager.click_stream, "Click stream is loaded"
|
||||||
|
)
|
||||||
|
|
||||||
# Test that play_ui_click can be called safely
|
# Test that play_ui_click can be called safely
|
||||||
var original_stream = audio_manager.ui_click_player.stream
|
var original_stream = audio_manager.ui_click_player.stream
|
||||||
@@ -296,7 +345,9 @@ func test_ui_sound_effects():
|
|||||||
# Test multiple rapid clicks (should not cause errors)
|
# Test multiple rapid clicks (should not cause errors)
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
audio_manager.play_ui_click()
|
audio_manager.play_ui_click()
|
||||||
TestHelperClass.assert_true(true, "Rapid click %d handled safely" % (i + 1))
|
TestHelperClass.assert_true(
|
||||||
|
true, "Rapid click %d handled safely" % (i + 1)
|
||||||
|
)
|
||||||
|
|
||||||
# Test click with null stream
|
# Test click with null stream
|
||||||
var backup_stream = audio_manager.click_stream
|
var backup_stream = audio_manager.click_stream
|
||||||
@@ -315,7 +366,9 @@ func test_stream_loop_configuration():
|
|||||||
if music_stream is AudioStreamWAV:
|
if music_stream is AudioStreamWAV:
|
||||||
# For WAV files, check loop mode
|
# For WAV files, check loop mode
|
||||||
var has_loop_mode = "loop_mode" in music_stream
|
var has_loop_mode = "loop_mode" in music_stream
|
||||||
TestHelperClass.assert_true(has_loop_mode, "WAV stream has loop_mode property")
|
TestHelperClass.assert_true(
|
||||||
|
has_loop_mode, "WAV stream has loop_mode property"
|
||||||
|
)
|
||||||
if has_loop_mode:
|
if has_loop_mode:
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
AudioStreamWAV.LOOP_FORWARD,
|
AudioStreamWAV.LOOP_FORWARD,
|
||||||
@@ -325,12 +378,18 @@ func test_stream_loop_configuration():
|
|||||||
elif music_stream is AudioStreamOggVorbis:
|
elif music_stream is AudioStreamOggVorbis:
|
||||||
# For OGG files, check loop property
|
# For OGG files, check loop property
|
||||||
var has_loop = "loop" in music_stream
|
var has_loop = "loop" in music_stream
|
||||||
TestHelperClass.assert_true(has_loop, "OGG stream has loop property")
|
TestHelperClass.assert_true(
|
||||||
|
has_loop, "OGG stream has loop property"
|
||||||
|
)
|
||||||
if has_loop:
|
if has_loop:
|
||||||
TestHelperClass.assert_true(music_stream.loop, "OGG stream loop enabled")
|
TestHelperClass.assert_true(
|
||||||
|
music_stream.loop, "OGG stream loop enabled"
|
||||||
|
)
|
||||||
|
|
||||||
# Test loop configuration for different stream types
|
# Test loop configuration for different stream types
|
||||||
TestHelperClass.assert_true(true, "Stream loop configuration tested based on type")
|
TestHelperClass.assert_true(
|
||||||
|
true, "Stream loop configuration tested based on type"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_error_handling():
|
func test_error_handling():
|
||||||
@@ -341,15 +400,18 @@ func test_error_handling():
|
|||||||
|
|
||||||
# Test that AudioManager initializes even with potential issues
|
# Test that AudioManager initializes even with potential issues
|
||||||
TestHelperClass.assert_not_null(
|
TestHelperClass.assert_not_null(
|
||||||
audio_manager, "AudioManager initializes despite potential resource issues"
|
audio_manager,
|
||||||
|
"AudioManager initializes despite potential resource issues"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test that players are still created even if streams fail to load
|
# Test that players are still created even if streams fail to load
|
||||||
TestHelperClass.assert_not_null(
|
TestHelperClass.assert_not_null(
|
||||||
audio_manager.music_player, "Music player created regardless of stream loading"
|
audio_manager.music_player,
|
||||||
|
"Music player created regardless of stream loading"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_not_null(
|
TestHelperClass.assert_not_null(
|
||||||
audio_manager.ui_click_player, "UI click player created regardless of stream loading"
|
audio_manager.ui_click_player,
|
||||||
|
"UI click player created regardless of stream loading"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test null stream handling in play_ui_click
|
# Test null stream handling in play_ui_click
|
||||||
@@ -358,7 +420,9 @@ func test_error_handling():
|
|||||||
|
|
||||||
# This should not crash
|
# This should not crash
|
||||||
audio_manager.play_ui_click()
|
audio_manager.play_ui_click()
|
||||||
TestHelperClass.assert_true(true, "play_ui_click handles null stream gracefully")
|
TestHelperClass.assert_true(
|
||||||
|
true, "play_ui_click handles null stream gracefully"
|
||||||
|
)
|
||||||
|
|
||||||
# Restore original stream
|
# Restore original stream
|
||||||
audio_manager.click_stream = original_click_stream
|
audio_manager.click_stream = original_click_stream
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ func test_basic_functionality():
|
|||||||
|
|
||||||
# Test that GameManager has expected properties
|
# Test that GameManager has expected properties
|
||||||
TestHelperClass.assert_has_properties(
|
TestHelperClass.assert_has_properties(
|
||||||
game_manager, ["pending_gameplay_mode", "is_changing_scene"], "GameManager properties"
|
game_manager,
|
||||||
|
["pending_gameplay_mode", "is_changing_scene"],
|
||||||
|
"GameManager properties"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test that GameManager has expected methods
|
# Test that GameManager has expected methods
|
||||||
@@ -70,13 +72,19 @@ func test_basic_functionality():
|
|||||||
"save_game",
|
"save_game",
|
||||||
"exit_to_main_menu"
|
"exit_to_main_menu"
|
||||||
]
|
]
|
||||||
TestHelperClass.assert_has_methods(game_manager, expected_methods, "GameManager methods")
|
TestHelperClass.assert_has_methods(
|
||||||
|
game_manager, expected_methods, "GameManager methods"
|
||||||
|
)
|
||||||
|
|
||||||
# Test initial state
|
# Test initial state
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"match3", game_manager.pending_gameplay_mode, "Default pending gameplay mode"
|
"match3",
|
||||||
|
game_manager.pending_gameplay_mode,
|
||||||
|
"Default pending gameplay mode"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_false(
|
||||||
|
game_manager.is_changing_scene, "Initial scene change flag"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_false(game_manager.is_changing_scene, "Initial scene change flag")
|
|
||||||
|
|
||||||
|
|
||||||
func test_scene_constants():
|
func test_scene_constants():
|
||||||
@@ -97,15 +105,23 @@ func test_scene_constants():
|
|||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
game_path.begins_with("res://"), "Game scene path uses res:// protocol"
|
game_path.begins_with("res://"), "Game scene path uses res:// protocol"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(game_path.ends_with(".tscn"), "Game scene path has .tscn extension")
|
TestHelperClass.assert_true(
|
||||||
|
game_path.ends_with(".tscn"), "Game scene path has .tscn extension"
|
||||||
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
main_path.begins_with("res://"), "Main scene path uses res:// protocol"
|
main_path.begins_with("res://"), "Main scene path uses res:// protocol"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(main_path.ends_with(".tscn"), "Main scene path has .tscn extension")
|
TestHelperClass.assert_true(
|
||||||
|
main_path.ends_with(".tscn"), "Main scene path has .tscn extension"
|
||||||
|
)
|
||||||
|
|
||||||
# Test that scene files exist
|
# Test that scene files exist
|
||||||
TestHelperClass.assert_true(ResourceLoader.exists(game_path), "Game scene file exists at path")
|
TestHelperClass.assert_true(
|
||||||
TestHelperClass.assert_true(ResourceLoader.exists(main_path), "Main scene file exists at path")
|
ResourceLoader.exists(game_path), "Game scene file exists at path"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
ResourceLoader.exists(main_path), "Main scene file exists at path"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_input_validation():
|
func test_input_validation():
|
||||||
@@ -118,38 +134,50 @@ func test_input_validation():
|
|||||||
# Test empty string validation
|
# Test empty string validation
|
||||||
game_manager.start_game_with_mode("")
|
game_manager.start_game_with_mode("")
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
original_mode, game_manager.pending_gameplay_mode, "Empty string mode rejected"
|
original_mode,
|
||||||
|
game_manager.pending_gameplay_mode,
|
||||||
|
"Empty string mode rejected"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_false(
|
TestHelperClass.assert_false(
|
||||||
game_manager.is_changing_scene, "Scene change flag unchanged after empty mode"
|
game_manager.is_changing_scene,
|
||||||
|
"Scene change flag unchanged after empty mode"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test null validation - GameManager expects String, so this tests the type safety
|
# Test null validation - GameManager expects String, so this tests the type safety
|
||||||
# Note: In Godot 4.4, passing null to String parameter causes script error as expected
|
# Note: In Godot 4.4, passing null to String parameter causes script error as expected
|
||||||
# The function properly validates empty strings instead
|
# The function properly validates empty strings instead
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
original_mode, game_manager.pending_gameplay_mode, "Mode preserved after empty string test"
|
original_mode,
|
||||||
|
game_manager.pending_gameplay_mode,
|
||||||
|
"Mode preserved after empty string test"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_false(
|
TestHelperClass.assert_false(
|
||||||
game_manager.is_changing_scene, "Scene change flag unchanged after validation tests"
|
game_manager.is_changing_scene,
|
||||||
|
"Scene change flag unchanged after validation tests"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test invalid mode validation
|
# Test invalid mode validation
|
||||||
game_manager.start_game_with_mode("invalid_mode")
|
game_manager.start_game_with_mode("invalid_mode")
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
original_mode, game_manager.pending_gameplay_mode, "Invalid mode rejected"
|
original_mode,
|
||||||
|
game_manager.pending_gameplay_mode,
|
||||||
|
"Invalid mode rejected"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_false(
|
TestHelperClass.assert_false(
|
||||||
game_manager.is_changing_scene, "Scene change flag unchanged after invalid mode"
|
game_manager.is_changing_scene,
|
||||||
|
"Scene change flag unchanged after invalid mode"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test case sensitivity
|
# Test case sensitivity
|
||||||
game_manager.start_game_with_mode("MATCH3")
|
game_manager.start_game_with_mode("MATCH3")
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
original_mode, game_manager.pending_gameplay_mode, "Case-sensitive mode validation"
|
original_mode,
|
||||||
|
game_manager.pending_gameplay_mode,
|
||||||
|
"Case-sensitive mode validation"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_false(
|
TestHelperClass.assert_false(
|
||||||
game_manager.is_changing_scene, "Scene change flag unchanged after wrong case"
|
game_manager.is_changing_scene,
|
||||||
|
"Scene change flag unchanged after wrong case"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -165,14 +193,19 @@ func test_race_condition_protection():
|
|||||||
|
|
||||||
# Verify second request was rejected
|
# Verify second request was rejected
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
original_mode, game_manager.pending_gameplay_mode, "Concurrent scene change blocked"
|
original_mode,
|
||||||
|
game_manager.pending_gameplay_mode,
|
||||||
|
"Concurrent scene change blocked"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
game_manager.is_changing_scene, "Scene change flag preserved"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(game_manager.is_changing_scene, "Scene change flag preserved")
|
|
||||||
|
|
||||||
# Test exit to main menu during scene change
|
# Test exit to main menu during scene change
|
||||||
game_manager.exit_to_main_menu()
|
game_manager.exit_to_main_menu()
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
game_manager.is_changing_scene, "Exit request blocked during scene change"
|
game_manager.is_changing_scene,
|
||||||
|
"Exit request blocked during scene change"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reset state
|
# Reset state
|
||||||
@@ -191,13 +224,17 @@ func test_gameplay_mode_validation():
|
|||||||
|
|
||||||
# Create a temporary mock to test validation
|
# Create a temporary mock to test validation
|
||||||
var test_mode_valid = mode in ["match3", "clickomania"]
|
var test_mode_valid = mode in ["match3", "clickomania"]
|
||||||
TestHelperClass.assert_true(test_mode_valid, "Valid mode accepted: " + mode)
|
TestHelperClass.assert_true(
|
||||||
|
test_mode_valid, "Valid mode accepted: " + mode
|
||||||
|
)
|
||||||
|
|
||||||
# Test whitelist enforcement
|
# Test whitelist enforcement
|
||||||
var invalid_modes = ["puzzle", "arcade", "adventure", "rpg", "action"]
|
var invalid_modes = ["puzzle", "arcade", "adventure", "rpg", "action"]
|
||||||
for mode in invalid_modes:
|
for mode in invalid_modes:
|
||||||
var test_mode_invalid = not (mode in ["match3", "clickomania"])
|
var test_mode_invalid = not (mode in ["match3", "clickomania"])
|
||||||
TestHelperClass.assert_true(test_mode_invalid, "Invalid mode rejected: " + mode)
|
TestHelperClass.assert_true(
|
||||||
|
test_mode_invalid, "Invalid mode rejected: " + mode
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_scene_transition_safety():
|
func test_scene_transition_safety():
|
||||||
@@ -209,12 +246,20 @@ func test_scene_transition_safety():
|
|||||||
|
|
||||||
# Test scene resource loading
|
# Test scene resource loading
|
||||||
var game_scene = load(game_scene_path)
|
var game_scene = load(game_scene_path)
|
||||||
TestHelperClass.assert_not_null(game_scene, "Game scene resource loads successfully")
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_true(game_scene is PackedScene, "Game scene is PackedScene type")
|
game_scene, "Game scene resource loads successfully"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
game_scene is PackedScene, "Game scene is PackedScene type"
|
||||||
|
)
|
||||||
|
|
||||||
var main_scene = load(main_scene_path)
|
var main_scene = load(main_scene_path)
|
||||||
TestHelperClass.assert_not_null(main_scene, "Main scene resource loads successfully")
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_true(main_scene is PackedScene, "Main scene is PackedScene type")
|
main_scene, "Main scene resource loads successfully"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
main_scene is PackedScene, "Main scene is PackedScene type"
|
||||||
|
)
|
||||||
|
|
||||||
# Test that current scene exists
|
# Test that current scene exists
|
||||||
TestHelperClass.assert_not_null(current_scene, "Current scene exists")
|
TestHelperClass.assert_not_null(current_scene, "Current scene exists")
|
||||||
@@ -234,10 +279,14 @@ func test_error_handling():
|
|||||||
# Verify state preservation after invalid inputs
|
# Verify state preservation after invalid inputs
|
||||||
game_manager.start_game_with_mode("")
|
game_manager.start_game_with_mode("")
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
original_changing, game_manager.is_changing_scene, "State preserved after empty mode error"
|
original_changing,
|
||||||
|
game_manager.is_changing_scene,
|
||||||
|
"State preserved after empty mode error"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
original_mode, game_manager.pending_gameplay_mode, "Mode preserved after empty mode error"
|
original_mode,
|
||||||
|
game_manager.pending_gameplay_mode,
|
||||||
|
"Mode preserved after empty mode error"
|
||||||
)
|
)
|
||||||
|
|
||||||
game_manager.start_game_with_mode("invalid")
|
game_manager.start_game_with_mode("invalid")
|
||||||
@@ -247,7 +296,9 @@ func test_error_handling():
|
|||||||
"State preserved after invalid mode error"
|
"State preserved after invalid mode error"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
original_mode, game_manager.pending_gameplay_mode, "Mode preserved after invalid mode error"
|
original_mode,
|
||||||
|
game_manager.pending_gameplay_mode,
|
||||||
|
"Mode preserved after invalid mode error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -263,9 +314,15 @@ func test_scene_method_validation():
|
|||||||
var has_set_global_score = mock_scene.has_method("set_global_score")
|
var has_set_global_score = mock_scene.has_method("set_global_score")
|
||||||
var has_get_global_score = mock_scene.has_method("get_global_score")
|
var has_get_global_score = mock_scene.has_method("get_global_score")
|
||||||
|
|
||||||
TestHelperClass.assert_false(has_set_gameplay_mode, "Mock scene lacks set_gameplay_mode method")
|
TestHelperClass.assert_false(
|
||||||
TestHelperClass.assert_false(has_set_global_score, "Mock scene lacks set_global_score method")
|
has_set_gameplay_mode, "Mock scene lacks set_gameplay_mode method"
|
||||||
TestHelperClass.assert_false(has_get_global_score, "Mock scene lacks get_global_score method")
|
)
|
||||||
|
TestHelperClass.assert_false(
|
||||||
|
has_set_global_score, "Mock scene lacks set_global_score method"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_false(
|
||||||
|
has_get_global_score, "Mock scene lacks get_global_score method"
|
||||||
|
)
|
||||||
|
|
||||||
# Clean up mock scene
|
# Clean up mock scene
|
||||||
mock_scene.queue_free()
|
mock_scene.queue_free()
|
||||||
@@ -284,7 +341,9 @@ func test_pending_mode_management():
|
|||||||
# This simulates what would happen in start_game_with_mode
|
# This simulates what would happen in start_game_with_mode
|
||||||
game_manager.pending_gameplay_mode = test_mode
|
game_manager.pending_gameplay_mode = test_mode
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
test_mode, game_manager.pending_gameplay_mode, "Pending mode set correctly"
|
test_mode,
|
||||||
|
game_manager.pending_gameplay_mode,
|
||||||
|
"Pending mode set correctly"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test mode preservation during errors
|
# Test mode preservation during errors
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ func setup_test_environment():
|
|||||||
|
|
||||||
# Load Match3 scene
|
# Load Match3 scene
|
||||||
match3_scene = load("res://scenes/game/gameplays/Match3Gameplay.tscn")
|
match3_scene = load("res://scenes/game/gameplays/Match3Gameplay.tscn")
|
||||||
TestHelperClass.assert_not_null(match3_scene, "Match3 scene loads successfully")
|
TestHelperClass.assert_not_null(
|
||||||
|
match3_scene, "Match3 scene loads successfully"
|
||||||
|
)
|
||||||
|
|
||||||
# Create test viewport for isolated testing
|
# Create test viewport for isolated testing
|
||||||
test_viewport = SubViewport.new()
|
test_viewport = SubViewport.new()
|
||||||
@@ -62,7 +64,9 @@ func setup_test_environment():
|
|||||||
if match3_scene:
|
if match3_scene:
|
||||||
match3_instance = match3_scene.instantiate()
|
match3_instance = match3_scene.instantiate()
|
||||||
test_viewport.add_child(match3_instance)
|
test_viewport.add_child(match3_instance)
|
||||||
TestHelperClass.assert_not_null(match3_instance, "Match3 instance created successfully")
|
TestHelperClass.assert_not_null(
|
||||||
|
match3_instance, "Match3 instance created successfully"
|
||||||
|
)
|
||||||
|
|
||||||
# Wait for initialization
|
# Wait for initialization
|
||||||
await process_frame
|
await process_frame
|
||||||
@@ -73,28 +77,44 @@ func test_basic_functionality():
|
|||||||
TestHelperClass.print_step("Basic Functionality")
|
TestHelperClass.print_step("Basic Functionality")
|
||||||
|
|
||||||
if not match3_instance:
|
if not match3_instance:
|
||||||
TestHelperClass.assert_true(false, "Match3 instance not available for testing")
|
TestHelperClass.assert_true(
|
||||||
|
false, "Match3 instance not available for testing"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Test that Match3 has expected properties
|
# Test that Match3 has expected properties
|
||||||
var expected_properties = [
|
var expected_properties = [
|
||||||
"GRID_SIZE", "TILE_TYPES", "grid", "current_state", "selected_tile", "cursor_position"
|
"GRID_SIZE",
|
||||||
|
"TILE_TYPES",
|
||||||
|
"grid",
|
||||||
|
"current_state",
|
||||||
|
"selected_tile",
|
||||||
|
"cursor_position"
|
||||||
]
|
]
|
||||||
for prop in expected_properties:
|
for prop in expected_properties:
|
||||||
TestHelperClass.assert_true(prop in match3_instance, "Match3 has property: " + prop)
|
TestHelperClass.assert_true(
|
||||||
|
prop in match3_instance, "Match3 has property: " + prop
|
||||||
|
)
|
||||||
|
|
||||||
# Test that Match3 has expected methods
|
# Test that Match3 has expected methods
|
||||||
var expected_methods = [
|
var expected_methods = [
|
||||||
"_has_match_at", "_check_for_matches", "_get_match_line", "_clear_matches"
|
"_has_match_at",
|
||||||
|
"_check_for_matches",
|
||||||
|
"_get_match_line",
|
||||||
|
"_clear_matches"
|
||||||
]
|
]
|
||||||
TestHelperClass.assert_has_methods(match3_instance, expected_methods, "Match3 gameplay methods")
|
TestHelperClass.assert_has_methods(
|
||||||
|
match3_instance, expected_methods, "Match3 gameplay methods"
|
||||||
|
)
|
||||||
|
|
||||||
# Test signals
|
# Test signals
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.has_signal("score_changed"), "Match3 has score_changed signal"
|
match3_instance.has_signal("score_changed"),
|
||||||
|
"Match3 has score_changed signal"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.has_signal("grid_state_loaded"), "Match3 has grid_state_loaded signal"
|
match3_instance.has_signal("grid_state_loaded"),
|
||||||
|
"Match3 has grid_state_loaded signal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -105,26 +125,41 @@ func test_constants_and_safety_limits():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Test safety constants exist
|
# Test safety constants exist
|
||||||
TestHelperClass.assert_true("MAX_GRID_SIZE" in match3_instance, "MAX_GRID_SIZE constant exists")
|
TestHelperClass.assert_true(
|
||||||
|
"MAX_GRID_SIZE" in match3_instance, "MAX_GRID_SIZE constant exists"
|
||||||
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"MAX_TILE_TYPES" in match3_instance, "MAX_TILE_TYPES constant exists"
|
"MAX_TILE_TYPES" in match3_instance, "MAX_TILE_TYPES constant exists"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"MAX_CASCADE_ITERATIONS" in match3_instance, "MAX_CASCADE_ITERATIONS constant exists"
|
"MAX_CASCADE_ITERATIONS" in match3_instance,
|
||||||
|
"MAX_CASCADE_ITERATIONS constant exists"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
"MIN_GRID_SIZE" in match3_instance, "MIN_GRID_SIZE constant exists"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true("MIN_GRID_SIZE" in match3_instance, "MIN_GRID_SIZE constant exists")
|
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"MIN_TILE_TYPES" in match3_instance, "MIN_TILE_TYPES constant exists"
|
"MIN_TILE_TYPES" in match3_instance, "MIN_TILE_TYPES constant exists"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test safety limit values are reasonable
|
# Test safety limit values are reasonable
|
||||||
TestHelperClass.assert_equal(15, match3_instance.MAX_GRID_SIZE, "MAX_GRID_SIZE is reasonable")
|
|
||||||
TestHelperClass.assert_equal(10, match3_instance.MAX_TILE_TYPES, "MAX_TILE_TYPES is reasonable")
|
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
20, match3_instance.MAX_CASCADE_ITERATIONS, "MAX_CASCADE_ITERATIONS prevents infinite loops"
|
15, match3_instance.MAX_GRID_SIZE, "MAX_GRID_SIZE is reasonable"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_equal(
|
||||||
|
10, match3_instance.MAX_TILE_TYPES, "MAX_TILE_TYPES is reasonable"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_equal(
|
||||||
|
20,
|
||||||
|
match3_instance.MAX_CASCADE_ITERATIONS,
|
||||||
|
"MAX_CASCADE_ITERATIONS prevents infinite loops"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_equal(
|
||||||
|
3, match3_instance.MIN_GRID_SIZE, "MIN_GRID_SIZE is reasonable"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_equal(
|
||||||
|
3, match3_instance.MIN_TILE_TYPES, "MIN_TILE_TYPES is reasonable"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(3, match3_instance.MIN_GRID_SIZE, "MIN_GRID_SIZE is reasonable")
|
|
||||||
TestHelperClass.assert_equal(3, match3_instance.MIN_TILE_TYPES, "MIN_TILE_TYPES is reasonable")
|
|
||||||
|
|
||||||
# Test current values are within safety limits
|
# Test current values are within safety limits
|
||||||
TestHelperClass.assert_in_range(
|
TestHelperClass.assert_in_range(
|
||||||
@@ -148,13 +183,16 @@ func test_constants_and_safety_limits():
|
|||||||
|
|
||||||
# Test timing constants
|
# Test timing constants
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"CASCADE_WAIT_TIME" in match3_instance, "CASCADE_WAIT_TIME constant exists"
|
"CASCADE_WAIT_TIME" in match3_instance,
|
||||||
|
"CASCADE_WAIT_TIME constant exists"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"SWAP_ANIMATION_TIME" in match3_instance, "SWAP_ANIMATION_TIME constant exists"
|
"SWAP_ANIMATION_TIME" in match3_instance,
|
||||||
|
"SWAP_ANIMATION_TIME constant exists"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"TILE_DROP_WAIT_TIME" in match3_instance, "TILE_DROP_WAIT_TIME constant exists"
|
"TILE_DROP_WAIT_TIME" in match3_instance,
|
||||||
|
"TILE_DROP_WAIT_TIME constant exists"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -165,8 +203,12 @@ func test_grid_initialization():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Test grid structure
|
# Test grid structure
|
||||||
TestHelperClass.assert_not_null(match3_instance.grid, "Grid array is initialized")
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_true(match3_instance.grid is Array, "Grid is Array type")
|
match3_instance.grid, "Grid array is initialized"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
match3_instance.grid is Array, "Grid is Array type"
|
||||||
|
)
|
||||||
|
|
||||||
# Test grid dimensions
|
# Test grid dimensions
|
||||||
var expected_height = match3_instance.GRID_SIZE.y
|
var expected_height = match3_instance.GRID_SIZE.y
|
||||||
@@ -180,7 +222,9 @@ func test_grid_initialization():
|
|||||||
for y in range(match3_instance.grid.size()):
|
for y in range(match3_instance.grid.size()):
|
||||||
if y < expected_height:
|
if y < expected_height:
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
expected_width, match3_instance.grid[y].size(), "Grid row %d has correct width" % y
|
expected_width,
|
||||||
|
match3_instance.grid[y].size(),
|
||||||
|
"Grid row %d has correct width" % y
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test tiles are properly instantiated
|
# Test tiles are properly instantiated
|
||||||
@@ -195,10 +239,12 @@ func test_grid_initialization():
|
|||||||
if tile and is_instance_valid(tile):
|
if tile and is_instance_valid(tile):
|
||||||
valid_tile_count += 1
|
valid_tile_count += 1
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"tile_type" in tile, "Tile at (%d,%d) has tile_type property" % [x, y]
|
"tile_type" in tile,
|
||||||
|
"Tile at (%d,%d) has tile_type property" % [x, y]
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"grid_position" in tile, "Tile at (%d,%d) has grid_position property" % [x, y]
|
"grid_position" in tile,
|
||||||
|
"Tile at (%d,%d) has grid_position property" % [x, y]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test tile type is within valid range
|
# Test tile type is within valid range
|
||||||
@@ -222,15 +268,24 @@ func test_grid_layout_calculation():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Test tile size calculation
|
# Test tile size calculation
|
||||||
TestHelperClass.assert_true(match3_instance.tile_size > 0, "Tile size is positive")
|
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.tile_size <= 200, "Tile size is reasonable (not too large)"
|
match3_instance.tile_size > 0, "Tile size is positive"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
match3_instance.tile_size <= 200,
|
||||||
|
"Tile size is reasonable (not too large)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test grid offset
|
# Test grid offset
|
||||||
TestHelperClass.assert_not_null(match3_instance.grid_offset, "Grid offset is set")
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_true(match3_instance.grid_offset.x >= 0, "Grid offset X is non-negative")
|
match3_instance.grid_offset, "Grid offset is set"
|
||||||
TestHelperClass.assert_true(match3_instance.grid_offset.y >= 0, "Grid offset Y is non-negative")
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
match3_instance.grid_offset.x >= 0, "Grid offset X is non-negative"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
match3_instance.grid_offset.y >= 0, "Grid offset Y is non-negative"
|
||||||
|
)
|
||||||
|
|
||||||
# Test layout constants
|
# Test layout constants
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
@@ -242,7 +297,9 @@ func test_grid_layout_calculation():
|
|||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
50.0, match3_instance.GRID_LEFT_MARGIN, "Grid left margin constant"
|
50.0, match3_instance.GRID_LEFT_MARGIN, "Grid left margin constant"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(50.0, match3_instance.GRID_TOP_MARGIN, "Grid top margin constant")
|
TestHelperClass.assert_equal(
|
||||||
|
50.0, match3_instance.GRID_TOP_MARGIN, "Grid top margin constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_state_management():
|
func test_state_management():
|
||||||
@@ -253,23 +310,30 @@ func test_state_management():
|
|||||||
|
|
||||||
# Test GameState enum exists and has expected values
|
# Test GameState enum exists and has expected values
|
||||||
var game_state_class = match3_instance.get_script().get_global_class()
|
var game_state_class = match3_instance.get_script().get_global_class()
|
||||||
TestHelperClass.assert_true("GameState" in match3_instance, "GameState enum accessible")
|
TestHelperClass.assert_true(
|
||||||
|
"GameState" in match3_instance, "GameState enum accessible"
|
||||||
|
)
|
||||||
|
|
||||||
# Test current state is valid
|
# Test current state is valid
|
||||||
TestHelperClass.assert_not_null(match3_instance.current_state, "Current state is set")
|
TestHelperClass.assert_not_null(
|
||||||
|
match3_instance.current_state, "Current state is set"
|
||||||
|
)
|
||||||
|
|
||||||
# Test initialization flags
|
# Test initialization flags
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"grid_initialized" in match3_instance, "Grid initialized flag exists"
|
"grid_initialized" in match3_instance, "Grid initialized flag exists"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(match3_instance.grid_initialized, "Grid is marked as initialized")
|
TestHelperClass.assert_true(
|
||||||
|
match3_instance.grid_initialized, "Grid is marked as initialized"
|
||||||
|
)
|
||||||
|
|
||||||
# Test instance ID for debugging
|
# Test instance ID for debugging
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"instance_id" in match3_instance, "Instance ID exists for debugging"
|
"instance_id" in match3_instance, "Instance ID exists for debugging"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.instance_id.begins_with("Match3_"), "Instance ID has correct format"
|
match3_instance.instance_id.begins_with("Match3_"),
|
||||||
|
"Instance ID has correct format"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -281,13 +345,16 @@ func test_match_detection():
|
|||||||
|
|
||||||
# Test match detection methods exist and can be called safely
|
# Test match detection methods exist and can be called safely
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.has_method("_has_match_at"), "_has_match_at method exists"
|
match3_instance.has_method("_has_match_at"),
|
||||||
|
"_has_match_at method exists"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.has_method("_check_for_matches"), "_check_for_matches method exists"
|
match3_instance.has_method("_check_for_matches"),
|
||||||
|
"_check_for_matches method exists"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.has_method("_get_match_line"), "_get_match_line method exists"
|
match3_instance.has_method("_get_match_line"),
|
||||||
|
"_get_match_line method exists"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test boundary checking with invalid positions
|
# Test boundary checking with invalid positions
|
||||||
@@ -310,7 +377,10 @@ func test_match_detection():
|
|||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
is_invalid,
|
is_invalid,
|
||||||
"Invalid position (%d,%d) is correctly identified as invalid" % [pos.x, pos.y]
|
(
|
||||||
|
"Invalid position (%d,%d) is correctly identified as invalid"
|
||||||
|
% [pos.x, pos.y]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test valid positions through public interface
|
# Test valid positions through public interface
|
||||||
@@ -324,7 +394,8 @@ func test_match_detection():
|
|||||||
and pos.y < match3_instance.GRID_SIZE.y
|
and pos.y < match3_instance.GRID_SIZE.y
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
is_valid, "Valid position (%d,%d) is within grid bounds" % [x, y]
|
is_valid,
|
||||||
|
"Valid position (%d,%d) is within grid bounds" % [x, y]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -339,12 +410,14 @@ func test_scoring_system():
|
|||||||
|
|
||||||
# Test that the match3 instance can handle scoring (indirectly through clearing matches)
|
# Test that the match3 instance can handle scoring (indirectly through clearing matches)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.has_method("_clear_matches"), "Scoring system method exists"
|
match3_instance.has_method("_clear_matches"),
|
||||||
|
"Scoring system method exists"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test that score_changed signal exists
|
# Test that score_changed signal exists
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.has_signal("score_changed"), "Score changed signal exists"
|
match3_instance.has_signal("score_changed"),
|
||||||
|
"Score changed signal exists"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test scoring formula logic (based on the documented formula)
|
# Test scoring formula logic (based on the documented formula)
|
||||||
@@ -360,7 +433,9 @@ func test_scoring_system():
|
|||||||
calculated_score = match_size + max(0, match_size - 2)
|
calculated_score = match_size + max(0, match_size - 2)
|
||||||
|
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
expected_score, calculated_score, "Scoring formula correct for %d gems" % match_size
|
expected_score,
|
||||||
|
calculated_score,
|
||||||
|
"Scoring formula correct for %d gems" % match_size
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -375,22 +450,26 @@ func test_input_validation():
|
|||||||
match3_instance.cursor_position, "Cursor position is initialized"
|
match3_instance.cursor_position, "Cursor position is initialized"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.cursor_position is Vector2i, "Cursor position is Vector2i type"
|
match3_instance.cursor_position is Vector2i,
|
||||||
|
"Cursor position is Vector2i type"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test keyboard navigation flag
|
# Test keyboard navigation flag
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
"keyboard_navigation_enabled" in match3_instance, "Keyboard navigation flag exists"
|
"keyboard_navigation_enabled" in match3_instance,
|
||||||
|
"Keyboard navigation flag exists"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.keyboard_navigation_enabled is bool, "Keyboard navigation flag is boolean"
|
match3_instance.keyboard_navigation_enabled is bool,
|
||||||
|
"Keyboard navigation flag is boolean"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test selected tile safety
|
# Test selected tile safety
|
||||||
# selected_tile can be null initially, which is valid
|
# selected_tile can be null initially, which is valid
|
||||||
if match3_instance.selected_tile:
|
if match3_instance.selected_tile:
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
is_instance_valid(match3_instance.selected_tile), "Selected tile is valid if not null"
|
is_instance_valid(match3_instance.selected_tile),
|
||||||
|
"Selected tile is valid if not null"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -412,20 +491,25 @@ func test_memory_safety():
|
|||||||
var tile = match3_instance.grid[y][x]
|
var tile = match3_instance.grid[y][x]
|
||||||
if tile:
|
if tile:
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
is_instance_valid(tile), "Grid tile at (%d,%d) is valid instance" % [x, y]
|
is_instance_valid(tile),
|
||||||
|
"Grid tile at (%d,%d) is valid instance" % [x, y]
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile.get_parent() == match3_instance, "Tile properly parented to Match3"
|
tile.get_parent() == match3_instance,
|
||||||
|
"Tile properly parented to Match3"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test position validation
|
# Test position validation
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.has_method("_is_valid_grid_position"), "Position validation method exists"
|
match3_instance.has_method("_is_valid_grid_position"),
|
||||||
|
"Position validation method exists"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test safe tile access patterns exist
|
# Test safe tile access patterns exist
|
||||||
# The Match3 code uses comprehensive bounds checking and null validation
|
# The Match3 code uses comprehensive bounds checking and null validation
|
||||||
TestHelperClass.assert_true(true, "Memory safety patterns implemented in Match3 code")
|
TestHelperClass.assert_true(
|
||||||
|
true, "Memory safety patterns implemented in Match3 code"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_performance_requirements():
|
func test_performance_requirements():
|
||||||
@@ -449,13 +533,16 @@ func test_performance_requirements():
|
|||||||
|
|
||||||
# Test timing constants are reasonable for 60fps gameplay
|
# Test timing constants are reasonable for 60fps gameplay
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.CASCADE_WAIT_TIME >= 0.05, "Cascade wait time allows for smooth animation"
|
match3_instance.CASCADE_WAIT_TIME >= 0.05,
|
||||||
|
"Cascade wait time allows for smooth animation"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.SWAP_ANIMATION_TIME <= 0.5, "Swap animation time is responsive"
|
match3_instance.SWAP_ANIMATION_TIME <= 0.5,
|
||||||
|
"Swap animation time is responsive"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
match3_instance.TILE_DROP_WAIT_TIME <= 0.3, "Tile drop wait time is responsive"
|
match3_instance.TILE_DROP_WAIT_TIME <= 0.3,
|
||||||
|
"Tile drop wait time is responsive"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test grid initialization performance
|
# Test grid initialization performance
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ func test_migration_compatibility():
|
|||||||
|
|
||||||
# The checksums should be different (old system broken)
|
# The checksums should be different (old system broken)
|
||||||
TestHelperClass.assert_not_equal(
|
TestHelperClass.assert_not_equal(
|
||||||
old_checksum, new_checksum, "Old and new checksum formats should be different"
|
old_checksum,
|
||||||
|
new_checksum,
|
||||||
|
"Old and new checksum formats should be different"
|
||||||
)
|
)
|
||||||
print("Old checksum: %s" % old_checksum)
|
print("Old checksum: %s" % old_checksum)
|
||||||
print("New checksum: %s" % new_checksum)
|
print("New checksum: %s" % new_checksum)
|
||||||
@@ -85,9 +87,13 @@ func test_migration_compatibility():
|
|||||||
print("Consistent checksum: %s" % first_checksum)
|
print("Consistent checksum: %s" % first_checksum)
|
||||||
|
|
||||||
TestHelperClass.print_step("Migration Strategy Verification")
|
TestHelperClass.print_step("Migration Strategy Verification")
|
||||||
TestHelperClass.assert_true(true, "Version-based checksum handling implemented")
|
TestHelperClass.assert_true(
|
||||||
|
true, "Version-based checksum handling implemented"
|
||||||
|
)
|
||||||
print("✓ Files without _checksum: Allow (backward compatibility)")
|
print("✓ Files without _checksum: Allow (backward compatibility)")
|
||||||
print("✓ Files with version < current: Recalculate checksum after migration")
|
print(
|
||||||
|
"✓ Files with version < current: Recalculate checksum after migration"
|
||||||
|
)
|
||||||
print("✓ Files with current version: Use new checksum validation")
|
print("✓ Files with current version: Use new checksum validation")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ func test_scene_discovery():
|
|||||||
for directory in scene_directories:
|
for directory in scene_directories:
|
||||||
discover_scenes_in_directory(directory)
|
discover_scenes_in_directory(directory)
|
||||||
|
|
||||||
TestHelperClass.assert_true(discovered_scenes.size() > 0, "Found scenes in project")
|
TestHelperClass.assert_true(
|
||||||
|
discovered_scenes.size() > 0, "Found scenes in project"
|
||||||
|
)
|
||||||
print("Discovered %d scene files" % discovered_scenes.size())
|
print("Discovered %d scene files" % discovered_scenes.size())
|
||||||
|
|
||||||
# List discovered scenes for reference
|
# List discovered scenes for reference
|
||||||
@@ -89,23 +91,31 @@ func validate_scene_loading(scene_path: String):
|
|||||||
# Check if resource exists
|
# Check if resource exists
|
||||||
if not ResourceLoader.exists(scene_path):
|
if not ResourceLoader.exists(scene_path):
|
||||||
validation_results[scene_path] = "Resource does not exist"
|
validation_results[scene_path] = "Resource does not exist"
|
||||||
TestHelperClass.assert_false(true, "%s - Resource does not exist" % scene_name)
|
TestHelperClass.assert_false(
|
||||||
|
true, "%s - Resource does not exist" % scene_name
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Attempt to load the scene
|
# Attempt to load the scene
|
||||||
var packed_scene = load(scene_path)
|
var packed_scene = load(scene_path)
|
||||||
if not packed_scene:
|
if not packed_scene:
|
||||||
validation_results[scene_path] = "Failed to load scene"
|
validation_results[scene_path] = "Failed to load scene"
|
||||||
TestHelperClass.assert_false(true, "%s - Failed to load scene" % scene_name)
|
TestHelperClass.assert_false(
|
||||||
|
true, "%s - Failed to load scene" % scene_name
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not packed_scene is PackedScene:
|
if not packed_scene is PackedScene:
|
||||||
validation_results[scene_path] = "Resource is not a PackedScene"
|
validation_results[scene_path] = "Resource is not a PackedScene"
|
||||||
TestHelperClass.assert_false(true, "%s - Resource is not a PackedScene" % scene_name)
|
TestHelperClass.assert_false(
|
||||||
|
true, "%s - Resource is not a PackedScene" % scene_name
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
validation_results[scene_path] = "Loading successful"
|
validation_results[scene_path] = "Loading successful"
|
||||||
TestHelperClass.assert_true(true, "%s - Scene loads successfully" % scene_name)
|
TestHelperClass.assert_true(
|
||||||
|
true, "%s - Scene loads successfully" % scene_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_scene_instantiation():
|
func test_scene_instantiation():
|
||||||
@@ -127,12 +137,15 @@ func validate_scene_instantiation(scene_path: String):
|
|||||||
var scene_instance = packed_scene.instantiate()
|
var scene_instance = packed_scene.instantiate()
|
||||||
if not scene_instance:
|
if not scene_instance:
|
||||||
validation_results[scene_path] = "Failed to instantiate scene"
|
validation_results[scene_path] = "Failed to instantiate scene"
|
||||||
TestHelperClass.assert_false(true, "%s - Failed to instantiate scene" % scene_name)
|
TestHelperClass.assert_false(
|
||||||
|
true, "%s - Failed to instantiate scene" % scene_name
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Validate the instance
|
# Validate the instance
|
||||||
TestHelperClass.assert_not_null(
|
TestHelperClass.assert_not_null(
|
||||||
scene_instance, "%s - Scene instantiation creates valid node" % scene_name
|
scene_instance,
|
||||||
|
"%s - Scene instantiation creates valid node" % scene_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# Clean up the instance
|
# Clean up the instance
|
||||||
@@ -160,10 +173,15 @@ func test_critical_scenes():
|
|||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"Full validation successful",
|
"Full validation successful",
|
||||||
status,
|
status,
|
||||||
"Critical scene %s must pass all validation" % scene_path.get_file()
|
(
|
||||||
|
"Critical scene %s must pass all validation"
|
||||||
|
% scene_path.get_file()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
TestHelperClass.assert_false(true, "Critical scene missing: %s" % scene_path)
|
TestHelperClass.assert_false(
|
||||||
|
true, "Critical scene missing: %s" % scene_path
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func print_validation_summary():
|
func print_validation_summary():
|
||||||
@@ -175,7 +193,10 @@ func print_validation_summary():
|
|||||||
|
|
||||||
for scene_path in discovered_scenes:
|
for scene_path in discovered_scenes:
|
||||||
var status = validation_results.get(scene_path, "Not tested")
|
var status = validation_results.get(scene_path, "Not tested")
|
||||||
if status == "Full validation successful" or status == "Loading successful":
|
if (
|
||||||
|
status == "Full validation successful"
|
||||||
|
or status == "Loading successful"
|
||||||
|
):
|
||||||
successful_scenes += 1
|
successful_scenes += 1
|
||||||
else:
|
else:
|
||||||
failed_scenes += 1
|
failed_scenes += 1
|
||||||
|
|||||||
@@ -64,23 +64,35 @@ func test_basic_functionality():
|
|||||||
|
|
||||||
# Test that SettingsManager has expected methods
|
# Test that SettingsManager has expected methods
|
||||||
var expected_methods = [
|
var expected_methods = [
|
||||||
"get_setting", "set_setting", "save_settings", "load_settings", "reset_settings_to_defaults"
|
"get_setting",
|
||||||
|
"set_setting",
|
||||||
|
"save_settings",
|
||||||
|
"load_settings",
|
||||||
|
"reset_settings_to_defaults"
|
||||||
]
|
]
|
||||||
TestHelperClass.assert_has_methods(
|
TestHelperClass.assert_has_methods(
|
||||||
settings_manager, expected_methods, "SettingsManager methods"
|
settings_manager, expected_methods, "SettingsManager methods"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test default settings structure
|
# Test default settings structure
|
||||||
var expected_defaults = ["master_volume", "music_volume", "sfx_volume", "language"]
|
var expected_defaults = [
|
||||||
|
"master_volume", "music_volume", "sfx_volume", "language"
|
||||||
|
]
|
||||||
for key in expected_defaults:
|
for key in expected_defaults:
|
||||||
TestHelperClass.assert_has_key(
|
TestHelperClass.assert_has_key(
|
||||||
settings_manager.default_settings, key, "Default setting key: " + key
|
settings_manager.default_settings,
|
||||||
|
key,
|
||||||
|
"Default setting key: " + key
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test getting settings
|
# Test getting settings
|
||||||
var master_volume = settings_manager.get_setting("master_volume")
|
var master_volume = settings_manager.get_setting("master_volume")
|
||||||
TestHelperClass.assert_not_null(master_volume, "Can get master_volume setting")
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_true(master_volume is float, "master_volume is float type")
|
master_volume, "Can get master_volume setting"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
master_volume is float, "master_volume is float type"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_input_validation_security():
|
func test_input_validation_security():
|
||||||
@@ -88,38 +100,56 @@ func test_input_validation_security():
|
|||||||
|
|
||||||
# Test NaN validation
|
# Test NaN validation
|
||||||
var nan_result = settings_manager.set_setting("master_volume", NAN)
|
var nan_result = settings_manager.set_setting("master_volume", NAN)
|
||||||
TestHelperClass.assert_false(nan_result, "NaN values rejected for volume settings")
|
TestHelperClass.assert_false(
|
||||||
|
nan_result, "NaN values rejected for volume settings"
|
||||||
|
)
|
||||||
|
|
||||||
# Test Infinity validation
|
# Test Infinity validation
|
||||||
var inf_result = settings_manager.set_setting("master_volume", INF)
|
var inf_result = settings_manager.set_setting("master_volume", INF)
|
||||||
TestHelperClass.assert_false(inf_result, "Infinity values rejected for volume settings")
|
TestHelperClass.assert_false(
|
||||||
|
inf_result, "Infinity values rejected for volume settings"
|
||||||
|
)
|
||||||
|
|
||||||
# Test negative infinity validation
|
# Test negative infinity validation
|
||||||
var neg_inf_result = settings_manager.set_setting("master_volume", -INF)
|
var neg_inf_result = settings_manager.set_setting("master_volume", -INF)
|
||||||
TestHelperClass.assert_false(neg_inf_result, "Negative infinity values rejected")
|
TestHelperClass.assert_false(
|
||||||
|
neg_inf_result, "Negative infinity values rejected"
|
||||||
|
)
|
||||||
|
|
||||||
# Test range validation for volumes
|
# Test range validation for volumes
|
||||||
var negative_volume = settings_manager.set_setting("master_volume", -0.5)
|
var negative_volume = settings_manager.set_setting("master_volume", -0.5)
|
||||||
TestHelperClass.assert_false(negative_volume, "Negative volume values rejected")
|
TestHelperClass.assert_false(
|
||||||
|
negative_volume, "Negative volume values rejected"
|
||||||
|
)
|
||||||
|
|
||||||
var excessive_volume = settings_manager.set_setting("master_volume", 1.5)
|
var excessive_volume = settings_manager.set_setting("master_volume", 1.5)
|
||||||
TestHelperClass.assert_false(excessive_volume, "Volume values > 1.0 rejected")
|
TestHelperClass.assert_false(
|
||||||
|
excessive_volume, "Volume values > 1.0 rejected"
|
||||||
|
)
|
||||||
|
|
||||||
# Test valid volume range
|
# Test valid volume range
|
||||||
var valid_volume = settings_manager.set_setting("master_volume", 0.5)
|
var valid_volume = settings_manager.set_setting("master_volume", 0.5)
|
||||||
TestHelperClass.assert_true(valid_volume, "Valid volume values accepted")
|
TestHelperClass.assert_true(valid_volume, "Valid volume values accepted")
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
0.5, settings_manager.get_setting("master_volume"), "Volume value set correctly"
|
0.5,
|
||||||
|
settings_manager.get_setting("master_volume"),
|
||||||
|
"Volume value set correctly"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test string length validation for language
|
# Test string length validation for language
|
||||||
var long_language = "a".repeat(20) # Exceeds MAX_SETTING_STRING_LENGTH
|
var long_language = "a".repeat(20) # Exceeds MAX_SETTING_STRING_LENGTH
|
||||||
var long_lang_result = settings_manager.set_setting("language", long_language)
|
var long_lang_result = settings_manager.set_setting(
|
||||||
TestHelperClass.assert_false(long_lang_result, "Excessively long language codes rejected")
|
"language", long_language
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_false(
|
||||||
|
long_lang_result, "Excessively long language codes rejected"
|
||||||
|
)
|
||||||
|
|
||||||
# Test invalid characters in language code
|
# Test invalid characters in language code
|
||||||
var invalid_chars = settings_manager.set_setting("language", "en<script>")
|
var invalid_chars = settings_manager.set_setting("language", "en<script>")
|
||||||
TestHelperClass.assert_false(invalid_chars, "Language codes with invalid characters rejected")
|
TestHelperClass.assert_false(
|
||||||
|
invalid_chars, "Language codes with invalid characters rejected"
|
||||||
|
)
|
||||||
|
|
||||||
# Test valid language code
|
# Test valid language code
|
||||||
var valid_lang = settings_manager.set_setting("language", "en")
|
var valid_lang = settings_manager.set_setting("language", "en")
|
||||||
@@ -141,10 +171,14 @@ func test_file_io_security():
|
|||||||
|
|
||||||
# Test loading with backup scenario
|
# Test loading with backup scenario
|
||||||
settings_manager.load_settings()
|
settings_manager.load_settings()
|
||||||
TestHelperClass.assert_not_null(settings_manager.settings, "Settings loaded successfully")
|
TestHelperClass.assert_not_null(
|
||||||
|
settings_manager.settings, "Settings loaded successfully"
|
||||||
|
)
|
||||||
|
|
||||||
# Test that settings file exists after save
|
# Test that settings file exists after save
|
||||||
TestHelperClass.assert_file_exists("user://settings.cfg", "Settings file created after save")
|
TestHelperClass.assert_file_exists(
|
||||||
|
"user://settings.cfg", "Settings file created after save"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_json_parsing_security():
|
func test_json_parsing_security():
|
||||||
@@ -157,7 +191,9 @@ func test_json_parsing_security():
|
|||||||
temp_files.append(invalid_json_path)
|
temp_files.append(invalid_json_path)
|
||||||
|
|
||||||
# Create oversized JSON file
|
# Create oversized JSON file
|
||||||
var large_json_content = '{"languages": {"' + "x".repeat(70000) + '": "test"}}'
|
var large_json_content = (
|
||||||
|
'{"languages": {"' + "x".repeat(70000) + '": "test"}}'
|
||||||
|
)
|
||||||
var oversized_json_path = TestHelper.create_temp_file(
|
var oversized_json_path = TestHelper.create_temp_file(
|
||||||
"oversized_languages.json", large_json_content
|
"oversized_languages.json", large_json_content
|
||||||
)
|
)
|
||||||
@@ -178,11 +214,15 @@ func test_language_validation():
|
|||||||
var supported_langs = ["en", "ru"]
|
var supported_langs = ["en", "ru"]
|
||||||
for lang in supported_langs:
|
for lang in supported_langs:
|
||||||
var result = settings_manager.set_setting("language", lang)
|
var result = settings_manager.set_setting("language", lang)
|
||||||
TestHelperClass.assert_true(result, "Supported language accepted: " + lang)
|
TestHelperClass.assert_true(
|
||||||
|
result, "Supported language accepted: " + lang
|
||||||
|
)
|
||||||
|
|
||||||
# Test unsupported language
|
# Test unsupported language
|
||||||
var unsupported_result = settings_manager.set_setting("language", "xyz")
|
var unsupported_result = settings_manager.set_setting("language", "xyz")
|
||||||
TestHelperClass.assert_false(unsupported_result, "Unsupported language rejected")
|
TestHelperClass.assert_false(
|
||||||
|
unsupported_result, "Unsupported language rejected"
|
||||||
|
)
|
||||||
|
|
||||||
# Test empty language
|
# Test empty language
|
||||||
var empty_result = settings_manager.set_setting("language", "")
|
var empty_result = settings_manager.set_setting("language", "")
|
||||||
@@ -201,26 +241,32 @@ func test_volume_validation():
|
|||||||
for setting in volume_settings:
|
for setting in volume_settings:
|
||||||
# Test boundary values
|
# Test boundary values
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
settings_manager.set_setting(setting, 0.0), "Volume 0.0 accepted for " + setting
|
settings_manager.set_setting(setting, 0.0),
|
||||||
|
"Volume 0.0 accepted for " + setting
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
settings_manager.set_setting(setting, 1.0), "Volume 1.0 accepted for " + setting
|
settings_manager.set_setting(setting, 1.0),
|
||||||
|
"Volume 1.0 accepted for " + setting
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test out of range values
|
# Test out of range values
|
||||||
TestHelperClass.assert_false(
|
TestHelperClass.assert_false(
|
||||||
settings_manager.set_setting(setting, -0.1), "Negative volume rejected for " + setting
|
settings_manager.set_setting(setting, -0.1),
|
||||||
|
"Negative volume rejected for " + setting
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_false(
|
TestHelperClass.assert_false(
|
||||||
settings_manager.set_setting(setting, 1.1), "Volume > 1.0 rejected for " + setting
|
settings_manager.set_setting(setting, 1.1),
|
||||||
|
"Volume > 1.0 rejected for " + setting
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test invalid types
|
# Test invalid types
|
||||||
TestHelperClass.assert_false(
|
TestHelperClass.assert_false(
|
||||||
settings_manager.set_setting(setting, "0.5"), "String volume rejected for " + setting
|
settings_manager.set_setting(setting, "0.5"),
|
||||||
|
"String volume rejected for " + setting
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_false(
|
TestHelperClass.assert_false(
|
||||||
settings_manager.set_setting(setting, null), "Null volume rejected for " + setting
|
settings_manager.set_setting(setting, null),
|
||||||
|
"Null volume rejected for " + setting
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -228,8 +274,12 @@ func test_error_handling_and_recovery():
|
|||||||
TestHelperClass.print_step("Error Handling and Recovery")
|
TestHelperClass.print_step("Error Handling and Recovery")
|
||||||
|
|
||||||
# Test unknown setting key
|
# Test unknown setting key
|
||||||
var unknown_result = settings_manager.set_setting("unknown_setting", "value")
|
var unknown_result = settings_manager.set_setting(
|
||||||
TestHelperClass.assert_false(unknown_result, "Unknown setting keys rejected")
|
"unknown_setting", "value"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_false(
|
||||||
|
unknown_result, "Unknown setting keys rejected"
|
||||||
|
)
|
||||||
|
|
||||||
# Test recovery from corrupted settings
|
# Test recovery from corrupted settings
|
||||||
# Save current state
|
# Save current state
|
||||||
@@ -248,13 +298,18 @@ func test_error_handling_and_recovery():
|
|||||||
|
|
||||||
# Test fallback language loading
|
# Test fallback language loading
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
settings_manager.languages_data.has("languages"), "Fallback languages loaded"
|
settings_manager.languages_data.has("languages"),
|
||||||
|
"Fallback languages loaded"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_has_key(
|
TestHelperClass.assert_has_key(
|
||||||
settings_manager.languages_data["languages"], "en", "English fallback language available"
|
settings_manager.languages_data["languages"],
|
||||||
|
"en",
|
||||||
|
"English fallback language available"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_has_key(
|
TestHelperClass.assert_has_key(
|
||||||
settings_manager.languages_data["languages"], "ru", "Russian fallback language available"
|
settings_manager.languages_data["languages"],
|
||||||
|
"ru",
|
||||||
|
"Russian fallback language available"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -281,7 +336,9 @@ func test_reset_functionality():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Test that reset saves automatically
|
# Test that reset saves automatically
|
||||||
TestHelperClass.assert_file_exists("user://settings.cfg", "Settings file exists after reset")
|
TestHelperClass.assert_file_exists(
|
||||||
|
"user://settings.cfg", "Settings file exists after reset"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_performance_benchmarks():
|
func test_performance_benchmarks():
|
||||||
@@ -290,18 +347,24 @@ func test_performance_benchmarks():
|
|||||||
# Test settings load performance
|
# Test settings load performance
|
||||||
TestHelperClass.start_performance_test("load_settings")
|
TestHelperClass.start_performance_test("load_settings")
|
||||||
settings_manager.load_settings()
|
settings_manager.load_settings()
|
||||||
TestHelperClass.end_performance_test("load_settings", 100.0, "Settings load within 100ms")
|
TestHelperClass.end_performance_test(
|
||||||
|
"load_settings", 100.0, "Settings load within 100ms"
|
||||||
|
)
|
||||||
|
|
||||||
# Test settings save performance
|
# Test settings save performance
|
||||||
TestHelperClass.start_performance_test("save_settings")
|
TestHelperClass.start_performance_test("save_settings")
|
||||||
settings_manager.save_settings()
|
settings_manager.save_settings()
|
||||||
TestHelperClass.end_performance_test("save_settings", 50.0, "Settings save within 50ms")
|
TestHelperClass.end_performance_test(
|
||||||
|
"save_settings", 50.0, "Settings save within 50ms"
|
||||||
|
)
|
||||||
|
|
||||||
# Test validation performance
|
# Test validation performance
|
||||||
TestHelperClass.start_performance_test("validation")
|
TestHelperClass.start_performance_test("validation")
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
settings_manager.set_setting("master_volume", 0.5)
|
settings_manager.set_setting("master_volume", 0.5)
|
||||||
TestHelperClass.end_performance_test("validation", 50.0, "100 validations within 50ms")
|
TestHelperClass.end_performance_test(
|
||||||
|
"validation", 50.0, "100 validations within 50ms"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func cleanup_tests():
|
func cleanup_tests():
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ func setup_test_environment():
|
|||||||
if tile_scene:
|
if tile_scene:
|
||||||
tile_instance = tile_scene.instantiate()
|
tile_instance = tile_scene.instantiate()
|
||||||
test_viewport.add_child(tile_instance)
|
test_viewport.add_child(tile_instance)
|
||||||
TestHelperClass.assert_not_null(tile_instance, "Tile instance created successfully")
|
TestHelperClass.assert_not_null(
|
||||||
|
tile_instance, "Tile instance created successfully"
|
||||||
|
)
|
||||||
|
|
||||||
# Wait for initialization
|
# Wait for initialization
|
||||||
await process_frame
|
await process_frame
|
||||||
@@ -73,7 +75,9 @@ func test_basic_functionality():
|
|||||||
TestHelperClass.print_step("Basic Functionality")
|
TestHelperClass.print_step("Basic Functionality")
|
||||||
|
|
||||||
if not tile_instance:
|
if not tile_instance:
|
||||||
TestHelperClass.assert_true(false, "Tile instance not available for testing")
|
TestHelperClass.assert_true(
|
||||||
|
false, "Tile instance not available for testing"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Test that Tile has expected properties
|
# Test that Tile has expected properties
|
||||||
@@ -86,7 +90,9 @@ func test_basic_functionality():
|
|||||||
"active_gem_types"
|
"active_gem_types"
|
||||||
]
|
]
|
||||||
for prop in expected_properties:
|
for prop in expected_properties:
|
||||||
TestHelperClass.assert_true(prop in tile_instance, "Tile has property: " + prop)
|
TestHelperClass.assert_true(
|
||||||
|
prop in tile_instance, "Tile has property: " + prop
|
||||||
|
)
|
||||||
|
|
||||||
# Test that Tile has expected methods
|
# Test that Tile has expected methods
|
||||||
var expected_methods = [
|
var expected_methods = [
|
||||||
@@ -96,19 +102,28 @@ func test_basic_functionality():
|
|||||||
"remove_gem_type",
|
"remove_gem_type",
|
||||||
"force_reset_visual_state"
|
"force_reset_visual_state"
|
||||||
]
|
]
|
||||||
TestHelperClass.assert_has_methods(tile_instance, expected_methods, "Tile component methods")
|
TestHelperClass.assert_has_methods(
|
||||||
|
tile_instance, expected_methods, "Tile component methods"
|
||||||
|
)
|
||||||
|
|
||||||
# Test signals
|
# Test signals
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.has_signal("tile_selected"), "Tile has tile_selected signal"
|
tile_instance.has_signal("tile_selected"),
|
||||||
|
"Tile has tile_selected signal"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test sprite reference
|
# Test sprite reference
|
||||||
TestHelperClass.assert_not_null(tile_instance.sprite, "Sprite node is available")
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_true(tile_instance.sprite is Sprite2D, "Sprite is Sprite2D type")
|
tile_instance.sprite, "Sprite node is available"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
tile_instance.sprite is Sprite2D, "Sprite is Sprite2D type"
|
||||||
|
)
|
||||||
|
|
||||||
# Test group membership
|
# Test group membership
|
||||||
TestHelperClass.assert_true(tile_instance.is_in_group("tiles"), "Tile is in 'tiles' group")
|
TestHelperClass.assert_true(
|
||||||
|
tile_instance.is_in_group("tiles"), "Tile is in 'tiles' group"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_tile_constants():
|
func test_tile_constants():
|
||||||
@@ -118,22 +133,33 @@ func test_tile_constants():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Test TILE_SIZE constant
|
# Test TILE_SIZE constant
|
||||||
TestHelperClass.assert_equal(48, tile_instance.TILE_SIZE, "TILE_SIZE constant is correct")
|
TestHelperClass.assert_equal(
|
||||||
|
48, tile_instance.TILE_SIZE, "TILE_SIZE constant is correct"
|
||||||
|
)
|
||||||
|
|
||||||
# Test all_gem_textures array
|
# Test all_gem_textures array
|
||||||
TestHelperClass.assert_not_null(tile_instance.all_gem_textures, "All gem textures array exists")
|
TestHelperClass.assert_not_null(
|
||||||
|
tile_instance.all_gem_textures, "All gem textures array exists"
|
||||||
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.all_gem_textures is Array, "All gem textures is Array type"
|
tile_instance.all_gem_textures is Array,
|
||||||
|
"All gem textures is Array type"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
8, tile_instance.all_gem_textures.size(), "All gem textures has expected count"
|
8,
|
||||||
|
tile_instance.all_gem_textures.size(),
|
||||||
|
"All gem textures has expected count"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test that all gem textures are valid
|
# Test that all gem textures are valid
|
||||||
for i in range(tile_instance.all_gem_textures.size()):
|
for i in range(tile_instance.all_gem_textures.size()):
|
||||||
var texture = tile_instance.all_gem_textures[i]
|
var texture = tile_instance.all_gem_textures[i]
|
||||||
TestHelperClass.assert_not_null(texture, "Gem texture %d is not null" % i)
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_true(texture is Texture2D, "Gem texture %d is Texture2D type" % i)
|
texture, "Gem texture %d is not null" % i
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
texture is Texture2D, "Gem texture %d is Texture2D type" % i
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_texture_management():
|
func test_texture_management():
|
||||||
@@ -147,10 +173,12 @@ func test_texture_management():
|
|||||||
tile_instance.active_gem_types, "Active gem types is initialized"
|
tile_instance.active_gem_types, "Active gem types is initialized"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.active_gem_types is Array, "Active gem types is Array type"
|
tile_instance.active_gem_types is Array,
|
||||||
|
"Active gem types is Array type"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.active_gem_types.size() > 0, "Active gem types has content"
|
tile_instance.active_gem_types.size() > 0,
|
||||||
|
"Active gem types has content"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test texture assignment for valid tile types
|
# Test texture assignment for valid tile types
|
||||||
@@ -164,7 +192,8 @@ func test_texture_management():
|
|||||||
|
|
||||||
if tile_instance.sprite:
|
if tile_instance.sprite:
|
||||||
TestHelperClass.assert_not_null(
|
TestHelperClass.assert_not_null(
|
||||||
tile_instance.sprite.texture, "Sprite texture assigned for type %d" % i
|
tile_instance.sprite.texture,
|
||||||
|
"Sprite texture assigned for type %d" % i
|
||||||
)
|
)
|
||||||
|
|
||||||
# Restore original type
|
# Restore original type
|
||||||
@@ -184,42 +213,60 @@ func test_gem_type_management():
|
|||||||
var test_gems = [0, 1, 2]
|
var test_gems = [0, 1, 2]
|
||||||
tile_instance.set_active_gem_types(test_gems)
|
tile_instance.set_active_gem_types(test_gems)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
3, tile_instance.get_active_gem_count(), "Active gem count set correctly"
|
3,
|
||||||
|
tile_instance.get_active_gem_count(),
|
||||||
|
"Active gem count set correctly"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test add_gem_type
|
# Test add_gem_type
|
||||||
var add_result = tile_instance.add_gem_type(3)
|
var add_result = tile_instance.add_gem_type(3)
|
||||||
TestHelperClass.assert_true(add_result, "Valid gem type added successfully")
|
TestHelperClass.assert_true(add_result, "Valid gem type added successfully")
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
4, tile_instance.get_active_gem_count(), "Gem count increased after addition"
|
4,
|
||||||
|
tile_instance.get_active_gem_count(),
|
||||||
|
"Gem count increased after addition"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test adding duplicate gem type
|
# Test adding duplicate gem type
|
||||||
var duplicate_result = tile_instance.add_gem_type(3)
|
var duplicate_result = tile_instance.add_gem_type(3)
|
||||||
TestHelperClass.assert_false(duplicate_result, "Duplicate gem type addition rejected")
|
TestHelperClass.assert_false(
|
||||||
|
duplicate_result, "Duplicate gem type addition rejected"
|
||||||
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
4, tile_instance.get_active_gem_count(), "Gem count unchanged after duplicate"
|
4,
|
||||||
|
tile_instance.get_active_gem_count(),
|
||||||
|
"Gem count unchanged after duplicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test add_gem_type with invalid index
|
# Test add_gem_type with invalid index
|
||||||
var invalid_add = tile_instance.add_gem_type(99)
|
var invalid_add = tile_instance.add_gem_type(99)
|
||||||
TestHelperClass.assert_false(invalid_add, "Invalid gem index addition rejected")
|
TestHelperClass.assert_false(
|
||||||
|
invalid_add, "Invalid gem index addition rejected"
|
||||||
|
)
|
||||||
|
|
||||||
# Test remove_gem_type
|
# Test remove_gem_type
|
||||||
var remove_result = tile_instance.remove_gem_type(3)
|
var remove_result = tile_instance.remove_gem_type(3)
|
||||||
TestHelperClass.assert_true(remove_result, "Valid gem type removed successfully")
|
TestHelperClass.assert_true(
|
||||||
|
remove_result, "Valid gem type removed successfully"
|
||||||
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
3, tile_instance.get_active_gem_count(), "Gem count decreased after removal"
|
3,
|
||||||
|
tile_instance.get_active_gem_count(),
|
||||||
|
"Gem count decreased after removal"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test removing non-existent gem type
|
# Test removing non-existent gem type
|
||||||
var nonexistent_remove = tile_instance.remove_gem_type(99)
|
var nonexistent_remove = tile_instance.remove_gem_type(99)
|
||||||
TestHelperClass.assert_false(nonexistent_remove, "Non-existent gem type removal rejected")
|
TestHelperClass.assert_false(
|
||||||
|
nonexistent_remove, "Non-existent gem type removal rejected"
|
||||||
|
)
|
||||||
|
|
||||||
# Test minimum gem types protection
|
# Test minimum gem types protection
|
||||||
tile_instance.set_active_gem_types([0, 1]) # Set to minimum
|
tile_instance.set_active_gem_types([0, 1]) # Set to minimum
|
||||||
var protected_remove = tile_instance.remove_gem_type(0)
|
var protected_remove = tile_instance.remove_gem_type(0)
|
||||||
TestHelperClass.assert_false(protected_remove, "Minimum gem types protection active")
|
TestHelperClass.assert_false(
|
||||||
|
protected_remove, "Minimum gem types protection active"
|
||||||
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
2, tile_instance.get_active_gem_count(), "Minimum gem count preserved"
|
2, tile_instance.get_active_gem_count(), "Minimum gem count preserved"
|
||||||
)
|
)
|
||||||
@@ -240,7 +287,9 @@ func test_visual_feedback_system():
|
|||||||
|
|
||||||
# Test selection visual feedback
|
# Test selection visual feedback
|
||||||
tile_instance.is_selected = true
|
tile_instance.is_selected = true
|
||||||
TestHelperClass.assert_true(tile_instance.is_selected, "Selection state set correctly")
|
TestHelperClass.assert_true(
|
||||||
|
tile_instance.is_selected, "Selection state set correctly"
|
||||||
|
)
|
||||||
|
|
||||||
# Wait for potential animation
|
# Wait for potential animation
|
||||||
await process_frame
|
await process_frame
|
||||||
@@ -256,21 +305,29 @@ func test_visual_feedback_system():
|
|||||||
# Test highlight visual feedback
|
# Test highlight visual feedback
|
||||||
tile_instance.is_selected = false
|
tile_instance.is_selected = false
|
||||||
tile_instance.is_highlighted = true
|
tile_instance.is_highlighted = true
|
||||||
TestHelperClass.assert_true(tile_instance.is_highlighted, "Highlight state set correctly")
|
TestHelperClass.assert_true(
|
||||||
|
tile_instance.is_highlighted, "Highlight state set correctly"
|
||||||
|
)
|
||||||
|
|
||||||
# Wait for potential animation
|
# Wait for potential animation
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
||||||
# Test normal state
|
# Test normal state
|
||||||
tile_instance.is_highlighted = false
|
tile_instance.is_highlighted = false
|
||||||
TestHelperClass.assert_false(tile_instance.is_highlighted, "Normal state restored")
|
TestHelperClass.assert_false(
|
||||||
|
tile_instance.is_highlighted, "Normal state restored"
|
||||||
|
)
|
||||||
|
|
||||||
# Test force reset
|
# Test force reset
|
||||||
tile_instance.is_selected = true
|
tile_instance.is_selected = true
|
||||||
tile_instance.is_highlighted = true
|
tile_instance.is_highlighted = true
|
||||||
tile_instance.force_reset_visual_state()
|
tile_instance.force_reset_visual_state()
|
||||||
TestHelperClass.assert_false(tile_instance.is_selected, "Force reset clears selection")
|
TestHelperClass.assert_false(
|
||||||
TestHelperClass.assert_false(tile_instance.is_highlighted, "Force reset clears highlight")
|
tile_instance.is_selected, "Force reset clears selection"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_false(
|
||||||
|
tile_instance.is_highlighted, "Force reset clears highlight"
|
||||||
|
)
|
||||||
|
|
||||||
# Restore original state
|
# Restore original state
|
||||||
tile_instance.is_selected = original_selected
|
tile_instance.is_selected = original_selected
|
||||||
@@ -284,12 +341,16 @@ func test_state_management():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Test initial state
|
# Test initial state
|
||||||
TestHelperClass.assert_true(tile_instance.tile_type >= 0, "Initial tile type is non-negative")
|
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.grid_position is Vector2i, "Grid position is Vector2i type"
|
tile_instance.tile_type >= 0, "Initial tile type is non-negative"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.original_scale is Vector2, "Original scale is Vector2 type"
|
tile_instance.grid_position is Vector2i,
|
||||||
|
"Grid position is Vector2i type"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
tile_instance.original_scale is Vector2,
|
||||||
|
"Original scale is Vector2 type"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test tile type bounds checking
|
# Test tile type bounds checking
|
||||||
@@ -324,20 +385,23 @@ func test_input_validation():
|
|||||||
tile_instance.set_active_gem_types([])
|
tile_instance.set_active_gem_types([])
|
||||||
# Should fall back to defaults or maintain previous state
|
# Should fall back to defaults or maintain previous state
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.get_active_gem_count() > 0, "Empty gem array handled gracefully"
|
tile_instance.get_active_gem_count() > 0,
|
||||||
|
"Empty gem array handled gracefully"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test null gem types array
|
# Test null gem types array
|
||||||
tile_instance.set_active_gem_types(null)
|
tile_instance.set_active_gem_types(null)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.get_active_gem_count() > 0, "Null gem array handled gracefully"
|
tile_instance.get_active_gem_count() > 0,
|
||||||
|
"Null gem array handled gracefully"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test invalid gem indices in array
|
# Test invalid gem indices in array
|
||||||
tile_instance.set_active_gem_types([0, 1, 99, 2]) # 99 is invalid
|
tile_instance.set_active_gem_types([0, 1, 99, 2]) # 99 is invalid
|
||||||
# Should use fallback or filter invalid indices
|
# Should use fallback or filter invalid indices
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.get_active_gem_count() > 0, "Invalid gem indices handled gracefully"
|
tile_instance.get_active_gem_count() > 0,
|
||||||
|
"Invalid gem indices handled gracefully"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test negative gem indices
|
# Test negative gem indices
|
||||||
@@ -345,7 +409,9 @@ func test_input_validation():
|
|||||||
TestHelperClass.assert_false(negative_add, "Negative gem index rejected")
|
TestHelperClass.assert_false(negative_add, "Negative gem index rejected")
|
||||||
|
|
||||||
# Test out-of-bounds gem indices
|
# Test out-of-bounds gem indices
|
||||||
var oob_add = tile_instance.add_gem_type(tile_instance.all_gem_textures.size())
|
var oob_add = tile_instance.add_gem_type(
|
||||||
|
tile_instance.all_gem_textures.size()
|
||||||
|
)
|
||||||
TestHelperClass.assert_false(oob_add, "Out-of-bounds gem index rejected")
|
TestHelperClass.assert_false(oob_add, "Out-of-bounds gem index rejected")
|
||||||
|
|
||||||
# Restore original state
|
# Restore original state
|
||||||
@@ -359,9 +425,15 @@ func test_scaling_and_sizing():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Test original scale calculation
|
# Test original scale calculation
|
||||||
TestHelperClass.assert_not_null(tile_instance.original_scale, "Original scale is calculated")
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_true(tile_instance.original_scale.x > 0, "Original scale X is positive")
|
tile_instance.original_scale, "Original scale is calculated"
|
||||||
TestHelperClass.assert_true(tile_instance.original_scale.y > 0, "Original scale Y is positive")
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
tile_instance.original_scale.x > 0, "Original scale X is positive"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
tile_instance.original_scale.y > 0, "Original scale Y is positive"
|
||||||
|
)
|
||||||
|
|
||||||
# Test that tile size is respected
|
# Test that tile size is respected
|
||||||
if tile_instance.sprite and tile_instance.sprite.texture:
|
if tile_instance.sprite and tile_instance.sprite.texture:
|
||||||
@@ -369,11 +441,14 @@ func test_scaling_and_sizing():
|
|||||||
var scaled_size = texture_size * tile_instance.original_scale
|
var scaled_size = texture_size * tile_instance.original_scale
|
||||||
var max_dimension = max(scaled_size.x, scaled_size.y)
|
var max_dimension = max(scaled_size.x, scaled_size.y)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
max_dimension <= tile_instance.TILE_SIZE + 1, "Scaled tile fits within TILE_SIZE"
|
max_dimension <= tile_instance.TILE_SIZE + 1,
|
||||||
|
"Scaled tile fits within TILE_SIZE"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test scale animation for visual feedback
|
# Test scale animation for visual feedback
|
||||||
var original_scale = tile_instance.sprite.scale if tile_instance.sprite else Vector2.ONE
|
var original_scale = (
|
||||||
|
tile_instance.sprite.scale if tile_instance.sprite else Vector2.ONE
|
||||||
|
)
|
||||||
|
|
||||||
# Test selection scaling
|
# Test selection scaling
|
||||||
tile_instance.is_selected = true
|
tile_instance.is_selected = true
|
||||||
@@ -383,7 +458,8 @@ func test_scaling_and_sizing():
|
|||||||
if tile_instance.sprite:
|
if tile_instance.sprite:
|
||||||
var selected_scale = tile_instance.sprite.scale
|
var selected_scale = tile_instance.sprite.scale
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
selected_scale.x >= original_scale.x, "Selected tile scale is larger or equal"
|
selected_scale.x >= original_scale.x,
|
||||||
|
"Selected tile scale is larger or equal"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reset to normal
|
# Reset to normal
|
||||||
@@ -420,7 +496,8 @@ func test_memory_safety():
|
|||||||
|
|
||||||
# Test gem types array integrity
|
# Test gem types array integrity
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.active_gem_types is Array, "Active gem types maintains Array type"
|
tile_instance.active_gem_types is Array,
|
||||||
|
"Active gem types maintains Array type"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test that gem indices are within bounds
|
# Test that gem indices are within bounds
|
||||||
@@ -444,12 +521,16 @@ func test_error_handling():
|
|||||||
|
|
||||||
# Test that tile type setting handles null sprite gracefully
|
# Test that tile type setting handles null sprite gracefully
|
||||||
tile_instance.tile_type = 0 # Use public property instead
|
tile_instance.tile_type = 0 # Use public property instead
|
||||||
TestHelperClass.assert_true(true, "Tile type setting handles null sprite gracefully")
|
TestHelperClass.assert_true(
|
||||||
|
true, "Tile type setting handles null sprite gracefully"
|
||||||
|
)
|
||||||
|
|
||||||
# Test that scaling handles null sprite gracefully
|
# Test that scaling handles null sprite gracefully
|
||||||
# Force redraw to trigger scaling logic
|
# Force redraw to trigger scaling logic
|
||||||
tile_instance.queue_redraw()
|
tile_instance.queue_redraw()
|
||||||
TestHelperClass.assert_true(true, "Sprite scaling handles null sprite gracefully")
|
TestHelperClass.assert_true(
|
||||||
|
true, "Sprite scaling handles null sprite gracefully"
|
||||||
|
)
|
||||||
|
|
||||||
# Restore sprite
|
# Restore sprite
|
||||||
tile_instance.sprite = backup_sprite
|
tile_instance.sprite = backup_sprite
|
||||||
@@ -473,7 +554,10 @@ func test_error_handling():
|
|||||||
tile_instance.set_active_gem_types(large_gem_types)
|
tile_instance.set_active_gem_types(large_gem_types)
|
||||||
# Should fall back to safe defaults
|
# Should fall back to safe defaults
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
tile_instance.get_active_gem_count() <= tile_instance.all_gem_textures.size(),
|
(
|
||||||
|
tile_instance.get_active_gem_count()
|
||||||
|
<= tile_instance.all_gem_textures.size()
|
||||||
|
),
|
||||||
"Large gem array handled safely"
|
"Large gem array handled safely"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ func setup_test_environment():
|
|||||||
|
|
||||||
# Load ValueStepper scene
|
# Load ValueStepper scene
|
||||||
stepper_scene = load("res://scenes/ui/components/ValueStepper.tscn")
|
stepper_scene = load("res://scenes/ui/components/ValueStepper.tscn")
|
||||||
TestHelperClass.assert_not_null(stepper_scene, "ValueStepper scene loads successfully")
|
TestHelperClass.assert_not_null(
|
||||||
|
stepper_scene, "ValueStepper scene loads successfully"
|
||||||
|
)
|
||||||
|
|
||||||
# Create test viewport for isolated testing
|
# Create test viewport for isolated testing
|
||||||
test_viewport = SubViewport.new()
|
test_viewport = SubViewport.new()
|
||||||
@@ -79,15 +81,23 @@ func test_basic_functionality():
|
|||||||
TestHelperClass.print_step("Basic Functionality")
|
TestHelperClass.print_step("Basic Functionality")
|
||||||
|
|
||||||
if not stepper_instance:
|
if not stepper_instance:
|
||||||
TestHelperClass.assert_true(false, "ValueStepper instance not available for testing")
|
TestHelperClass.assert_true(
|
||||||
|
false, "ValueStepper instance not available for testing"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Test that ValueStepper has expected properties
|
# Test that ValueStepper has expected properties
|
||||||
var expected_properties = [
|
var expected_properties = [
|
||||||
"data_source", "custom_format_function", "values", "display_names", "current_index"
|
"data_source",
|
||||||
|
"custom_format_function",
|
||||||
|
"values",
|
||||||
|
"display_names",
|
||||||
|
"current_index"
|
||||||
]
|
]
|
||||||
for prop in expected_properties:
|
for prop in expected_properties:
|
||||||
TestHelperClass.assert_true(prop in stepper_instance, "ValueStepper has property: " + prop)
|
TestHelperClass.assert_true(
|
||||||
|
prop in stepper_instance, "ValueStepper has property: " + prop
|
||||||
|
)
|
||||||
|
|
||||||
# Test that ValueStepper has expected methods
|
# Test that ValueStepper has expected methods
|
||||||
var expected_methods = [
|
var expected_methods = [
|
||||||
@@ -105,12 +115,17 @@ func test_basic_functionality():
|
|||||||
|
|
||||||
# Test signals
|
# Test signals
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
stepper_instance.has_signal("value_changed"), "ValueStepper has value_changed signal"
|
stepper_instance.has_signal("value_changed"),
|
||||||
|
"ValueStepper has value_changed signal"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test UI components
|
# Test UI components
|
||||||
TestHelperClass.assert_not_null(stepper_instance.left_button, "Left button is available")
|
TestHelperClass.assert_not_null(
|
||||||
TestHelperClass.assert_not_null(stepper_instance.right_button, "Right button is available")
|
stepper_instance.left_button, "Left button is available"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_not_null(
|
||||||
|
stepper_instance.right_button, "Right button is available"
|
||||||
|
)
|
||||||
TestHelperClass.assert_not_null(
|
TestHelperClass.assert_not_null(
|
||||||
stepper_instance.value_display, "Value display label is available"
|
stepper_instance.value_display, "Value display label is available"
|
||||||
)
|
)
|
||||||
@@ -135,24 +150,33 @@ func test_data_source_loading():
|
|||||||
|
|
||||||
# Test default language data source
|
# Test default language data source
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"language", stepper_instance.data_source, "Default data source is language"
|
"language",
|
||||||
|
stepper_instance.data_source,
|
||||||
|
"Default data source is language"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test that values are loaded
|
# Test that values are loaded
|
||||||
TestHelperClass.assert_not_null(stepper_instance.values, "Values array is initialized")
|
TestHelperClass.assert_not_null(
|
||||||
|
stepper_instance.values, "Values array is initialized"
|
||||||
|
)
|
||||||
TestHelperClass.assert_not_null(
|
TestHelperClass.assert_not_null(
|
||||||
stepper_instance.display_names, "Display names array is initialized"
|
stepper_instance.display_names, "Display names array is initialized"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(stepper_instance.values is Array, "Values is Array type")
|
TestHelperClass.assert_true(
|
||||||
|
stepper_instance.values is Array, "Values is Array type"
|
||||||
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
stepper_instance.display_names is Array, "Display names is Array type"
|
stepper_instance.display_names is Array, "Display names is Array type"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test that language data is loaded correctly
|
# Test that language data is loaded correctly
|
||||||
if stepper_instance.data_source == "language":
|
if stepper_instance.data_source == "language":
|
||||||
TestHelperClass.assert_true(stepper_instance.values.size() > 0, "Language values loaded")
|
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
stepper_instance.display_names.size() > 0, "Language display names loaded"
|
stepper_instance.values.size() > 0, "Language values loaded"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
stepper_instance.display_names.size() > 0,
|
||||||
|
"Language display names loaded"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
stepper_instance.values.size(),
|
stepper_instance.values.size(),
|
||||||
@@ -161,7 +185,9 @@ func test_data_source_loading():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Test that current language is properly selected
|
# Test that current language is properly selected
|
||||||
var current_lang = root.get_node("SettingsManager").get_setting("language")
|
var current_lang = root.get_node("SettingsManager").get_setting(
|
||||||
|
"language"
|
||||||
|
)
|
||||||
var expected_index = stepper_instance.values.find(current_lang)
|
var expected_index = stepper_instance.values.find(current_lang)
|
||||||
if expected_index >= 0:
|
if expected_index >= 0:
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
@@ -176,9 +202,13 @@ func test_data_source_loading():
|
|||||||
test_viewport.add_child(resolution_stepper)
|
test_viewport.add_child(resolution_stepper)
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
||||||
TestHelperClass.assert_true(resolution_stepper.values.size() > 0, "Resolution values loaded")
|
TestHelperClass.assert_true(
|
||||||
|
resolution_stepper.values.size() > 0, "Resolution values loaded"
|
||||||
|
)
|
||||||
TestHelperClass.assert_contains(
|
TestHelperClass.assert_contains(
|
||||||
resolution_stepper.values, "1920x1080", "Resolution data contains expected value"
|
resolution_stepper.values,
|
||||||
|
"1920x1080",
|
||||||
|
"Resolution data contains expected value"
|
||||||
)
|
)
|
||||||
|
|
||||||
resolution_stepper.queue_free()
|
resolution_stepper.queue_free()
|
||||||
@@ -189,9 +219,13 @@ func test_data_source_loading():
|
|||||||
test_viewport.add_child(difficulty_stepper)
|
test_viewport.add_child(difficulty_stepper)
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
||||||
TestHelperClass.assert_true(difficulty_stepper.values.size() > 0, "Difficulty values loaded")
|
TestHelperClass.assert_true(
|
||||||
|
difficulty_stepper.values.size() > 0, "Difficulty values loaded"
|
||||||
|
)
|
||||||
TestHelperClass.assert_contains(
|
TestHelperClass.assert_contains(
|
||||||
difficulty_stepper.values, "normal", "Difficulty data contains expected value"
|
difficulty_stepper.values,
|
||||||
|
"normal",
|
||||||
|
"Difficulty data contains expected value"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
1, difficulty_stepper.current_index, "Difficulty defaults to normal"
|
1, difficulty_stepper.current_index, "Difficulty defaults to normal"
|
||||||
@@ -214,13 +248,17 @@ func test_value_navigation():
|
|||||||
var initial_value = stepper_instance.get_current_value()
|
var initial_value = stepper_instance.get_current_value()
|
||||||
stepper_instance.change_value(1)
|
stepper_instance.change_value(1)
|
||||||
var next_value = stepper_instance.get_current_value()
|
var next_value = stepper_instance.get_current_value()
|
||||||
TestHelperClass.assert_not_equal(initial_value, next_value, "Forward navigation changes value")
|
TestHelperClass.assert_not_equal(
|
||||||
|
initial_value, next_value, "Forward navigation changes value"
|
||||||
|
)
|
||||||
|
|
||||||
# Test backward navigation
|
# Test backward navigation
|
||||||
stepper_instance.change_value(-1)
|
stepper_instance.change_value(-1)
|
||||||
var back_value = stepper_instance.get_current_value()
|
var back_value = stepper_instance.get_current_value()
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
initial_value, back_value, "Backward navigation returns to original value"
|
initial_value,
|
||||||
|
back_value,
|
||||||
|
"Backward navigation returns to original value"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test wrap-around forward
|
# Test wrap-around forward
|
||||||
@@ -228,14 +266,18 @@ func test_value_navigation():
|
|||||||
stepper_instance.current_index = max_index
|
stepper_instance.current_index = max_index
|
||||||
stepper_instance.change_value(1)
|
stepper_instance.change_value(1)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
0, stepper_instance.current_index, "Forward navigation wraps to beginning"
|
0,
|
||||||
|
stepper_instance.current_index,
|
||||||
|
"Forward navigation wraps to beginning"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test wrap-around backward
|
# Test wrap-around backward
|
||||||
stepper_instance.current_index = 0
|
stepper_instance.current_index = 0
|
||||||
stepper_instance.change_value(-1)
|
stepper_instance.change_value(-1)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
max_index, stepper_instance.current_index, "Backward navigation wraps to end"
|
max_index,
|
||||||
|
stepper_instance.current_index,
|
||||||
|
"Backward navigation wraps to end"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Restore original state
|
# Restore original state
|
||||||
@@ -258,13 +300,19 @@ func test_custom_values():
|
|||||||
var custom_values = ["apple", "banana", "cherry"]
|
var custom_values = ["apple", "banana", "cherry"]
|
||||||
stepper_instance.setup_custom_values(custom_values)
|
stepper_instance.setup_custom_values(custom_values)
|
||||||
|
|
||||||
TestHelperClass.assert_equal(3, stepper_instance.values.size(), "Custom values set correctly")
|
TestHelperClass.assert_equal(
|
||||||
TestHelperClass.assert_equal("apple", stepper_instance.values[0], "First custom value correct")
|
3, stepper_instance.values.size(), "Custom values set correctly"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_equal(
|
||||||
|
"apple", stepper_instance.values[0], "First custom value correct"
|
||||||
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
0, stepper_instance.current_index, "Index reset to 0 for custom values"
|
0, stepper_instance.current_index, "Index reset to 0 for custom values"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"apple", stepper_instance.get_current_value(), "Current value matches first custom value"
|
"apple",
|
||||||
|
stepper_instance.get_current_value(),
|
||||||
|
"Current value matches first custom value"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test custom values with display names
|
# Test custom values with display names
|
||||||
@@ -272,31 +320,43 @@ func test_custom_values():
|
|||||||
stepper_instance.setup_custom_values(custom_values, custom_display_names)
|
stepper_instance.setup_custom_values(custom_values, custom_display_names)
|
||||||
|
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
3, stepper_instance.display_names.size(), "Custom display names set correctly"
|
3,
|
||||||
|
stepper_instance.display_names.size(),
|
||||||
|
"Custom display names set correctly"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"Red Apple", stepper_instance.display_names[0], "First display name correct"
|
"Red Apple",
|
||||||
|
stepper_instance.display_names[0],
|
||||||
|
"First display name correct"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test navigation with custom values
|
# Test navigation with custom values
|
||||||
stepper_instance.change_value(1)
|
stepper_instance.change_value(1)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"banana", stepper_instance.get_current_value(), "Navigation works with custom values"
|
"banana",
|
||||||
|
stepper_instance.get_current_value(),
|
||||||
|
"Navigation works with custom values"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test set_current_value
|
# Test set_current_value
|
||||||
stepper_instance.set_current_value("cherry")
|
stepper_instance.set_current_value("cherry")
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"cherry", stepper_instance.get_current_value(), "set_current_value works correctly"
|
"cherry",
|
||||||
|
stepper_instance.get_current_value(),
|
||||||
|
"set_current_value works correctly"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
2, stepper_instance.current_index, "Index updated correctly by set_current_value"
|
2,
|
||||||
|
stepper_instance.current_index,
|
||||||
|
"Index updated correctly by set_current_value"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test invalid value
|
# Test invalid value
|
||||||
stepper_instance.set_current_value("grape")
|
stepper_instance.set_current_value("grape")
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"cherry", stepper_instance.get_current_value(), "Invalid value doesn't change current value"
|
"cherry",
|
||||||
|
stepper_instance.get_current_value(),
|
||||||
|
"Invalid value doesn't change current value"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Restore original state
|
# Restore original state
|
||||||
@@ -319,7 +379,9 @@ func test_input_handling():
|
|||||||
var left_handled = stepper_instance.handle_input_action("move_left")
|
var left_handled = stepper_instance.handle_input_action("move_left")
|
||||||
TestHelperClass.assert_true(left_handled, "Left input action handled")
|
TestHelperClass.assert_true(left_handled, "Left input action handled")
|
||||||
TestHelperClass.assert_not_equal(
|
TestHelperClass.assert_not_equal(
|
||||||
original_value, stepper_instance.get_current_value(), "Left action changes value"
|
original_value,
|
||||||
|
stepper_instance.get_current_value(),
|
||||||
|
"Left action changes value"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test right input action
|
# Test right input action
|
||||||
@@ -333,14 +395,18 @@ func test_input_handling():
|
|||||||
|
|
||||||
# Test invalid input action
|
# Test invalid input action
|
||||||
var invalid_handled = stepper_instance.handle_input_action("invalid_action")
|
var invalid_handled = stepper_instance.handle_input_action("invalid_action")
|
||||||
TestHelperClass.assert_false(invalid_handled, "Invalid input action not handled")
|
TestHelperClass.assert_false(
|
||||||
|
invalid_handled, "Invalid input action not handled"
|
||||||
|
)
|
||||||
|
|
||||||
# Test button press simulation
|
# Test button press simulation
|
||||||
if stepper_instance.left_button:
|
if stepper_instance.left_button:
|
||||||
var before_left = stepper_instance.get_current_value()
|
var before_left = stepper_instance.get_current_value()
|
||||||
stepper_instance.handle_input_action("move_left")
|
stepper_instance.handle_input_action("move_left")
|
||||||
TestHelperClass.assert_not_equal(
|
TestHelperClass.assert_not_equal(
|
||||||
before_left, stepper_instance.get_current_value(), "Left button press changes value"
|
before_left,
|
||||||
|
stepper_instance.get_current_value(),
|
||||||
|
"Left button press changes value"
|
||||||
)
|
)
|
||||||
|
|
||||||
if stepper_instance.right_button:
|
if stepper_instance.right_button:
|
||||||
@@ -365,9 +431,12 @@ func test_visual_feedback():
|
|||||||
|
|
||||||
# Test highlighting
|
# Test highlighting
|
||||||
stepper_instance.set_highlighted(true)
|
stepper_instance.set_highlighted(true)
|
||||||
TestHelperClass.assert_true(stepper_instance.is_highlighted, "Highlighted state set correctly")
|
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
stepper_instance.scale.x > original_scale.x, "Scale increased when highlighted"
|
stepper_instance.is_highlighted, "Highlighted state set correctly"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_true(
|
||||||
|
stepper_instance.scale.x > original_scale.x,
|
||||||
|
"Scale increased when highlighted"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test unhighlighting
|
# Test unhighlighting
|
||||||
@@ -376,17 +445,25 @@ func test_visual_feedback():
|
|||||||
stepper_instance.is_highlighted, "Highlighted state cleared correctly"
|
stepper_instance.is_highlighted, "Highlighted state cleared correctly"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
original_scale, stepper_instance.scale, "Scale restored when unhighlighted"
|
original_scale,
|
||||||
|
stepper_instance.scale,
|
||||||
|
"Scale restored when unhighlighted"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
original_modulate, stepper_instance.modulate, "Modulate restored when unhighlighted"
|
original_modulate,
|
||||||
|
stepper_instance.modulate,
|
||||||
|
"Modulate restored when unhighlighted"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test display update
|
# Test display update
|
||||||
if stepper_instance.value_display:
|
if stepper_instance.value_display:
|
||||||
var current_text = stepper_instance.value_display.text
|
var current_text = stepper_instance.value_display.text
|
||||||
TestHelperClass.assert_true(current_text.length() > 0, "Value display has text content")
|
TestHelperClass.assert_true(
|
||||||
TestHelperClass.assert_not_equal("N/A", current_text, "Value display shows valid content")
|
current_text.length() > 0, "Value display has text content"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_not_equal(
|
||||||
|
"N/A", current_text, "Value display shows valid content"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_settings_integration():
|
func test_settings_integration():
|
||||||
@@ -413,13 +490,17 @@ func test_settings_integration():
|
|||||||
# Value change is applied automatically through set_current_value
|
# Value change is applied automatically through set_current_value
|
||||||
|
|
||||||
# Verify setting was updated
|
# Verify setting was updated
|
||||||
var updated_lang = root.get_node("SettingsManager").get_setting("language")
|
var updated_lang = root.get_node("SettingsManager").get_setting(
|
||||||
|
"language"
|
||||||
|
)
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
target_lang, updated_lang, "Language setting updated correctly"
|
target_lang, updated_lang, "Language setting updated correctly"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Restore original language
|
# Restore original language
|
||||||
root.get_node("SettingsManager").set_setting("language", original_lang)
|
root.get_node("SettingsManager").set_setting(
|
||||||
|
"language", original_lang
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_boundary_conditions():
|
func test_boundary_conditions():
|
||||||
@@ -435,12 +516,16 @@ func test_boundary_conditions():
|
|||||||
await process_frame
|
await process_frame
|
||||||
|
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"", empty_stepper.get_current_value(), "Empty values array returns empty string"
|
"",
|
||||||
|
empty_stepper.get_current_value(),
|
||||||
|
"Empty values array returns empty string"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test change_value with empty array
|
# Test change_value with empty array
|
||||||
empty_stepper.change_value(1) # Should not crash
|
empty_stepper.change_value(1) # Should not crash
|
||||||
TestHelperClass.assert_true(true, "change_value handles empty array gracefully")
|
TestHelperClass.assert_true(
|
||||||
|
true, "change_value handles empty array gracefully"
|
||||||
|
)
|
||||||
|
|
||||||
empty_stepper.queue_free()
|
empty_stepper.queue_free()
|
||||||
|
|
||||||
@@ -450,14 +535,18 @@ func test_boundary_conditions():
|
|||||||
stepper_instance.current_index = -1
|
stepper_instance.current_index = -1
|
||||||
# Display updates automatically when value changes
|
# Display updates automatically when value changes
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"N/A", stepper_instance.value_display.text, "Negative index shows N/A"
|
"N/A",
|
||||||
|
stepper_instance.value_display.text,
|
||||||
|
"Negative index shows N/A"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test out-of-bounds index handling
|
# Test out-of-bounds index handling
|
||||||
stepper_instance.current_index = stepper_instance.values.size()
|
stepper_instance.current_index = stepper_instance.values.size()
|
||||||
# Display updates automatically when value changes
|
# Display updates automatically when value changes
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"N/A", stepper_instance.value_display.text, "Out-of-bounds index shows N/A"
|
"N/A",
|
||||||
|
stepper_instance.value_display.text,
|
||||||
|
"Out-of-bounds index shows N/A"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Restore valid index
|
# Restore valid index
|
||||||
@@ -487,7 +576,8 @@ func test_error_handling():
|
|||||||
control_name.ends_with("_stepper"), "Control name has correct suffix"
|
control_name.ends_with("_stepper"), "Control name has correct suffix"
|
||||||
)
|
)
|
||||||
TestHelperClass.assert_true(
|
TestHelperClass.assert_true(
|
||||||
control_name.begins_with(stepper_instance.data_source), "Control name includes data source"
|
control_name.begins_with(stepper_instance.data_source),
|
||||||
|
"Control name includes data source"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test custom values with mismatched arrays
|
# Test custom values with mismatched arrays
|
||||||
@@ -496,16 +586,22 @@ func test_error_handling():
|
|||||||
stepper_instance.setup_custom_values(values_3, names_2)
|
stepper_instance.setup_custom_values(values_3, names_2)
|
||||||
|
|
||||||
# Should handle gracefully - display_names should be duplicated from values
|
# Should handle gracefully - display_names should be duplicated from values
|
||||||
TestHelperClass.assert_equal(3, stepper_instance.values.size(), "Values array size preserved")
|
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
2, stepper_instance.display_names.size(), "Display names size preserved as provided"
|
3, stepper_instance.values.size(), "Values array size preserved"
|
||||||
|
)
|
||||||
|
TestHelperClass.assert_equal(
|
||||||
|
2,
|
||||||
|
stepper_instance.display_names.size(),
|
||||||
|
"Display names size preserved as provided"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test navigation with mismatched arrays
|
# Test navigation with mismatched arrays
|
||||||
stepper_instance.current_index = 2 # Index where display_names doesn't exist
|
stepper_instance.current_index = 2 # Index where display_names doesn't exist
|
||||||
# Display updates automatically when value changes
|
# Display updates automatically when value changes
|
||||||
TestHelperClass.assert_equal(
|
TestHelperClass.assert_equal(
|
||||||
"c", stepper_instance.value_display.text, "Falls back to value when display name missing"
|
"c",
|
||||||
|
stepper_instance.value_display.text,
|
||||||
|
"Falls back to value when display name missing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user