Added power plant mission

pull/15/head
wadeaston 2025-03-26 14:39:40 +07:00
parent f22ed12db6
commit b97b2afce6
4 changed files with 284 additions and 16 deletions

@ -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

@ -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():

@ -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!")

@ -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