rework missions to be a little more in alignment with standards and make more sense xD

pull/15/head
Wade 2025-04-08 21:26:26 +07:00
parent 6da3808f15
commit 65ab28253f
19 changed files with 708 additions and 420 deletions

@ -31,3 +31,5 @@ feedback_text = ""
incorrect_feedback = ""
company_data = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])

@ -40,3 +40,5 @@ feedback_text = ""
incorrect_feedback = ""
company_data = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])

@ -15,24 +15,32 @@ completed = false
[resource]
script = ExtResource("2_mum3p")
id = "4"
title = "Power Math Challenge"
description = "Your city of 40 houses needs a reliable electricity supply. Calculate the total power demand using the formula and determine how many power plants you need to build. This will require using mathematical skills with exponents and radicals."
title = "Residential Energy Usage"
description = ""
objectives = Array[ExtResource("1_dhx01")]([SubResource("Resource_c06be")])
rewards = {
"cash": 0
}
next_mission_id = "5"
graph_path = ""
full_screen_path = "res://images/city_expansion.png"
intro_text = "Your city now has 40 houses and requires reliable electricity. You'll need to build the right number of power plants to meet your city's needs."
question_text = "How many power plants are needed to power 40 houses? (Enter a number)"
correct_answer = "1"
feedback_text = "Correct! Your calculations are accurate. Total power demand is 2×√40 + 40^0.8 = 12.64 + 19.14 = 31.78 kilowatts. Since each plant generates 40 kilowatts, 1 power plant is sufficient. The plant will power all houses within a 31.6 grid unit radius (5×√40 = 31.6)."
incorrect_feedback = "Not quite right. Remember to follow these steps:
1. Calculate power needed: 2×√40 + 40^0.8
2. For √40, use 6.32 (or calculate precisely)
3. For 40^0.8, use about 19.14
4. Add both parts: 12.64 + 19.14 = 31.78 kilowatts
5. Divide by plant output (40 kW) and round up to a whole number"
full_screen_path = ""
intro_text = "A neighborhood is calculating energy usage from gaming consoles and TVs.
At House A, they ran 2 gaming consoles and 3 TVs and used a total of 660 kilowatt-hours (kWh) of electricity.
At House B, they ran 4 gaming consoles and 1 TV and used 760 kWh of electricity."
question_text = ""
correct_answer = "150,70"
feedback_text = "Correct! You've accurately calculated that gaming consoles use 150 kWh and TVs use 70 kWh. Using a system of equations: 2x + 3y = 660 and 4x + y = 760, you can solve by substitution to find x = 150 and y = 70."
incorrect_feedback = "Not quite right. Try setting up a system of equations:
- House A: 2 gaming consoles + 3 TVs = 660 kWh
- House B: 4 gaming consoles + 1 TV = 760 kWh
Let x be the kWh for gaming consoles and y be the kWh for TVs.
So we have: 2x + 3y = 660 and 4x + y = 760
Solve for y in the second equation: y = 760 - 4x
Substitute into the first equation: 2x + 3(760 - 4x) = 660
Simplify: 2x + 2280 - 12x = 660
Solve: -10x = -1620
Therefore: x = 150 and y = 70"
company_data = ""
power_math_content = ""
num_of_user_inputs = 2
input_labels = Array[String](["Gaming Consoles", "TVs"])

@ -16,7 +16,7 @@ completed = false
script = ExtResource("2_mum3p")
id = "2"
title = "Compare Construction Companies"
description = "Study the company data below, find the unit rates (houses per worker), and determine which company would require fewer workers to build 40 houses in a week."
description = ""
objectives = Array[ExtResource("1_dhx01")]([SubResource("Resource_c06be")])
rewards = {
"cash": 0
@ -24,25 +24,12 @@ rewards = {
next_mission_id = ""
graph_path = "res://images/mission_2.png"
full_screen_path = ""
intro_text = "Your city is rapidly growing, and you need to build houses to accommodate new residents! Two different construction companies offer to help."
question_text = "Which company requires fewer workers to build 40 houses in a week? (A or B)"
intro_text = "Your city is rapidly growing, and you need to build houses to accommodate new residents! Two different construction companies offer to help. Study the company data below, find the unit rates (houses per worker), and determine which company would require fewer workers to build 40 houses in a week. Which company requires fewer workers to build 40 houses in a week? (A or B)"
question_text = ""
correct_answer = "A"
feedback_text = "Correct! Company A (City Builders Inc.) would require fewer workers to build 40 houses. Company A builds at a rate of 4 houses per worker per week, while Company B builds at a rate of 3 houses per worker per week. For 40 houses, Company A needs 10 workers while Company B needs about 13.33 workers."
incorrect_feedback = "Not quite right. Look carefully at the data for both companies. Compare their rates: Company A builds 4 houses per worker per week, while Company B builds 3 houses per worker per week. Calculate how many workers each would need for 40 houses."
company_data = "[b][color=#60c2a8]Company A: City Builders Inc.[/color][/b]
• 2 workers build 6 houses per week
• 4 workers build 12 houses per week
• 6 workers build 18 houses per week
• 10 workers build 30 houses per week
[b][color=#e06666]Company B: Urban Growth Solutions[/color][/b]
• 3 workers build 9 houses per week
• 6 workers build 18 houses per week
• 9 workers build 27 houses per week
• 12 workers build 36 houses per week
If you need 40 houses in a week, which company would require fewer workers?
Enter A or B below.
Hint: Find the pattern for each company, then calculate how many workers would be needed for 40 houses."
company_data = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])

@ -31,3 +31,5 @@ feedback_text = ""
incorrect_feedback = ""
company_data = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])

@ -4,15 +4,16 @@ importer="texture"
type="CompressedTexture2D"
uid="uid://bnn1b2yg61elu"
path.s3tc="res://.godot/imported/colormap.png-c1bc3c3aabeec406ff4b53328583776a.s3tc.ctex"
path.etc2="res://.godot/imported/colormap.png-c1bc3c3aabeec406ff4b53328583776a.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://models/Textures/colormap.png"
dest_files=["res://.godot/imported/colormap.png-c1bc3c3aabeec406ff4b53328583776a.s3tc.ctex"]
dest_files=["res://.godot/imported/colormap.png-c1bc3c3aabeec406ff4b53328583776a.s3tc.ctex", "res://.godot/imported/colormap.png-c1bc3c3aabeec406ff4b53328583776a.etc2.ctex"]
[params]

@ -4,15 +4,16 @@ importer="texture"
type="CompressedTexture2D"
uid="uid://crbry5s73b6rw"
path.s3tc="res://.godot/imported/colormap.png-212e5588ca846efe35817fd63dff6086.s3tc.ctex"
path.etc2="res://.godot/imported/colormap.png-212e5588ca846efe35817fd63dff6086.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://people/Textures/colormap.png"
dest_files=["res://.godot/imported/colormap.png-212e5588ca846efe35817fd63dff6086.s3tc.ctex"]
dest_files=["res://.godot/imported/colormap.png-212e5588ca846efe35817fd63dff6086.s3tc.ctex", "res://.godot/imported/colormap.png-212e5588ca846efe35817fd63dff6086.etc2.ctex"]
[params]

