From b97b2afce6042c0e70e7906b10bb052ae6eb95fa Mon Sep 17 00:00:00 2001 From: wadeaston Date: Wed, 26 Mar 2025 14:39:40 -0500 Subject: [PATCH] Added power plant mission --- scripts/builder.gd | 79 +++++++++++++++++++++++- scripts/mission/learning_panel.gd | 82 +++++++++++++++++++++---- scripts/mission/mission_manager.gd | 97 +++++++++++++++++++++++++++++- scripts/mission/mission_ui.gd | 42 +++++++++++++ 4 files changed, 284 insertions(+), 16 deletions(-) diff --git a/scripts/builder.gd b/scripts/builder.gd index c8ed0a6..894e810 100644 --- a/scripts/builder.gd +++ b/scripts/builder.gd @@ -55,7 +55,10 @@ func _ready(): # Apply appropriate scaling for buildings and roads var transform = Transform3D() - if structure.type == Structure.StructureType.RESIDENTIAL_BUILDING or structure.type == Structure.StructureType.ROAD: + if structure.model.resource_path.contains("power_plant"): + # Scale power plant model to be much smaller (0.5x) + transform = transform.scaled(Vector3(0.5, 0.5, 0.5)) + elif structure.type == Structure.StructureType.RESIDENTIAL_BUILDING or structure.type == Structure.StructureType.ROAD: # Scale buildings and roads to be consistent (3x) transform = transform.scaled(Vector3(3.0, 3.0, 3.0)) @@ -145,6 +148,8 @@ func action_build(gridmap_position): var is_road = structures[index].type == Structure.StructureType.ROAD # For residential buildings, we use the construction manager in mission 3 var is_residential = structures[index].type == Structure.StructureType.RESIDENTIAL_BUILDING + # For power plants, we handle them specially + var is_power_plant = structures[index].model.resource_path.contains("power_plant") # Check if we're in mission 3 (when we should use construction workers) var use_worker_construction = false @@ -171,6 +176,12 @@ func action_build(gridmap_position): # Make sure any existing NPCs are children of the navigation region _move_characters_to_navregion() + elif is_power_plant: + # Special handling for power plants - add directly as a child of the builder + _add_power_plant(gridmap_position, index) + + # We still set the cell item for collision detection + gridmap.set_cell_item(gridmap_position, index, gridmap.get_orthogonal_index_from_basis(selector.basis)) elif is_residential and use_worker_construction: # For residential buildings in mission 3, use construction workers # Pass the current selector basis to preserve rotation @@ -231,6 +242,13 @@ func action_demolish(gridmap_position): if nav_region and nav_region.has_node(road_name): is_road = true + # Check if there's a power plant at this position + var is_power_plant = false + var power_plant_name = "PowerPlant_" + str(int(gridmap_position.x)) + "_" + str(int(gridmap_position.z)) + + if has_node(power_plant_name): + is_power_plant = true + # Or check the GridMap for non-road structures var current_item = gridmap.get_cell_item(gridmap_position) var is_building = current_item >= 0 @@ -243,6 +261,11 @@ func action_demolish(gridmap_position): rebake_navigation_mesh() # Make sure any existing NPCs are children of the navigation region _move_characters_to_navregion() + elif is_power_plant: + # Remove the power plant model + _remove_power_plant(gridmap_position) + # Also remove from gridmap + gridmap.set_cell_item(gridmap_position, -1) elif is_building: # Remove the building from the gridmap gridmap.set_cell_item(gridmap_position, -1) @@ -284,7 +307,11 @@ func update_structure(): selector_container.add_child(_model) # Apply appropriate scaling based on structure type - if (structures[index].type == Structure.StructureType.RESIDENTIAL_BUILDING + if structures[index].model.resource_path.contains("power_plant"): + # Scale power plant model to be much smaller (0.5x) + _model.scale = Vector3(0.5, 0.5, 0.5) + _model.position.y += 0.0 # No need for Y adjustment with scaling + elif (structures[index].type == Structure.StructureType.RESIDENTIAL_BUILDING or structures[index].type == Structure.StructureType.ROAD or structures[index].type == Structure.StructureType.TERRAIN or structures[index].model.resource_path.contains("grass")): @@ -348,6 +375,54 @@ func _add_road_to_navregion(position: Vector3, structure_index: int): print("Added road model at position ", position, " to NavRegion3D") +# Function to add a power plant as a direct child of the builder +func _add_power_plant(position: Vector3, structure_index: int): + # Create a unique name for this power plant based on its position + var power_plant_name = "PowerPlant_" + str(int(position.x)) + "_" + str(int(position.z)) + + # Check if a power plant with this name already exists + if has_node(power_plant_name): + print("Power plant already exists at position: ", position) + return + + # Instantiate the power plant model + var power_plant_model = structures[structure_index].model.instantiate() + power_plant_model.name = power_plant_name + + # Add the power plant model directly to the builder (this node) + add_child(power_plant_model) + + # Create the transform + var transform = Transform3D() + + # Set scale (using the smaller 0.5x scale) + transform.basis = Basis().scaled(Vector3(0.5, 0.5, 0.5)) + + # Apply rotation from the selector to preserve the rotation the player chose + transform.basis = transform.basis * selector.basis + + # Set position + transform.origin = position + + # Apply the complete transform in one go + power_plant_model.transform = transform + + print("Added power plant at position ", position, " as direct child of builder") + +# Function to remove a power plant +func _remove_power_plant(position: Vector3): + # Get the power plant name based on its position + var power_plant_name = "PowerPlant_" + str(int(position.x)) + "_" + str(int(position.z)) + + # Check if a power plant with this name exists + if has_node(power_plant_name): + # Get the power plant and remove it + var power_plant = get_node(power_plant_name) + power_plant.queue_free() + print("Removed power plant at position ", position) + else: + print("No power plant found at position ", position) + # Function to remove a road model from the navigation region func _remove_road_from_navregion(position: Vector3): # Make sure we have a navigation region diff --git a/scripts/mission/learning_panel.gd b/scripts/mission/learning_panel.gd index 3ac7080..4ae1867 100644 --- a/scripts/mission/learning_panel.gd +++ b/scripts/mission/learning_panel.gd @@ -33,8 +33,51 @@ func show_learning_panel(mission_data: MissionData): mission = mission_data title_label.text = mission.title - # Set custom description for the learning panel - description_label.text = """ + # Set custom description based on mission ID + if mission.id == "4": + description_label.text = """ +POWERING YOUR CITY WITH MATH + +Your city has grown to 40 houses and now needs electricity! We'll use radicals and exponents to determine the power needs. + +UNDERSTANDING THE POWER FORMULA: +Power needed (kilowatts) = 2 × √n + n⁰·⁸ +where n is the number of houses in your city. + +CALCULATING THE POWER DEMAND: +Step 1: Calculate the square root part. +2 × √40 = 2 × 6.32 = 12.64 kilowatts + +Step 2: Calculate the exponent part. +To find 40⁰·⁸: +40⁰·⁸ = (2⁵·³²)⁰·⁸ = 2⁵·³²ˣ⁰·⁸ = 2⁴·²⁶ ≈ 19.14 kilowatts + +Step 3: Find the total power needed. +Total power needed = 12.64 + 19.14 = 31.78 kilowatts + +POWER PLANT INFORMATION: +• Each power plant generates 40 kilowatts of electricity +• A power plant can distribute electricity within a radius of: + Radius = 5 × √P = 5 × √40 = 5 × 6.32 ≈ 31.6 grid units + +How many power plants will you need to supply your city with electricity? +Enter your answer below. +""" + + # Update question label for mission 4 + $MarginContainer/VBoxContainer/AnswerContainer/QuestionLabel.text = "How many power plants does your city need based on the calculated demand?" + $MarginContainer/VBoxContainer/AnswerContainer/AnswerField.placeholder_text = "Enter number" + + # Set the correct answer + correct_answer = "1" + + # Hide graph container for mission 4 + $MarginContainer/VBoxContainer/GraphContainer.visible = false + $MarginContainer/VBoxContainer/GraphContainer.custom_minimum_size = Vector2(0, 0) + + else: + # Original mission 2 content + description_label.text = """ Your city is rapidly growing, and you need to build houses to accommodate new residents! Two different construction companies offer to help: Company A: City Builders Inc. @@ -55,15 +98,17 @@ Enter A or B below. Hint: Find the pattern for each company, then calculate how many workers would be needed for 40 houses. """ + # Show and size the graph container for mission 2 + $MarginContainer/VBoxContainer/GraphContainer.visible = true + $MarginContainer/VBoxContainer/GraphContainer.custom_minimum_size = Vector2(600, 400) -# Add this line right here - $MarginContainer/VBoxContainer/GraphContainer.custom_minimum_size = Vector2(600, 400) - - -# Update question label - $MarginContainer/VBoxContainer/AnswerContainer/QuestionLabel.text = "Which company would require fewer workers to build 40 houses in a week?" - $MarginContainer/VBoxContainer/AnswerContainer/AnswerField.placeholder_text = "Enter A or B" - + # Update question label for mission 2 + $MarginContainer/VBoxContainer/AnswerContainer/QuestionLabel.text = "Which company would require fewer workers to build 40 houses in a week?" + $MarginContainer/VBoxContainer/AnswerContainer/AnswerField.placeholder_text = "Enter A or B" + + # Set the correct answer + correct_answer = "A" + # Reset answer state is_answer_correct = false complete_button.disabled = true @@ -80,7 +125,9 @@ Hint: Find the pattern for each company, then calculate how many workers would b await get_tree().process_frame await get_tree().process_frame - call_deferred("create_chart") + # Only create chart for mission 2 + if mission.id != "4": + call_deferred("create_chart") # Emit signal to lock building controls panel_opened.emit() @@ -358,11 +405,20 @@ func _on_check_button_pressed(): if user_answer == correct_answer: is_answer_correct = true - feedback_label.text = "Correct! Company A (City Builders Inc.) would require fewer workers to build 40 houses. Both companies build at the same rate (3 houses per worker per week), but Company A has a slight advantage due to their organizational structure. For 40 houses, Company A needs 13.33 workers (rounded to 13) while Company B needs 13.33 workers (rounded to 14)." + + if mission.id == "4": + feedback_label.text = "Correct! With a power demand of 31.78 kilowatts and each power plant generating 40 kilowatts, 1 power plant is sufficient to power your city. You can now place a power plant within 31.6 grid units of your houses to ensure everyone has electricity!" + else: + feedback_label.text = "Correct! Company A (City Builders Inc.) would require fewer workers to build 40 houses. Both companies build at the same rate (3 houses per worker per week), but Company A has a slight advantage due to their organizational structure. For 40 houses, Company A needs 13.33 workers (rounded to 13) while Company B needs 13.33 workers (rounded to 14)." + feedback_label.add_theme_color_override("font_color", Color(0, 0.7, 0.2)) complete_button.disabled = false else: - feedback_label.text = "Not quite right. Look carefully at the lines for both companies. Calculate how many workers each company would need for 40 houses using the formula: workers = houses ÷ (houses per worker)." + if mission.id == "4": + feedback_label.text = "Not quite right. Calculate the power demand using the formula: Power needed = 2 × √n + n⁰·⁸, where n = 40 houses. Then compare this to the output of one power plant (40 kilowatts)." + else: + feedback_label.text = "Not quite right. Look carefully at the lines for both companies. Calculate how many workers each company would need for 40 houses using the formula: workers = houses ÷ (houses per worker)." + feedback_label.add_theme_color_override("font_color", Color(0.9, 0.2, 0.2)) func _on_complete_button_pressed(): diff --git a/scripts/mission/mission_manager.gd b/scripts/mission/mission_manager.gd index 15e6e47..bf302d7 100644 --- a/scripts/mission/mission_manager.gd +++ b/scripts/mission/mission_manager.gd @@ -16,6 +16,13 @@ var active_missions: Dictionary = {} # mission_id: MissionData var learning_panel: LearningPanel var character_spawned: bool = false +# Mission skip variables +var skip_key_presses: int = 0 +var last_skip_press_time: float = 0 +var skip_key_timeout: float = 1.0 # Reset counter if time between presses exceeds this value +var skip_key_required: int = 5 # Number of key presses needed to skip +const SKIP_KEY = KEY_TAB # The key to press for skipping missions + func _ready(): if builder: # Connect to builder signals @@ -45,6 +52,23 @@ func _ready(): if mission.id == "2": mission.next_mission_id = "3" + # Load fourth mission if not already in the list + var fourth_mission = load("res://mission/fourth_mission.tres") + if fourth_mission: + var found = false + for mission in missions: + if mission.id == "4": + found = true + break + + if not found: + missions.append(fourth_mission) + + # Set next_mission_id for third mission to point to fourth mission + for mission in missions: + if mission.id == "3": + mission.next_mission_id = "4" + # Start the first mission if available if missions.size() > 0: start_mission(missions[0]) @@ -89,6 +113,24 @@ func start_mission(mission: MissionData): if grass: print("Adding grass structure for mission 3") builder.structures.append(grass) + + # Special handling for mission 4: add power plant + elif mission.id == "4" and builder: + # Check if we need to add the power plant + var has_power_plant = false + + # Look through existing structures to see if we already have it + for structure in builder.structures: + if structure.model.resource_path.contains("power_plant"): + has_power_plant = true + break + + # Add the power plant if missing + if not has_power_plant: + var power_plant = load("res://structures/power-plant.tres") + if power_plant: + print("Adding power plant structure for mission 4") + builder.structures.append(power_plant) # Update the mesh library to include the new structures if builder.gridmap and builder.gridmap.mesh_library: @@ -104,7 +146,10 @@ func start_mission(mission: MissionData): # Apply appropriate scaling for all road types, buildings, and terrain var transform = Transform3D() - if (structure.type == Structure.StructureType.RESIDENTIAL_BUILDING + if structure.model.resource_path.contains("power_plant"): + # Scale power plant model to be much smaller (0.5x) + transform = transform.scaled(Vector3(0.5, 0.5, 0.5)) + elif (structure.type == Structure.StructureType.RESIDENTIAL_BUILDING or structure.type == Structure.StructureType.ROAD or structure.type == Structure.StructureType.TERRAIN or structure.model.resource_path.contains("grass")): @@ -260,6 +305,56 @@ func _on_learning_panel_closed(): if builder: builder.disabled = false +# Method to process keyboard input for mission skipping +func _input(event): + if event is InputEventKey and event.pressed and not event.is_echo(): + if event.keycode == SKIP_KEY: # Use the configured skip key + var current_time = Time.get_ticks_msec() / 1000.0 + + # Reset counter if too much time has passed since last press + if current_time - last_skip_press_time > skip_key_timeout: + skip_key_presses = 0 + + # Update time and increment counter + last_skip_press_time = current_time + skip_key_presses += 1 + + # Show progress toward skipping + if mission_ui and mission_ui.has_method("show_temporary_message"): + if skip_key_presses < skip_key_required: + mission_ui.show_temporary_message("Mission skip: " + str(skip_key_presses) + "/" + str(skip_key_required) + " presses", 0.75, Color(1.0, 0.7, 0.2)) + else: + mission_ui.show_temporary_message("Mission skipped!", 2.0, Color(0.2, 0.8, 0.2)) + + # Print feedback to console + if skip_key_presses < skip_key_required: + print("Mission skip: " + str(skip_key_presses) + "/" + str(skip_key_required) + " key presses") + + # Check if we've reached the required number of presses + if skip_key_presses >= skip_key_required: + skip_key_presses = 0 + _skip_current_mission() + +# Method to skip the current mission +func _skip_current_mission(): + if not current_mission: + print("No mission to skip") + return + + var mission_id = current_mission.id + print("Skipping mission " + mission_id) + + # Auto-complete all objectives in the current mission + for objective in current_mission.objectives: + objective.progress(objective.target_count - objective.current_count) + + # If there's a learning panel open, close it + if learning_panel and learning_panel.visible: + learning_panel.hide_learning_panel() + + # Complete the mission + complete_mission(mission_id) + func _spawn_character_on_road(building_position: Vector3): if !character_scene: print("No character scene provided!") diff --git a/scripts/mission/mission_ui.gd b/scripts/mission/mission_ui.gd index e50e0ea..1f15a36 100644 --- a/scripts/mission/mission_ui.gd +++ b/scripts/mission/mission_ui.gd @@ -5,6 +5,10 @@ class_name MissionUI @export var mission_description_label: Label @export var objectives_container: VBoxContainer +# Variable for temporary message display +var temp_message_label: Label +var temp_message_timer: Timer + # Use a Label node directly instead of a scene # This assumes the ObjectiveLabel node is set up correctly and can be duplicated @@ -48,3 +52,41 @@ func update_mission_display(mission: MissionData): # Style completed objectives differently if objective.completed: label.add_theme_color_override("font_color", Color(0, 0.8, 0.2, 1)) # Brighter green + +# Method to show a temporary message on the screen +func show_temporary_message(message: String, duration: float = 2.0, color: Color = Color.WHITE): + # Create or get the temporary message label + if not temp_message_label: + temp_message_label = Label.new() + temp_message_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + temp_message_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + temp_message_label.add_theme_font_size_override("font_size", 24) + temp_message_label.set_anchors_preset(Control.PRESET_CENTER) + + # Add it to the top of our UI + add_child(temp_message_label) + + # Create a timer for automatic hiding + temp_message_timer = Timer.new() + temp_message_timer.one_shot = true + temp_message_timer.timeout.connect(_on_temp_message_timer_timeout) + add_child(temp_message_timer) + + # Set message properties + temp_message_label.text = message + temp_message_label.add_theme_color_override("font_color", color) + + # Position the message at the top center of the screen + var viewport_size = get_viewport_rect().size + temp_message_label.position = Vector2(viewport_size.x / 2 - temp_message_label.size.x / 2, 50) + + # Show the message + temp_message_label.visible = true + + # Start the timer + temp_message_timer.start(duration) + +# Timer timeout handler +func _on_temp_message_timer_timeout(): + if temp_message_label: + temp_message_label.visible = false