@ -12,12 +12,16 @@ config_version=5
config/name="Starter Kit City Builder"
config/tags=PackedStringArray("starterkit")
run/main_scene="res://scenes/attribution_screen.tscn"
run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.3", "Forward Plus")
boot_splash/bg_color=Color(0.92549, 0.92549, 0.960784, 1)
boot_splash/image="res://splash-screen.png"
config/icon="res://icon.png"
[audio]
general/default_playback_type.web=0
[autoload]
SoundManager="*res://scripts/sound_manager.gd"
@ -113,6 +117,7 @@ camera_center={
[rendering]
textures/vram_compression/import_etc2_astc=true
anti_aliasing/quality/msaa_3d=2
debug/shapes/navigation/enable_edge_lines=false
debug/shapes/navigation/enable_edge_lines.web=false

@ -151,13 +151,6 @@ layout_mode = 2
theme_override_constants/separation = 5
alignment = 1
[node name="InputLabel" type="Label" parent="PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/UserInputContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0.376471, 0.760784, 0.658824, 1)
theme_override_font_sizes/font_size = 32
text = "Enter your answer:"
horizontal_alignment = 1
[node name="UserInput" type="LineEdit" parent="PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/UserInputContainer"]
custom_minimum_size = Vector2(600, 80)
layout_mode = 2

@ -50,8 +50,6 @@ func _ready():
construction_manager.builder = self
construction_manager.nav_region = nav_region
print("BUILDER: Created BuildingConstructionManager:", construction_manager)
# Sound effects now handled in game_manager.gd
for structure in structures:
@ -112,31 +110,24 @@ func is_mouse_over_ui() -> bool:
# Get mouse position
var mouse_pos = get_viewport().get_mouse_position()
# Add diagnostic output
print("Mouse position: ", mouse_pos)
# Let's try an extremely simple approach - just check coordinates
# most HUDs are at top of screen
if mouse_pos.y < 100:
# Mouse is likely in the HUD area at top of screen
print("Mouse in top area (likely HUD)")
return true
# Get HUD dimensions for debug
var hud = get_node_or_null("/root/Main/CanvasLayer/HUD")
if hud:
var hud_rect = hud.get_global_rect()
print("HUD rect: ", hud_rect)
# Get HBoxContainer dimensions - this is the actual content area
var hbox = hud.get_node_or_null("HBoxContainer")
if hbox:
var hbox_rect = hbox.get_global_rect()
print("HUD HBoxContainer rect: ", hbox_rect)
# Simple approach - just check if within actual HUD content area
if hbox_rect.has_point(mouse_pos):
print("Mouse over HUD content area")
return true
# Skip the complex recursion for now since it's not working
@ -145,30 +136,23 @@ func is_mouse_over_ui() -> bool:
var mission_panel = get_node_or_null("/root/Main/MissionManager/MissionPanel")
if mission_panel and mission_panel.visible:
var panel_rect = mission_panel.get_global_rect()
print("Mission panel rect: ", panel_rect)
if panel_rect.has_point(mouse_pos):
print("Mouse over mission panel")
return true
# Check learning panel too
var learning_panel = get_node_or_null("/root/Main/MissionManager/LearningPanel")
if learning_panel and learning_panel.visible:
var panel_rect = learning_panel.get_global_rect()
print("Learning panel rect: ", panel_rect)
if panel_rect.has_point(mouse_pos):
print("Mouse over learning panel")
return true
# Check controls panel
var controls_panel = get_node_or_null("/root/Main/CanvasLayer/ControlsPanel")
if controls_panel and controls_panel.visible:
var panel_rect = controls_panel.get_global_rect()
print("Controls panel rect: ", panel_rect)
if panel_rect.has_point(mouse_pos):
print("Mouse over controls panel")
return true
print("Mouse not over any UI element")
return false
# Retrieve the mesh from a PackedScene, used for dynamically creating a MeshLibrary
@ -307,6 +291,7 @@ func setup_navigation_region():
add_child(nav_region)
# Sound effects are now handled in game_manager.gd
@ -318,8 +303,7 @@ func rebake_navigation_mesh():
# Bake the navigation mesh for the entire map
nav_region.bake_navigation_mesh()
print("Navigation mesh rebaked")
# Demolish (remove) a structure
signal structure_removed(structure_index, position)
@ -358,8 +342,6 @@ func action_demolish(gridmap_position):
# Check for building model in the scene as a direct child of builder
var building_model_name = "Building_" + str(int(gridmap_position.x)) + "_" + str(int(gridmap_position.z))
var has_building_model = has_node(building_model_name)
if has_building_model:
print("Found building model: " + building_model_name)
# Store structure index before removal for signaling
var structure_index = -1
@ -501,7 +483,6 @@ func _add_road_to_navregion(position: Vector3, structure_index: int):
# Check if a road with this name already exists
if nav_region.has_node(road_name):
return
# Instantiate the road model - get the actual model based on road type
@ -547,7 +528,6 @@ func _add_power_plant(position: Vector3, structure_index: int):
# Check if a power plant with this name already exists
if has_node(power_plant_name):
return
# Instantiate the power plant model
@ -590,7 +570,8 @@ func _remove_power_plant(position: Vector3):
power_plant.queue_free()
else:
print("No power plant found at position ", position)
# No power plant found
pass
# Function to remove a resident model when a residential building is demolished
func _remove_resident_for_building(position: Vector3):
@ -606,7 +587,6 @@ func _remove_resident_for_building(position: Vector3):
var found = false
for child in nav_region.get_children():
if child.name.begins_with(resident_name):
print("Removing resident model for demolished building: ", child.name)
child.queue_free()
found = true
@ -624,7 +604,6 @@ func _remove_resident_for_building(position: Vector3):
var residents = get_tree().get_nodes_in_group("characters")
if residents.size() > 0:
# Just remove the first resident we find
print("Removing a resident model for demolished building (fallback)")
residents[0].queue_free()
# Update the HUD population count
@ -641,17 +620,10 @@ func _update_mission_objective_on_demolish():
if mission_manager and mission_manager.current_mission:
# Check if we're in mission 3 (build 40 residential buildings)
if mission_manager.current_mission.id == "3":
# For mission 3, do nothing here - the fix_mission.gd script will handle updating
# the counter via the structure_removed signal
print("Residential building demolished, fix_mission.gd will update the objective count")
# Population count is handled in _remove_resident_for_building
elif mission_manager.current_mission.id != "3":
if mission_manager.current_mission.id != "3":
# For other missions, use the normal method
var mission_id = mission_manager.current_mission.id
mission_manager.update_objective_progress(mission_id, MissionObjective.ObjectiveType.BUILD_RESIDENTIAL, -1)
print("Decremented residential building count in mission objective for mission " + mission_id)
# Function to remove terrain (grass or trees)
func _remove_terrain(position: Vector3):
@ -663,9 +635,9 @@ func _remove_terrain(position: Vector3):
# Get the terrain and remove it
var terrain = get_node(terrain_name)
terrain.queue_free()
print("Removed terrain at position ", position)
else:
print("No terrain found at position ", position)
# No terrain found
pass
# Function to remove building model from scene
func _remove_building_model(position: Vector3):
@ -686,7 +658,6 @@ func _remove_building_model(position: Vector3):
# Get the building and remove it
var building = get_node(pattern)
building.queue_free()
print("Removed building model at position ", position, " with name: ", pattern)
found = true
break
@ -701,7 +672,6 @@ func _remove_building_model(position: Vector3):
var pos_diff = (child.global_transform.origin - position).abs()
if pos_diff.x < 0.5 and pos_diff.z < 0.5:
child.queue_free()
print("Removed building model from nav_region at position ", position)
found = true
break
@ -718,7 +688,6 @@ func _remove_building_model(position: Vector3):
var pos_diff = (child.global_transform.origin - position).abs()
if pos_diff.x < 0.5 and pos_diff.z < 0.5:
child.queue_free()
print("Removed building model from Main at position ", position)
found = true
break
@ -729,12 +698,8 @@ func _remove_building_model(position: Vector3):
var pos_diff = (child.global_transform.origin - position).abs()
if pos_diff.x < 0.5 and pos_diff.z < 0.5:
child.queue_free()
print("Removed model from gridmap at position ", position, " with name: ", child.name)
found = true
break
if !found:
print("No building model found to remove at position ", position)
# Function to remove a road model from the navigation region
func _remove_road_from_navregion(position: Vector3):
@ -750,9 +715,9 @@ func _remove_road_from_navregion(position: Vector3):
# Get the road and remove it
var road = nav_region.get_node(road_name)
road.queue_free()
print("Removed road at position ", position, " from NavRegion3D")
else:
print("No road found at position ", position, " in NavRegion3D")
# No road found
pass
# Function to add all existing roads to the navigation region
func _add_existing_roads_to_navregion():
@ -766,7 +731,6 @@ func _add_existing_roads_to_navregion():
child.queue_free()
# Find all road cells in the gridmap
print("Finding and adding all existing roads to NavRegion3D...")
var added_count = 0
# We need to convert any existing roads in the GridMap to our new system
@ -781,8 +745,6 @@ func _add_existing_roads_to_navregion():
gridmap.set_cell_item(cell, -1)
added_count += 1
print("Added ", added_count, " existing roads to NavRegion3D")
# Function to move all character NPCs to be children of the navigation region
func _move_characters_to_navregion():
# Make sure we have a navigation region
@ -816,7 +778,6 @@ func _add_terrain(position: Vector3, structure_index: int):
# Check if terrain with this name already exists
if has_node(terrain_name):
return
# Instantiate the terrain model
@ -840,8 +801,6 @@ func _add_terrain(position: Vector3, structure_index: int):
# Apply the complete transform in one go
terrain_model.transform = transform
print("Added terrain at position ", position, " as direct child of builder")
# Callback for when construction is completed
func _on_construction_completed(position: Vector3):
@ -865,7 +824,6 @@ func _on_construction_completed(position: Vector3):
# Add the completed residential building to the gridmap with the correct rotation
gridmap.set_cell_item(position, residential_index, rotation_index)
print("Construction completed: added building to gridmap at ", position, " with rotation index ", rotation_index)
# Check if we need to spawn a character for mission 1
var mission_manager = get_node_or_null("/root/Main/MissionManager")
@ -876,7 +834,6 @@ func _on_construction_completed(position: Vector3):
# Now check if we need to manually handle mission 1 character spawning
if mission_manager.current_mission and mission_manager.current_mission.id == "1" and not mission_manager.character_spawned:
print("This is the first residential building in mission 1, spawning character")
mission_manager.character_spawned = true
mission_manager._spawn_character_on_road(position)
@ -885,7 +842,8 @@ func _on_construction_completed(position: Vector3):
# We don't emit the signal anymore to prevent double-counting
pass
else:
print("ERROR: No residential building structure found!")
# No residential building structure found
pass
# Make sure all characters (including newly spawned residents) are children of NavRegion3D
_move_characters_to_navregion()
@ -902,8 +860,6 @@ func _on_construction_completed(position: Vector3):
func action_save():
if Input.is_action_just_pressed("save"):
print("Saving map...")
map.structures.clear()
for cell in gridmap.get_used_cells():
@ -919,8 +875,6 @@ func action_save():
func action_load():
if Input.is_action_just_pressed("load"):
print("Loading map...")
gridmap.clear()
map = ResourceLoader.load("user://map.res")

@ -17,8 +17,6 @@ func _ready():
# Do initial count on mission start
update_mission_3_count()
else:
print("ERROR: Could not find Builder node for signal connections")
# Called when a structure is placed
func _on_structure_placed(structure_index, position):
@ -30,7 +28,6 @@ func _on_structure_removed(structure_index, position):
var builder = get_node_or_null("/root/Main/Builder")
if builder and structure_index >= 0 and structure_index < builder.structures.size():
if builder.structures[structure_index].type == 1: # Residential building
print("Residential building demolished at " + str(position) + ", updating mission count")
# Wait one frame to make sure the GridMap is updated
await get_tree().process_frame
# Update the count
@ -45,7 +42,6 @@ func update_mission_3_count():
# Find the mission manager
var mission_manager = get_node_or_null("/root/Main/MissionManager")
if not mission_manager:
print("ERROR: Could not find MissionManager")
return
# Check if we're in mission 3
@ -64,27 +60,22 @@ func update_mission_3_count():
if current_count != count:
# Reset the objective count to match the actual number
mission_manager.reset_objective_count(3, count) # 3 is the BUILD_RESIDENTIAL type
print("Updated mission 3 objective count to match actual building count: " + str(count))
func count_residential_buildings():
# Find the builder
var builder = get_node_or_null("/root/Main/Builder")
if not builder:
print("ERROR: Could not find Builder")
return 0
# Find the gridmap
var gridmap = builder.gridmap
if not gridmap:
print("ERROR: Could not find GridMap")
return 0
# Count residential buildings in the gridmap
var residential_count = 0
var found_positions = []
print("COUNTING: Starting residential building count")
# First count buildings in the gridmap
for cell in gridmap.get_used_cells():
var structure_index = gridmap.get_cell_item(cell)
@ -92,14 +83,10 @@ func count_residential_buildings():
if builder.structures[structure_index].type == 1: # 1 is RESIDENTIAL_BUILDING type
residential_count += 1
found_positions.append(Vector2(cell.x, cell.z))
print("COUNTING: Found residential building in GridMap at " + str(cell))
print("COUNTING: Found " + str(residential_count) + " buildings in GridMap")
# Also count completed buildings that might not be in the gridmap
if builder.has_node("NavRegion3D"):
var nav_region = builder.get_node("NavRegion3D")
var nav_buildings = 0
for child in nav_region.get_children():
if child.name.begins_with("Building_"):
@ -112,15 +99,10 @@ func count_residential_buildings():
# Only count if we haven't already counted this position
if not pos in found_positions:
residential_count += 1
nav_buildings += 1
found_positions.append(pos)
print("COUNTING: Found building model in NavRegion3D at " + str(pos))
print("COUNTING: Found " + str(nav_buildings) + " additional buildings in NavRegion3D")
# Also count any buildings under construction
if builder.construction_manager:
var construction_count = 0
for position in builder.construction_manager.construction_sites:
var site = builder.construction_manager.construction_sites[position]
if site.structure_index >= 0 and site.structure_index < builder.structures.size():
@ -131,13 +113,6 @@ func count_residential_buildings():
var pos = Vector2(position.x, position.z)
if not pos in found_positions:
residential_count += 1
construction_count += 1
found_positions.append(pos)
print("COUNTING: Found completed construction site at " + str(position))
else:
print("COUNTING: Ignoring completed construction site at " + str(position) + " because no building found in GridMap")
print("COUNTING: Found " + str(construction_count) + " buildings from construction sites")
print("COUNTING: Final total - " + str(residential_count) + " residential buildings")
return residential_count
return residential_count

@ -36,7 +36,6 @@ func _ready():
if not can_initialize_audio:
# For web, wait for the audio_ready signal before initializing audio
sound_manager.audio_ready.connect(_initialize_game_audio)
print("Web platform detected: Deferring audio setup until user interaction")
# Set up audio if allowed (immediate for desktop, after interaction for web)
if can_initialize_audio:
@ -55,24 +54,16 @@ func _ready():
# Initialize all game audio - called immediately on desktop, after user interaction on web
func _initialize_game_audio():
print("Initializing all game audio...")
# Set up all audio systems
setup_background_music()
setup_building_sfx()
setup_construction_sfx()
print("All game audio initialized successfully")
# This function is called when the controls panel is closed
func _on_controls_panel_closed():
print("Controls panel closed by player")
# This is the perfect place to initialize audio for web builds
# since we know the user has interacted with the game
if OS.has_feature("web"):
print("User closed controls panel - perfect time to ensure audio is working")
# Force initialize the sound manager (will have no effect if already initialized)
var sound_manager = get_node_or_null("/root/SoundManager")
if sound_manager and not sound_manager.audio_initialized:
@ -80,9 +71,8 @@ func _on_controls_panel_closed():
# Make sure our music is playing
if music_player and music_player.stream and not music_player.playing:
print("Starting background music after user interaction")
music_player.play()
# Function to set up the sound buses
func _setup_sound_buses():
# Wait a moment to ensure SoundManager is ready
@ -91,7 +81,6 @@ func _setup_sound_buses():
# Get reference to SoundManager singleton
var sound_manager = get_node_or_null("/root/SoundManager")
if !sound_manager:
print("ERROR: SoundManager singleton not found!")
return
# Move audio players to the appropriate buses
@ -103,8 +92,6 @@ func _setup_sound_buses():
if construction_sfx:
construction_sfx.bus = "SFX"
print("Sound buses configured successfully")
# Setup background music player
func setup_background_music():
@ -116,35 +103,22 @@ func setup_background_music():
# Use a direct file path for the music file to avoid any loading issues
var music_path = "res://sounds/jazz_new_orleans.mp3"
print("Loading music from path: " + music_path)
# Try both direct preload and load for maximum compatibility
var music = null
# Try preload first - this ensures MP3 is pre-decoded
print("Attempting to preload music file...")
music = preload("res://sounds/jazz_new_orleans.mp3")
# Log preload status
if music:
print("Music file preloaded successfully: " + str(music))
else:
print("Preload failed, falling back to regular load")
# If preload failed, try regular load
if !music:
music = load(music_path)
if music:
print("Music file loaded successfully via regular load: " + str(music))
# Continue setup if we have the music file
if music:
# Double-check the import settings
print("Music stream info - Class: " + str(music.get_class()))
# Set looping on the AudioStreamMP3 itself
if music is AudioStreamMP3:
music.loop = true
print("Set loop=true on AudioStreamMP3")
else:
print("Warning: Music is not AudioStreamMP3, it is " + str(music.get_class()))
music_player.stream = music
music_player.volume_db = 0 # Full volume for better web playback
@ -153,13 +127,9 @@ func setup_background_music():
# Direct check of music bus
var music_bus_idx = AudioServer.get_bus_index("Music")
if music_bus_idx >= 0:
print("Music bus found at index: " + str(music_bus_idx))
# Force bus volume
AudioServer.set_bus_volume_db(music_bus_idx, 0)
AudioServer.set_bus_mute(music_bus_idx, false)
print("Music bus volume set to: " + str(AudioServer.get_bus_volume_db(music_bus_idx)) + "dB")
else:
print("WARNING: Music bus not found!")
# Check if we can play audio immediately (desktop) or need to wait (web)
var can_play_now = true
@ -167,7 +137,6 @@ func setup_background_music():
var sound_manager = get_node_or_null("/root/SoundManager")
if sound_manager:
can_play_now = sound_manager.audio_initialized
print("Web build - audio initialized: " + str(can_play_now))
# Force SoundManager settings
sound_manager.music_volume = 1.0
@ -177,19 +146,14 @@ func setup_background_music():
# If not initialized, connect to the ready signal
if not can_play_now:
sound_manager.audio_ready.connect(_start_background_music)
print("Background music setup complete, waiting for user interaction")
# Play immediately if allowed
if can_play_now:
_start_background_music()
else:
print("ERROR: Could not load background music from path: " + music_path)
# Try a fallback sound as music
print("Attempting to load fallback sound...")
var fallback_sound = load("res://sounds/building_placing.wav")
if fallback_sound:
print("Loaded fallback sound")
music_player.stream = fallback_sound
music_player.volume_db = 0
music_player.bus = "Music"
@ -203,17 +167,12 @@ func setup_background_music():
if can_play_now:
music_player.play()
print("Playing fallback sound as music")
else:
print("Could not load fallback sound either")
# Start background music playing (called directly or via signal)
func _start_background_music():
if music_player and music_player.stream and not music_player.playing:
# For web builds, use a simple approach to starting audio
if OS.has_feature("web"):
print("Starting background music (web build)")
# Make sure we start from the beginning
music_player.stop()
music_player.seek(0.0)
@ -228,12 +187,10 @@ func _start_background_music():
# Music bus
var music_bus_idx = AudioServer.get_bus_index("Music")
if music_bus_idx >= 0:
print("Music bus found at index: " + str(music_bus_idx))
AudioServer.set_bus_mute(music_bus_idx, false)
# Play the music
music_player.play()
print("Started playing background music (web)")
# Simple JavaScript to ensure audio context is running
if Engine.has_singleton("JavaScriptBridge"):
@ -253,7 +210,6 @@ func _start_background_music():
else:
# Standard approach for desktop builds
music_player.play()
print("Started playing background music")
# This retry audio function has been removed in favor of the simpler approach
@ -272,9 +228,6 @@ func setup_building_sfx():
building_sfx.stream = sfx
building_sfx.volume_db = -5
building_sfx.bus = "SFX" # Use the SFX bus
print("Building placement SFX loaded successfully")
else:
print("ERROR: Could not load building placement SFX")
# Setup construction sound effects
# Note: Now mainly used for backward compatibility
@ -291,9 +244,6 @@ func setup_construction_sfx():
construction_sfx.stream = sfx
construction_sfx.volume_db = -8 # Reduced volume since workers have their own sounds
construction_sfx.bus = "SFX" # Use the SFX bus
print("Main construction SFX loaded (for backward compatibility)")
else:
print("ERROR: Could not load construction SFX")
# Play the building sound effect when a structure is placed
func _on_structure_placed(structure_index, position):
@ -309,10 +259,7 @@ func _on_structure_placed(structure_index, position):
if building_sfx.playing:
building_sfx.stop()
building_sfx.play()
print("Playing building placement SFX")
elif OS.has_feature("web"):
print("Structure placed but audio not yet initialized")
# Variables for construction sound looping
var construction_active = false
var construction_sound_timer = null
@ -322,9 +269,9 @@ var construction_sound_timer = null
# Compatibility function for mission triggers
func play_construction_sound():
print("GAME MANAGER: Received construction_started signal (for compatibility only)")
# We don't play any sounds from here anymore - workers handle their own sounds
# but we need to keep this function for backward compatibility
pass
# Compatibility function for mission triggers
func _loop_construction_sound():
@ -333,21 +280,18 @@ func _loop_construction_sound():
# Compatibility function for mission triggers
func stop_construction_sound():
print("GAME MANAGER: Received construction_ended signal (for compatibility only)")
# We don't stop any sounds from here anymore - workers handle their own sounds
# but we need to keep this function for backward compatibility
pass
# Removed duplicate _retry_music_play function that was here
# Setup construction signals properly
func _setup_construction_signals():
print("GAME MANAGER DELAYED: Attempting to find construction manager")
var builder = get_node_or_null("/root/Main/Builder")
print("GAME MANAGER DELAYED: Found builder:", builder)
if builder and builder.has_method("get") and builder.get("construction_manager"):
var construction_manager = builder.construction_manager
print("GAME MANAGER DELAYED: Construction manager found:", construction_manager)
if construction_manager:
# Disconnect any existing connections first to avoid duplicates
@ -360,8 +304,3 @@ func _setup_construction_signals():
# Connect signals
construction_manager.worker_construction_started.connect(play_construction_sound)
construction_manager.worker_construction_ended.connect(stop_construction_sound)
print("GAME MANAGER DELAYED: Connected to construction manager signals")
else:
print("GAME MANAGER DELAYED: Construction manager is null")
else:
print("GAME MANAGER DELAYED: Builder doesn't have construction_manager property")

@ -65,9 +65,6 @@ func _on_structure_placed(structure_index, position):
var structure = builder.structures[structure_index]
# Debug info
print("Structure placed: " + str(structure.type) + " with population: " + str(structure.population_count))
# Only update population for non-residential buildings or if we're NOT in the construction mission
var is_residential = structure.type == Structure.StructureType.RESIDENTIAL_BUILDING
var mission_manager = get_node_or_null("/root/Main/MissionManager")
@ -83,7 +80,6 @@ func _on_structure_placed(structure_index, position):
# Always update electricity usage/production
total_kW_usage += structure.kW_usage
total_kW_production += structure.kW_production
print("Energy updated - Usage: " + str(total_kW_usage) + " kW, Production: " + str(total_kW_production) + " kW")
# Update HUD
update_hud()
@ -117,7 +113,6 @@ func _on_structure_removed(structure_index, position):
# Update electricity
total_kW_usage = max(0, total_kW_usage - structure.kW_usage)
total_kW_production = max(0, total_kW_production - structure.kW_production)
print("Energy updated after removal - Usage: " + str(total_kW_usage) + " kW, Production: " + str(total_kW_production) + " kW")
# Update HUD
update_hud()
@ -165,7 +160,6 @@ func update_hud():
# Update the color of the indicator rectangle
if electricity_indicator:
electricity_indicator.color = indicator_color
print("Electricity indicator updated - Color: " + str(indicator_color))
# Tooltip handling
func _on_population_icon_mouse_entered():
@ -198,4 +192,4 @@ func _on_help_button_pressed():
get_viewport().set_input_as_handled()
if controls_panel:
controls_panel.show_panel()
controls_panel.show_panel()

@ -302,6 +302,52 @@ class JavaScriptGlobal:
else:
print("JavaScriptBridge singleton not available")
# Handle audio actions via JavaScript
static func handle_audio_action(action: String, sound_name: String = "", volume: float = -1.0):
if not OS.has_feature("web"):
return false
print("Handling audio action via JavaScript bridge: " + action)
var action_data = {
"action": action,
"sound": sound_name,
}
if volume >= 0.0:
action_data["volume"] = volume
var action_json = JSON.stringify(action_data)
var script = """
(function() {
try {
if (window.parent) {
console.log('Sending audio action to parent window:', %s);
window.parent.postMessage({
type: 'stemCity_audio',
data: %s,
source: 'godot-game',
timestamp: Date.now()
}, '*');
return true;
} else {
console.log('No parent window found for audio action');
return false;
}
} catch (e) {
console.error('Error sending audio action via postMessage:', e);
return false;
}
})();
""" % [action_json, action_json]
if Engine.has_singleton("JavaScriptBridge"):
var js = Engine.get_singleton("JavaScriptBridge")
return js.eval(script)
else:
print("JavaScriptBridge singleton not available")
return false
# Helper method to ensure the sound manager's audio is initialized
# Call this method after user interaction to ensure audio works
static func ensure_audio_initialized():
@ -310,16 +356,11 @@ class JavaScriptGlobal:
print("Ensuring audio is initialized via JavaScript bridge")
# Setup audio message listener if it's not already set up
setup_audio_message_listener()
# Try to initialize audio through the sound manager if it exists
var sound_manager = null
# Use the singleton pattern to find the SoundManager node
if Engine.get_main_loop().has_meta("sound_manager"):
sound_manager = Engine.get_main_loop().get_meta("sound_manager")
else:
# Try to find it in the scene tree
var scene_tree = Engine.get_main_loop() as SceneTree
if scene_tree:
sound_manager = scene_tree.root.get_node_or_null("/root/SoundManager")
var sound_manager = _get_sound_manager()
if sound_manager and sound_manager.has_method("init_web_audio_from_js"):
print("Found SoundManager, calling init_web_audio_from_js")
@ -393,3 +434,73 @@ class JavaScriptGlobal:
var result = js_interface.eval(script)
print("JavaScript audio initialization result:", result)
return result
# Setup audio message listener from JavaScript
static func setup_audio_message_listener():
if not OS.has_feature("web"):
return false
print("Setting up audio message listener via JavaScript bridge")
if not Engine.has_singleton("JavaScriptBridge"):
print("JavaScriptBridge singleton not available")
return false
var js = Engine.get_singleton("JavaScriptBridge")
# Register the callback function
js.set_callback("godot_audio_callback", Callable(_get_sound_manager(), "process_js_audio_state"))
# Set up a listener for audio state messages
var script = """
(function() {
// Set up message listener if not already done
if (!window.godot_audio_listener_initialized) {
window.addEventListener('message', function(event) {
if (event.data && event.data.type === 'stemCity_audio_state') {
console.log('Godot received audio state:', event.data);
// Call our Godot callback with the state data
if (typeof godot_audio_callback === 'function') {
console.log('Sending audio state to Godot');
godot_audio_callback(event.data.data);
} else {
console.warn('godot_audio_callback is not available');
}
}
});
console.log('Audio message listener initialized');
window.godot_audio_listener_initialized = true;
// Request initial audio state from parent
window.parent.postMessage({
type: 'stemCity_audio',
data: { action: 'GET_STATE' },
source: 'godot-game',
timestamp: Date.now()
}, '*');
return true;
}
return false;
})();
"""
var result = js.eval(script)
print("Audio message listener setup result: ", result)
return result
# Helper to get the sound manager instance
static func _get_sound_manager():
var sound_manager = null
# Try to find using meta
if Engine.get_main_loop().has_meta("sound_manager"):
sound_manager = Engine.get_main_loop().get_meta("sound_manager")
else:
# Try to find in scene tree
var scene_tree = Engine.get_main_loop() as SceneTree
if scene_tree:
sound_manager = scene_tree.root.get_node_or_null("/root/SoundManager")
return sound_manager

@ -25,34 +25,26 @@ func _ready():
if not worker_scene:
worker_scene = load("res://people/character-female-d.glb")
if not worker_scene:
print("WARNING: Could not load any character models for workers")
# Create an empty PackedScene as a last resort
worker_scene = PackedScene.new()
else:
print("Successfully loaded worker model: ", worker_scene.resource_path)
# Load the building plot scene (placeholder during construction)
building_plot_scene = load("res://models/building-small-a.glb")
if not building_plot_scene:
print("WARNING: Could not load building plot scene")
# Create an empty PackedScene as a last resort
building_plot_scene = PackedScene.new()
# Load the final building scene
final_building_scene = load("res://models/building-small-a.glb")
if not final_building_scene:
print("WARNING: Could not load final building scene")
# Create an empty PackedScene as a last resort
final_building_scene = PackedScene.new()
# Call this method to start construction at a position
func start_construction(position: Vector3, structure_index: int, rotation_basis = null):
if position in construction_sites:
print("Construction already in progress at ", position)
return
print("Starting new construction at ", position)
# Get the current selector rotation if available
var rotation_index = 0
if builder and builder.selector:
@ -62,7 +54,6 @@ func start_construction(position: Vector3, structure_index: int, rotation_basis
if builder.gridmap:
rotation_index = builder.gridmap.get_orthogonal_index_from_basis(rotation_basis)
print("Using rotation index: ", rotation_index)
# Create a construction site entry
construction_sites[position] = {
@ -129,7 +120,6 @@ func _spawn_worker_for_construction(target_position: Vector3):
var road_position = _find_nearest_road(target_position)
if road_position == Vector3.ZERO:
print("No roads found to spawn worker!")
return
# Create the worker
@ -171,7 +161,6 @@ func _create_worker(spawn_position: Vector3, target_position: Vector3):
# Load the worker script
var worker_script = load("res://scripts/mission/construction_worker.gd")
if not worker_script:
print("ERROR: Could not load construction worker script!")
return null
# Create the worker node with the script
@ -225,7 +214,6 @@ func _create_worker(spawn_position: Vector3, target_position: Vector3):
# Signal handlers for worker construction sounds
# Now needed ONLY for mission-triggering logic, not for sound
func _on_worker_construction_started():
print("CONSTRUCTION MANAGER: Received construction_started signal from worker")
# Forward the signal for mission managers/other systems that need it
# Workers now handle their own sounds independently
worker_construction_started.emit()
@ -235,7 +223,6 @@ func _on_worker_construction_started():
func _on_worker_construction_ended():
# Forward the signal for mission managers/other systems that need it
worker_construction_ended.emit()
print("Construction manager: forwarding construction_ended signal")
# Complete construction at a position
func _complete_construction(position: Vector3):
@ -275,7 +262,6 @@ func _complete_construction(position: Vector3):
# (let the mission manager handle that case to avoid double spawning)
if mission_manager and mission_manager.current_mission and mission_manager.current_mission.id == "1" and !mission_manager.character_spawned:
should_spawn_resident = false
print("Skip spawning resident for first building in mission 1")
# Spawn a resident from the new building (except for first building in mission 1)
if should_spawn_resident:
@ -287,61 +273,43 @@ func _complete_construction(position: Vector3):
# If not found, try to find it by group (we added the HUD to "hud" group)
if not hud:
print("Trying to find HUD by group...")
var hud_nodes = get_tree().get_nodes_in_group("hud")
if hud_nodes.size() > 0:
hud = hud_nodes[0]
print("Found HUD via group: " + hud.name)
# If not found, try other common paths
if not hud:
print("Trying alternative paths for HUD...")
var scene_root = get_tree().get_root()
for child in scene_root.get_children():
if child.name == "Main":
if child.has_node("CanvasLayer/HUD"):
hud = child.get_node("CanvasLayer/HUD")
print("Found HUD at Main/CanvasLayer/HUD")
break
# Last resort - try to find using builder's cash_display
if not hud and builder and builder.cash_display:
print("Trying to find HUD via cash_display...")
var parent = builder.cash_display.get_parent()
while parent and parent.get_parent():
if "HUD" in parent.name:
hud = parent
print("Found HUD via cash_display parent: " + parent.name)
break
parent = parent.get_parent()
print("HUD node found: " + str(hud != null))
if hud and site["structure_index"] >= 0 and site["structure_index"] < builder.structures.size():
var structure = builder.structures[site["structure_index"]]
print("Structure type: " + str(structure.type) + ", Is residential: " + str(structure.type == Structure.StructureType.RESIDENTIAL_BUILDING))
print("Population count: " + str(structure.population_count))
if structure.type == Structure.StructureType.RESIDENTIAL_BUILDING and structure.population_count > 0:
print("Adding population to HUD")
hud.total_population += structure.population_count
hud.update_hud()
hud.population_updated.emit(hud.total_population)
print("Added " + str(structure.population_count) + " population after construction completed")
else:
print("Building completed but has no population: " + str(structure.type))
# Emit completion signal
construction_completed.emit(position)
print("Construction completed at ", position)
# Function to handle building demolition at a position
func handle_demolition(position: Vector3):
# Check if this position has a construction site entry
if position in construction_sites:
print("Removing construction site at demolished position: ", position)
# Clean up any resources
var site = construction_sites[position]
@ -355,9 +323,6 @@ func handle_demolition(position: Vector3):
# Remove the entry from the dictionary
construction_sites.erase(position)
print("Construction site entry removed for demolished building")
else:
print("No construction site found at demolished position: ", position)
# Function to update mission objective when construction is complete
func _update_mission_objective_on_completion(structure_index: int):
@ -370,14 +335,12 @@ func _update_mission_objective_on_completion(structure_index: int):
var structure = builder.structures[structure_index]
if structure.type == Structure.StructureType.RESIDENTIAL_BUILDING:
print("Updating mission objective for completed residential building")
# For mission 3, we'll rely on the fix_mission.gd script to maintain an accurate count
# This prevents double counting, as we just need to make sure buildings are counted
# based on their actual presence in the scene
if mission_manager.current_mission.id == "3":
# Instead of directly updating, we'll wait for the fix script to apply the proper count
print("Mission 3 residential building completed, count will be updated by fix script")
pass
# Special handling for mission 1
elif mission_manager.current_mission.id == "1":
@ -389,7 +352,6 @@ func _update_mission_objective_on_completion(structure_index: int):
# Trigger an immediate progress check
mission_manager.check_mission_progress(mission_manager.current_mission.id)
print("Mission 1 progress check triggered after construction completed")
# Place the final building at the construction site
func _place_final_building(position: Vector3, structure_index: int):
@ -406,7 +368,6 @@ func _place_final_building(position: Vector3, structure_index: int):
var site = construction_sites[position]
if "rotation_basis" in site and site["rotation_basis"]:
building.basis = site["rotation_basis"]
print("Applied saved rotation to building")
building.scale = Vector3(3.0, 3.0, 3.0) # Scale to match other buildings
@ -451,15 +412,11 @@ func _make_model_transparent(model: Node3D, alpha: float):
# Spawn a resident from a newly constructed building
func _spawn_resident_from_building(position: Vector3):
print("Spawning resident from building at ", position)
# Make sure we have a valid nav_region reference
if not nav_region and builder and builder.nav_region:
nav_region = builder.nav_region
print("Updated nav_region reference from builder")
if not nav_region:
print("ERROR: No navigation region available for spawning resident!")
return
# Find a road to spawn near
@ -470,7 +427,6 @@ func _spawn_resident_from_building(position: Vector3):
# Use the pre-made character pathing scene (the same one that works in mission 1)
var character_scene = load("res://scenes/character_pathing.tscn")
if not character_scene:
print("ERROR: Could not load character_pathing.tscn scene!")
return
var resident = character_scene.instantiate()
@ -519,14 +475,11 @@ func _spawn_resident_from_building(position: Vector3):
target_position = position + Vector3(randf_range(-5, 5), 0, randf_range(-5, 5))
resident.set_movement_target(target_position)
print("Initial movement target set for resident")
if resident.has_method("_start_initial_movement"):
# Call deferred to ensure the navigation system is ready
resident.call_deferred("_start_initial_movement")
)
print("NavigationNPC resident spawned successfully as child of NavRegion3D")
# Find a random road to use as a target
func _find_random_road() -> Vector3:
@ -579,8 +532,6 @@ func initialize(resident_model: Node3D, anim_player: AnimationPlayer, navigation
# Start patrolling after a short delay
wait_timer = 2.0 # Wait 2 seconds before starting
print("Resident initialized at ", global_position)
func _physics_process(delta: float):
if is_moving:
@ -592,8 +543,6 @@ func _physics_process(delta: float):
# Play idle animation
if animation_player and animation_player.has_animation("idle"):
animation_player.play("idle")
print("Resident reached destination, waiting...")
else:
# Continue moving
move_along_path(delta)
@ -633,8 +582,6 @@ func set_movement_target(target: Vector3):
# Play walking animation
if animation_player and animation_player.has_animation("walk"):
animation_player.play("walk")
print("Resident moving to ", target)
func find_new_destination():
# Find a road to walk to
@ -649,7 +596,6 @@ func find_new_destination():
else:
# If no road found, try again later
wait_timer = 0.0
print("No road found for resident to walk to")
# Find a random road to walk to
func _find_random_road() -> Vector3:
@ -668,7 +614,6 @@ func _find_random_road() -> Vector3:
roads.append(road_pos)
else:
# If we can't find roads from our parent, try going back home
print("Resident couldn't find parent navigation region")
return home_position
# Pick a random road
@ -678,15 +623,12 @@ func _find_random_road() -> Vector3:
# Fallback to home position if no roads found
return home_position
"""
# Create the file with the script content
var file = FileAccess.open("res://scripts/mission/resident_character.gd", FileAccess.WRITE)
if file:
file.store_string(script_content)
file.close()
print("Created resident character script")
else:
print("Failed to create resident character script file")
# Helper to find all MeshInstance3D nodes
func _find_all_mesh_instances(node: Node, result: Array):
@ -694,4 +636,4 @@ func _find_all_mesh_instances(node: Node, result: Array):
result.append(node)
for child in node.get_children():
_find_all_mesh_instances(child, result)
_find_all_mesh_instances(child, result)

@ -4,8 +4,10 @@ signal completed
signal panel_opened
signal panel_closed
# Only store user_input and submit_button variables for signal connections
var user_input
# Store variables for signal connections
var user_input # For single input (backward compatibility)
var user_inputs = [] # Array for multiple inputs
var input_labels = [] # Array for input labels
var submit_button
var mission: MissionData
@ -26,6 +28,10 @@ func _ready():
user_input = get_node_or_null("PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/UserInputContainer/UserInput")
submit_button = get_node_or_null("PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/SubmitButtonContainer/SubmitButton")
# Clear the user inputs array
user_inputs = []
input_labels = []
# Connect button signals if the button exists
if submit_button != null:
if not submit_button.is_connected("pressed", Callable(self, "_on_submit_button_pressed")):
@ -54,9 +60,13 @@ func show_learning_panel(mission_data: MissionData):
# Default answer based on mission type
correct_answer = "1" if not mission.power_math_content.is_empty() else "A"
# Set up user input placeholder
if user_input:
user_input.placeholder_text = mission.question_text if not mission.question_text.is_empty() else "Enter your answer"
# Set up user input fields based on mission data
if mission.num_of_user_inputs > 1:
_setup_multiple_user_inputs()
else:
# Traditional single input
if user_input:
user_input.placeholder_text = mission.question_text if not mission.question_text.is_empty() else "Enter your answer"
# Hide the HUD when learning panel is shown
var hud = get_node_or_null("/root/Main/CanvasLayer/HUD")
@ -103,23 +113,122 @@ func _disable_background_interaction():
print("Background interaction disabled")
# Function to create multiple user input fields
func _setup_multiple_user_inputs():
# Clear any existing user inputs
user_inputs = []
input_labels = []
# Get the container where inputs should be added
var user_input_container = get_node_or_null("PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/UserInputContainer")
if not user_input_container:
push_error("User input container not found")
return
# If there's an existing single input, hide it
if user_input and user_input.get_parent() == user_input_container:
user_input.visible = false
# Create a centering container for better alignment
var center_container = CenterContainer.new()
center_container.name = "InputCenterContainer"
center_container.size_flags_horizontal = Control.SIZE_FILL
user_input_container.add_child(center_container)
# Add margin around the grid
var margin_container = MarginContainer.new()
margin_container.name = "InputMarginContainer"
margin_container.add_theme_constant_override("margin_top", 10)
margin_container.add_theme_constant_override("margin_bottom", 10)
center_container.add_child(margin_container)
# Create a grid container for inputs
var grid = GridContainer.new()
grid.name = "MultiInputGrid"
grid.columns = 2 # Label and input in each row
grid.size_flags_horizontal = Control.SIZE_FILL
grid.add_theme_constant_override("h_separation", 15) # Add horizontal spacing between columns
grid.add_theme_constant_override("v_separation", 10) # Add vertical spacing between rows
margin_container.add_child(grid)
# Create each input field
for i in range(mission.num_of_user_inputs):
# Create label
var label = Label.new()
label.name = "InputLabel" + str(i)
label.text = mission.input_labels[i] if i < mission.input_labels.size() else "Input " + str(i+1) + ":"
label.size_flags_horizontal = Control.SIZE_EXPAND
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT # Right-align text
# Set larger font size
var font_size = 26
label.add_theme_font_size_override("font_size", font_size)
# Add right margin for better spacing
var style = StyleBoxEmpty.new()
style.content_margin_right = 10 # Add 10 pixels of right margin
label.add_theme_stylebox_override("normal", style)
grid.add_child(label)
input_labels.append(label)
# Create input field
var input_field = LineEdit.new()
input_field.name = "UserInput" + str(i)
input_field.size_flags_horizontal = Control.SIZE_EXPAND_FILL
input_field.placeholder_text = "Enter value"
input_field.custom_minimum_size.x = 150 # Increase minimum width
input_field.custom_minimum_size.y = 40 # Set height for the input field
# Style the input field
input_field.alignment = HORIZONTAL_ALIGNMENT_LEFT # Left-align text inside the field
input_field.add_theme_font_size_override("font_size", 26) # Match label font size
# Connect text submitted signal
input_field.text_submitted.connect(_on_user_input_text_submitted)
grid.add_child(input_field)
user_inputs.append(input_field)
# Add spacing after the grid
var spacer = Control.new()
spacer.name = "InputSpacer"
spacer.custom_minimum_size.y = 20
user_input_container.add_child(spacer)
# Reset the panel to a clean state
func _reset_panel():
# Reset answer state
is_answer_correct = false
# Clear text inputs
# Clear single input if it exists
if user_input:
user_input.text = ""
# Clear multiple inputs if they exist
for input in user_inputs:
if input:
input.text = ""
# Hide feedback label
var feedback_label = get_node_or_null("PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/UserInputContainer/FeedbackLabel")
if feedback_label:
feedback_label.visible = false
# Clean up any TopMargin that might have been added
# Clean up any added UI elements
var user_input_container = get_node_or_null("PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/UserInputContainer")
if user_input_container:
# Clean up the input center container and all its children
var input_center_container = user_input_container.get_node_or_null("InputCenterContainer")
if input_center_container:
input_center_container.queue_free()
# Clean up input spacer if it exists
var input_spacer = user_input_container.get_node_or_null("InputSpacer")
if input_spacer:
input_spacer.queue_free()
# Clean up any TopMargin that might have been added
var top_margin = user_input_container.get_node_or_null("TopMargin")
if top_margin:
top_margin.queue_free()
@ -127,6 +236,14 @@ func _reset_panel():
# Reset custom sizing
user_input_container.custom_minimum_size.y = 0
user_input_container.size_flags_vertical = Control.SIZE_FILL
# Show the default input field if it exists
if user_input and user_input.get_parent() == user_input_container:
user_input.visible = true
# Clear the user inputs arrays
user_inputs = []
input_labels = []
# Reset submit button
if submit_button:
@ -185,7 +302,7 @@ func _setup_mission_specific_content():
func _clear_existing_content():
# Find the main containers
var graph_center_container = get_node_or_null("PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/GraphContainer/GraphCenterContainer")
var company_data_container = get_node_or_null("PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/GraphContainer/CompanyDataContainer")
var question_container = get_node_or_null("PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/GraphContainer/CompanyDataContainer")
# Clear power math content from the graph container
if graph_center_container:
@ -193,12 +310,12 @@ func _clear_existing_content():
if power_math_label:
power_math_label.queue_free()
# Reset company data container
if company_data_container:
company_data_container.visible = false
var company_data_label = company_data_container.get_node_or_null("CompanyDataLabel")
if company_data_label:
company_data_label.text = ""
# Reset the container that will hold our question text (previously used for company data)
if question_container:
question_container.visible = false
var text_label = question_container.get_node_or_null("CompanyDataLabel")
if text_label:
text_label.text = ""
# Set up construction company mission content
func _setup_construction_mission():
@ -251,85 +368,26 @@ func _setup_construction_mission():
graph_image.visible = false
print("Failed to load graph image")
# 2. Set company data
# 2. Set question text instead of company data
var company_data_container = get_node_or_null("PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/GraphContainer/CompanyDataContainer")
if company_data_container:
company_data_container.visible = true
# Check if we need to convert the company data to a horizontal layout
# Get the label where we'll display the question text
var company_data_label = company_data_container.get_node_or_null("CompanyDataLabel")
if company_data_label:
if mission.company_data.is_empty():
company_data_label.text = "[center][color=#e06666][font_size=18]No company data available.[/font_size][/color][/center]"
else:
# Split the original data to create a horizontal layout
var data_text = mission.company_data
# Check if we need to reformat to save vertical space
if graph_image and graph_image.visible and graph_image.custom_minimum_size.y > 400:
print("Graph is large, reformatting company data to horizontal layout")
# Parse and reformat the company data to be more compact
# This assumes the data has a typical format with company names and bullet points
var lines = data_text.split("\n")
var company_a_name = ""
var company_a_data = []
var company_b_name = ""
var company_b_data = []
var current_company = -1 # 0 for A, 1 for B
# Parse the data by line
for line in lines:
line = line.strip_edges()
if line == "" or line.length() == 0:
continue
if "[color=#60c2a8]" in line or "Company A:" in line:
# Found Company A header
company_a_name = line
current_company = 0
elif "[color=#e06666]" in line or "Company B:" in line:
# Found Company B header
company_b_name = line
current_company = 1
elif line.begins_with("") or line.begins_with("-") or line.begins_with("*"):
# This is a data point
if current_company == 0:
company_a_data.append(line)
elif current_company == 1:
company_b_data.append(line)
elif "Enter A or B" in line or "If you need" in line:
# This is the question part - add to both
company_a_data.append(line)
company_b_data.append("")
elif "Hint:" in line:
# This is the hint - add to both
company_a_data.append(line)
company_b_data.append("")
# Create a horizontal layout with two columns
var formatted_text = "[center]\n"
# Add Company A
formatted_text += "[color=#ce5371][b]" + (company_a_name.replace("[b]", "").replace("[/b]", "").replace("[color=#60c2a8]", "").replace("[/color]", "")) + "[/b][/color]\n"
for point in company_a_data:
formatted_text += point + "\n"
formatted_text += "\n"
# Add Company B
formatted_text += "[color=#3182c0][b]" + (company_b_name.replace("[b]", "").replace("[/b]", "").replace("[color=#e06666]", "").replace("[/color]", "")) + "[/b][/color]\n"
for point in company_b_data:
formatted_text += point + "\n"
formatted_text += "[/center]"
# Set the formatted text
company_data_label.text = formatted_text
company_data_label.custom_minimum_size.y = 140 # Reduce height for horizontal layout
else:
# Use original data format
company_data_label.text = data_text
# Create a formatted question text
var formatted_text = "[center]\n"
# Add the question text in a centered, clear format
if not mission.question_text.is_empty():
formatted_text += "[color=#dddddd][font_size=26]" + mission.question_text + "[/font_size][/color]"
formatted_text += "\n[/center]"
# Set the formatted text
company_data_label.text = formatted_text
company_data_label.custom_minimum_size.y = 80 # Reduce height for just question text
# Set up power math mission content
@ -449,12 +507,25 @@ func _check_answer():
push_error("Mission is null in _check_answer")
return
# Make sure we have a user input field
if not user_input:
push_error("Cannot check answer: user_input is null")
var user_answer = ""
# Handle multiple inputs if present
if mission.num_of_user_inputs > 1 and not user_inputs.is_empty():
var answers = []
for input in user_inputs:
if input:
answers.append(input.text.strip_edges())
user_answer = ",".join(answers)
# Fall back to single input
elif user_input:
user_answer = user_input.text.strip_edges()
else:
push_error("Cannot check answer: no user input fields available")
return
var user_answer = user_input.text.strip_edges().to_upper() # Convert to uppercase for case-insensitive comparison
# Convert to uppercase for case-insensitive comparison when appropriate
if not "," in correct_answer: # Don't uppercase comma-separated values
user_answer = user_answer.to_upper()
# Get the feedback label
var feedback_label = get_node_or_null("PanelContainer/MarginContainer/ScrollContainer/VBoxContainer/MainContent/UserInputContainer/FeedbackLabel")
@ -506,4 +577,4 @@ func _on_complete_mission():
hide_learning_panel()
# Emit signal
completed.emit()
completed.emit()

@ -16,3 +16,5 @@ class_name MissionData
@export var incorrect_feedback: String = "" # Feedback text shown when answer is incorrect
@export var company_data: String = "" # Company data for mission 2
@export var power_math_content: String = "" # Power math content for mission 4
@export var num_of_user_inputs: int = 1 # Number of user input fields to display
@export var input_labels: Array[String] = [] # Labels for each input field

@ -6,6 +6,10 @@ signal music_muted_changed(is_muted)
signal sfx_muted_changed(is_muted)
signal audio_ready # Signal emitted when audio is initialized (important for web)
# Sound bridges for web builds
var react_sound_bridge = null # Will be instantiated from a script
var audio_bridge = null # Will be instantiated from a script
# Volume ranges from 0.0 to 1.0
var music_volume: float = 0.8
var sfx_volume: float = 0.8
@ -24,30 +28,83 @@ const SFX_BUS_NAME = "SFX"
var audio_initialized: bool = false
# Sound files dictionary - mapping simplified names to file paths
const SOUND_FILES = {
"jazzNewOrleans": "res://sounds/jazz_new_orleans.mp3",
"lofiChillJazz": "res://sounds/lofi-chill-jazz-272869.mp3",
"buildingPlacing": "res://sounds/building_placing.wav",
"construction": "res://sounds/construction.wav",
"powerDrill": "res://sounds/power_drill.mp3"
}
# Current music track
var current_music: String = ""
# Currently playing audio streams (for direct Godot playback)
var music_player: AudioStreamPlayer = null
var sfx_players: Dictionary = {}
func _ready():
print("DEBUG: SoundManager initializing...")
# Create music player for all platforms
music_player = AudioStreamPlayer.new()
add_child(music_player)
# Setup audio buses (this doesn't actually start any audio playback)
_setup_audio_buses()
# For web builds, we need to detect user interaction to initialize audio
# For web builds, we'll use Audio Bridge
if OS.has_feature("web"):
print("Web build detected: Waiting for user interaction to initialize audio")
# Set a flag to track initialization
audio_initialized = false
# Connect to the input events to detect user interaction
# We'll use both mouse and keyboard events to be thorough
print("Web build detected, Audio bridges will be used")
# Connect to the input events to detect user interaction as fallback
get_viewport().connect("gui_focus_changed", _on_user_interaction)
# Use the _input method instead of trying to connect to signals
# This approach works better for capturing all input events in web builds
print("Using _input method for audio initialization")
# Try to use a custom Node for audio bridge functionality
# Instead of relying on class_name registration or preload
audio_bridge = Node.new()
audio_bridge.name = "AudioBridge"
add_child(audio_bridge)
# Set up the necessary properties
audio_bridge.set_script(load("res://scripts/audio_bridge.gd"))
# Connect to the signal after the script is loaded
if audio_bridge.has_signal("bridge_connected"):
audio_bridge.bridge_connected.connect(_on_audio_bridge_connected)
# We also create the ReactSoundBridge for backward compatibility
# But using the same approach as AudioBridge to avoid class_name dependency
react_sound_bridge = Node.new()
react_sound_bridge.name = "ReactSoundBridge"
add_child(react_sound_bridge)
# Set up the necessary properties
react_sound_bridge.set_script(load("res://scripts/react_sound_bridge.gd"))
# Connect to the signal after the script is loaded
if react_sound_bridge.has_signal("audio_ready"):
react_sound_bridge.audio_ready.connect(_on_react_audio_ready)
# Signal that we're using web audio (don't create audio buses)
await get_tree().process_frame
audio_initialized = true
audio_ready.emit()
# Store a reference to this object in the main loop for JavaScript callbacks
Engine.get_main_loop().set_meta("sound_manager", self)
else:
# For non-web platforms, set up standard Godot audio
# Set up the audio buses
_setup_audio_buses()
# Set the music player bus
music_player.bus = MUSIC_BUS_NAME
# For non-web platforms, we can initialize immediately
audio_initialized = true
print("Non-web build: Audio initialized immediately")
# Emit the audio_ready signal
audio_ready.emit()
# Setup audio buses (doesn't start audio playback)
func _setup_audio_buses():
@ -55,8 +112,6 @@ func _setup_audio_buses():
music_bus_index = AudioServer.get_bus_index(MUSIC_BUS_NAME)
sfx_bus_index = AudioServer.get_bus_index(SFX_BUS_NAME)
print("DEBUG: Initial bus indices - Music: ", music_bus_index, ", SFX: ", sfx_bus_index)
# If the buses don't exist yet, create them
if music_bus_index == -1:
# Create music bus
@ -64,7 +119,6 @@ func _setup_audio_buses():
AudioServer.add_bus()
AudioServer.set_bus_name(music_bus_index, MUSIC_BUS_NAME)
AudioServer.set_bus_send(music_bus_index, "Master")
print("DEBUG: Created Music bus at index ", music_bus_index)
if sfx_bus_index == -1:
# Create SFX bus
@ -72,12 +126,10 @@ func _setup_audio_buses():
AudioServer.add_bus()
AudioServer.set_bus_name(sfx_bus_index, SFX_BUS_NAME)
AudioServer.set_bus_send(sfx_bus_index, "Master")
print("DEBUG: Created SFX bus at index ", sfx_bus_index)
# Verify buses were created correctly
music_bus_index = AudioServer.get_bus_index(MUSIC_BUS_NAME)
sfx_bus_index = AudioServer.get_bus_index(SFX_BUS_NAME)
print("DEBUG: Final bus indices - Music: ", music_bus_index, ", SFX: ", sfx_bus_index)
# Apply initial settings
_apply_music_volume()
@ -90,6 +142,61 @@ func _setup_audio_buses():
if sfx_bus_index != -1:
AudioServer.set_bus_mute(sfx_bus_index, false)
# Process sound state received from JavaScript
func process_js_audio_state(state: Dictionary):
# Update local state based on received data
if state.has("musicVolume"):
music_volume = state.musicVolume
if state.has("sfxVolume"):
sfx_volume = state.sfxVolume
if state.has("musicMuted"):
music_muted = state.musicMuted
if state.has("sfxMuted"):
sfx_muted = state.sfxMuted
if state.has("currentMusic"):
current_music = state.currentMusic
# Emit signals about changes
music_volume_changed.emit(music_volume)
sfx_volume_changed.emit(sfx_volume)
music_muted_changed.emit(music_muted)
sfx_muted_changed.emit(sfx_muted)
# Called when ReactSoundBridge reports it's ready
func _on_react_audio_ready():
print("ReactSoundBridge reports ready")
audio_initialized = true
# Update local state from React
if react_sound_bridge != null:
if react_sound_bridge.get("music_volume") != null:
music_volume = react_sound_bridge.music_volume
if react_sound_bridge.get("sfx_volume") != null:
sfx_volume = react_sound_bridge.sfx_volume
if react_sound_bridge.get("music_muted") != null:
music_muted = react_sound_bridge.music_muted
if react_sound_bridge.get("sfx_muted") != null:
sfx_muted = react_sound_bridge.sfx_muted
if react_sound_bridge.get("current_music") != null:
current_music = react_sound_bridge.current_music
# Emit the audio ready signal
audio_ready.emit()
# Called when AudioBridge connects to the platform-one sound manager
func _on_audio_bridge_connected(is_connected: bool):
print("AudioBridge connected: ", is_connected)
if is_connected:
audio_initialized = true
# Request the sound state from the platform-one sound manager
if audio_bridge.has_method("get_sound_state"):
audio_bridge.get_sound_state()
# Emit the audio ready signal
audio_ready.emit()
# Called when any user interaction happens in web builds
func _on_user_interaction(_arg=null):
if OS.has_feature("web") and not audio_initialized:
@ -106,7 +213,6 @@ func _input(event):
# initialize audio properly in web builds
func init_web_audio_from_js():
if OS.has_feature("web") and not audio_initialized:
print("Audio initialization requested from JS")
_initialize_web_audio()
# Initialize audio for web builds
@ -114,71 +220,243 @@ func _initialize_web_audio():
if audio_initialized:
return
print("User interaction detected: Initializing web audio...")
# For web builds, we notify JavaScript to initialize audio
if OS.has_feature("web"):
JSBridge.JavaScriptGlobal.handle_audio_action("INITIALIZE_AUDIO")
# We don't need to create any dummy players, as JavaScript will handle the audio
audio_initialized = true
audio_ready.emit()
return
# Resume the AudioServer context
AudioServer.set_bus_mute(0, false) # Unmute master bus
# For non-web platforms, initialize Godot audio (this shouldn't get called)
if not OS.has_feature("web"):
# Set the flag to prevent multiple initializations
audio_initialized = true
audio_ready.emit()
# Play background music
func play_music(sound_name: String, loop: bool = true):
if not audio_initialized:
return
# Store the current music name
current_music = sound_name
# Play and immediately stop a silent sound to initialize the audio context
var silent_player = AudioStreamPlayer.new()
add_child(silent_player)
# For web builds, try multiple bridge options
if OS.has_feature("web"):
# Try AudioBridge first (platform-one integration)
if audio_bridge != null and audio_bridge.get("is_connected") == true:
print("Using AudioBridge to play music: ", sound_name)
if audio_bridge.has_method("play_music") and audio_bridge.play_music(sound_name):
return
# Fall back to JavaScript Bridge
print("Using JavaScriptBridge to play music: ", sound_name)
JSBridge.JavaScriptGlobal.handle_audio_action("PLAY_MUSIC", sound_name)
return
# Create a very short silent audio stream
var silent_stream = AudioStreamWAV.new()
silent_stream.format = AudioStreamWAV.FORMAT_16_BITS
silent_stream.stereo = true
silent_stream.data = PackedByteArray([0, 0, 0, 0]) # Minimal silent data
# For native builds, use Godot audio
if not SOUND_FILES.has(sound_name):
return
# Get the file path
var file_path = SOUND_FILES[sound_name]
# Play and immediately stop to kickstart audio
silent_player.stream = silent_stream
silent_player.volume_db = -80.0
silent_player.play()
# Load the audio stream
var stream = load(file_path)
if stream == null:
return
# Wait a moment before stopping (important for some browsers)
await get_tree().create_timer(0.1).timeout
silent_player.stop()
# Stop current music if playing
if music_player.playing:
music_player.stop()
# Clean up
await get_tree().process_frame
silent_player.queue_free()
# Set up and play the music
music_player.stream = stream
if music_muted:
music_player.volume_db = linear_to_db(0)
else:
music_player.volume_db = linear_to_db(music_volume)
music_player.bus = MUSIC_BUS_NAME
# Set the flag to prevent multiple initializations
audio_initialized = true
print("Web audio initialized successfully")
# Set looping if supported by the stream
if stream is AudioStreamMP3 or stream is AudioStreamOggVorbis:
stream.loop = loop
# Notify any waiting game systems that audio is now available
audio_ready.emit()
music_player.play()
# Play a sound effect
func play_sfx(sound_name: String):
if not audio_initialized:
return
# For web builds, try multiple bridge options
if OS.has_feature("web"):
# Try AudioBridge first (platform-one integration)
if audio_bridge != null and audio_bridge.get("is_connected") == true:
print("Using AudioBridge to play sfx: ", sound_name)
if audio_bridge.has_method("play_sfx") and audio_bridge.play_sfx(sound_name):
return
# Fall back to JavaScript Bridge
print("Using JavaScriptBridge to play sfx: ", sound_name)
JSBridge.JavaScriptGlobal.handle_audio_action("PLAY_SFX", sound_name)
return
# For native builds, use Godot audio
if not SOUND_FILES.has(sound_name):
return
# Get the file path
var file_path = SOUND_FILES[sound_name]
# Load the audio stream
var stream = load(file_path)
if stream == null:
return
# Create or reuse a player for this sound
var player: AudioStreamPlayer
if not sfx_players.has(sound_name):
player = AudioStreamPlayer.new()
add_child(player)
sfx_players[sound_name] = player
else:
player = sfx_players[sound_name]
if player.playing:
player.stop()
# Set up and play the sound
player.stream = stream
if sfx_muted:
player.volume_db = linear_to_db(0)
else:
player.volume_db = linear_to_db(sfx_volume)
player.bus = SFX_BUS_NAME
player.play()
# Stop background music
func stop_music():
if not audio_initialized:
return
# For web builds, try multiple bridge options
if OS.has_feature("web"):
# Try AudioBridge first (platform-one integration)
if audio_bridge != null and audio_bridge.get("is_connected") == true:
print("Using AudioBridge to stop music")
if audio_bridge.has_method("stop_music") and audio_bridge.stop_music():
current_music = ""
return
# Fall back to JavaScript Bridge
print("Using JavaScriptBridge to stop music")
JSBridge.JavaScriptGlobal.handle_audio_action("STOP_MUSIC")
current_music = ""
return
# For native builds, use Godot audio
if music_player and music_player.playing:
music_player.stop()
current_music = ""
# Set music volume (0.0 to 1.0)
func set_music_volume(volume: float):
music_volume = clampf(volume, 0.0, 1.0)
_apply_music_volume()
# For web builds, try multiple bridge options
if OS.has_feature("web"):
# Try AudioBridge first (platform-one integration)
if audio_bridge != null and audio_bridge.get("is_connected") == true:
print("Using AudioBridge to set music volume: ", music_volume)
if audio_bridge.has_method("set_music_volume"):
audio_bridge.set_music_volume(music_volume)
else:
# Fall back to JavaScript Bridge
print("Using JavaScriptBridge to set music volume: ", music_volume)
JSBridge.JavaScriptGlobal.handle_audio_action("SET_MUSIC_VOLUME", "", music_volume)
else:
# Apply to local Godot audio system
_apply_music_volume()
# Emit signal
music_volume_changed.emit(music_volume)
print("Music volume set to: ", music_volume)
# Set SFX volume (0.0 to 1.0)
func set_sfx_volume(volume: float):
sfx_volume = clampf(volume, 0.0, 1.0)
_apply_sfx_volume()
# For web builds, try multiple bridge options
if OS.has_feature("web"):
# Try AudioBridge first (platform-one integration)
if audio_bridge != null and audio_bridge.get("is_connected") == true:
print("Using AudioBridge to set sfx volume: ", sfx_volume)
if audio_bridge.has_method("set_sfx_volume"):
audio_bridge.set_sfx_volume(sfx_volume)
else:
# Fall back to JavaScript Bridge
print("Using JavaScriptBridge to set sfx volume: ", sfx_volume)
JSBridge.JavaScriptGlobal.handle_audio_action("SET_SFX_VOLUME", "", sfx_volume)
else:
# Apply to local Godot audio system
_apply_sfx_volume()
# Emit signal
sfx_volume_changed.emit(sfx_volume)
print("SFX volume set to: ", sfx_volume)
# Toggle music mute state
func toggle_music_mute():
music_muted = !music_muted
_apply_music_volume()
# For web builds, try multiple bridge options
if OS.has_feature("web"):
# Try AudioBridge first (platform-one integration)
if audio_bridge != null and audio_bridge.get("is_connected") == true:
print("Using AudioBridge to toggle music mute: ", music_muted)
if audio_bridge.has_method("toggle_music_mute"):
audio_bridge.toggle_music_mute()
else:
# Fall back to JavaScript Bridge
print("Using JavaScriptBridge to toggle music mute: ", music_muted)
JSBridge.JavaScriptGlobal.handle_audio_action("TOGGLE_MUSIC_MUTE")
else:
# Apply to local Godot audio system
_apply_music_volume()
# Emit signal
music_muted_changed.emit(music_muted)
print("Music mute toggled to: ", music_muted)
# Toggle SFX mute state
func toggle_sfx_mute():
sfx_muted = !sfx_muted
_apply_sfx_volume()
# For web builds, try multiple bridge options
if OS.has_feature("web"):
# Try AudioBridge first (platform-one integration)
if audio_bridge != null and audio_bridge.get("is_connected") == true:
print("Using AudioBridge to toggle sfx mute: ", sfx_muted)
if audio_bridge.has_method("toggle_sfx_mute"):
audio_bridge.toggle_sfx_mute()
else:
# Fall back to JavaScript Bridge
print("Using JavaScriptBridge to toggle sfx mute: ", sfx_muted)
JSBridge.JavaScriptGlobal.handle_audio_action("TOGGLE_SFX_MUTE")
else:
# Apply to local Godot audio system
_apply_sfx_volume()
# Emit signal
sfx_muted_changed.emit(sfx_muted)
print("SFX mute toggled to: ", sfx_muted)
# Apply music volume settings to the bus
# Apply music volume settings
func _apply_music_volume():
# Skip for web builds - JavaScript Bridge handles volume
if OS.has_feature("web"):
return
# For non-web builds, use the audio buses
if music_bus_index != -1:
if music_muted:
AudioServer.set_bus_mute(music_bus_index, true)
@ -187,9 +465,21 @@ func _apply_music_volume():
# Convert from linear to decibels (approximately -80dB to 0dB)
var db_value = linear_to_db(music_volume)
AudioServer.set_bus_volume_db(music_bus_index, db_value)
# Update music player volume if it exists
if music_player != null:
if music_muted:
music_player.volume_db = linear_to_db(0)
else:
music_player.volume_db = linear_to_db(music_volume)
# Apply SFX volume settings to the bus
# Apply SFX volume settings
func _apply_sfx_volume():
# Skip for web builds - JavaScript Bridge handles volume
if OS.has_feature("web"):
return
# For non-web builds, use the audio buses
if sfx_bus_index != -1:
if sfx_muted:
AudioServer.set_bus_mute(sfx_bus_index, true)
@ -198,6 +488,14 @@ func _apply_sfx_volume():
# Convert from linear to decibels
var db_value = linear_to_db(sfx_volume)
AudioServer.set_bus_volume_db(sfx_bus_index, db_value)
# Update all sfx player volumes
for player in sfx_players.values():
if player != null:
if sfx_muted:
player.volume_db = linear_to_db(0)
else:
player.volume_db = linear_to_db(sfx_volume)
# Helper function to convert linear volume to decibels with a more usable range
func linear_to_db(linear_value: float) -> float:

@ -4,15 +4,16 @@ importer="texture"
type="CompressedTexture2D"
uid="uid://cbk07cxgshg26"
path.s3tc="res://.godot/imported/selector.png-a5b2e7bc2bf34414a6d0f4a4e1472988.s3tc.ctex"
path.etc2="res://.godot/imported/selector.png-a5b2e7bc2bf34414a6d0f4a4e1472988.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://sprites/selector.png"
dest_files=["res://.godot/imported/selector.png-a5b2e7bc2bf34414a6d0f4a4e1472988.s3tc.ctex"]
dest_files=["res://.godot/imported/selector.png-a5b2e7bc2bf34414a6d0f4a4e1472988.s3tc.ctex", "res://.godot/imported/selector.png-a5b2e7bc2bf34414a6d0f4a4e1472988.etc2.ctex"]
[params]