Merge branch 'feature/mission_cleanup' into main

pull/18/head
mrwadepro 2025-04-21 11:17:15 +07:00 committed by GitHub
commit 0f11145d64
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 280 additions and 821 deletions

@ -0,0 +1,8 @@
# Mission Configs
enum ObjectiveType {
BUILD_STRUCTURE,
BUILD_RESIDENTIAL,
REACH_CASH_AMOUNT,
REACH_POPULATION,
LEARNING,
}

@ -0,0 +1,10 @@
extends Node
signal population_update(count:int)
var current_scene = null
func _ready():
var root = get_tree().root
# Using a negative index counts from the end, so this gets the last child node of `root`.
current_scene = root.get_child(-1)

@ -0,0 +1,17 @@
extends Node
@export var population: int = 0
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
func set_population_count(count: int) -> void:
# Update the population count
population += count
# Emit the signal to notify other nodes
EventBus.population_update.emit(population)
# Print the updated population for debugging

@ -1,79 +0,0 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=4 format=3 uid="uid://p3xwn2mp6bm6"]
[ext_resource type="Script" path="res://scripts/mission/mission_objective.gd" id="1_dhx01"]
[ext_resource type="Script" path="res://scripts/mission/mission_data.gd" id="2_mum3p"]
[sub_resource type="Resource" id="Resource_c06be"]
script = ExtResource("1_dhx01")
type = 7
target_count = 1
current_count = 0
description = "Calculate how many power plants are needed to supply electricity to 40 houses."
structure_index = -1
completed = false
[resource]
script = ExtResource("2_mum3p")
id = "5"
title = "Residential Energy Usage"
description = ""
objectives = Array[ExtResource("1_dhx01")]([SubResource("Resource_c06be")])
rewards = {
"cash": 0
}
next_mission_id = "6"
graph_path = ""
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"
company_data = ""
power_math_content = ""
num_of_user_inputs = 2
input_labels = Array[String](["Gaming Consoles", "TVs"])
companion_dialog = {
"correct_answer": {
"animation": "celebration",
"duration": 5000,
"text": "Excellent work! You correctly determined that gaming consoles use 150 kWh and TVs use 70 kWh."
},
"hint_request": {
"animation": "thinking",
"duration": 5000,
"text": "Try setting up two equations: 2x + 3y = 660 for House A and 4x + y = 760 for House B, where x is the energy used by consoles and y is the energy used by TVs."
},
"hint_second": {
"animation": "thinking",
"duration": 5000,
"text": "You can solve this by substitution. Rearrange the second equation to find y = 760 - 4x, then substitute this into the first equation."
},
"incorrect_answer": {
"animation": "sad",
"duration": 5000,
"text": "Not quite right. Let's try setting up the equations again. Remember, we have 2 consoles and 3 TVs using 660 kWh at House A, and 4 consoles and 1 TV using 760 kWh at House B."
},
"mission_completed": {
"animation": "happy",
"duration": 5000,
"text": "Now that we know the energy usage, we can plan our city's power needs much better. Well done!"
},
"mission_started": {
"animation": "greeting",
"duration": 5000,
"text": "Let's tackle a challenging energy problem! We need to figure out how much electricity gaming consoles and TVs use."
},
"question_shown": {
"animation": "thinking",
"duration": 5000,
"text": "This is a system of equations problem. We have two houses with different combinations of gaming consoles and TVs."
}
}
unlocked_items = Array[String](["res://models/power_plant.glb"])

@ -1,63 +0,0 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=4 format=3 uid="uid://x5h4xutbldq3"]
[ext_resource type="Script" path="res://scripts/mission/mission_data.gd" id="1_nv6c6"]
[ext_resource type="Script" path="res://scripts/mission/mission_objective.gd" id="1_yfbrc"]
[sub_resource type="Resource" id="Resource_ywws1"]
script = ExtResource("1_yfbrc")
type = 2
target_count = 1
current_count = 0
description = "Build a road"
structure_index = -1
completed = false
[resource]
script = ExtResource("1_nv6c6")
id = "1"
title = "Building Roads"
description = "Welcome to the city builder! Let's start by placing your first road."
objectives = Array[ExtResource("1_yfbrc")]([SubResource("Resource_ywws1")])
rewards = {
"cash": 250
}
next_mission_id = "2"
graph_path = ""
full_screen_path = ""
intro_text = "Welcome to Stem City! Let's learn how to build a city from the ground up. Start by building a road."
question_text = ""
correct_answer = ""
feedback_text = ""
incorrect_feedback = ""
company_data = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])
companion_dialog = {
"correct_answer": {
"animation": "happy",
"duration": 5000,
"text": ["Great job! That's the right answer!", "Perfect! You got it right!"]
},
"incorrect_answer": {
"animation": "sad",
"duration": 5000,
"text": ["Hmm, that doesn't look right. Let's try again.", "Not quite right. Don't worry, you can try again!"]
},
"mission_completed": {
"animation": "happy",
"duration": 6000,
"text": ["You've completed your first mission! Great job building your first road!"]
},
"mission_started": {
"animation": "excited",
"duration": 6000,
"text": ["Welcome to Stem City! I'm your learning companion. Let's build a city together!", "Time to start our adventure in Stem City! I'll help you build your first city."]
},
"objective_completed_2": {
"animation": "happy",
"duration": 5000,
"text": ["You built a road! Now your citizens can move around the city.", "Nice work on that road! Roads help connect different parts of your city."]
}
}
unlocked_items = Array[String](["res://models/building-small-a.glb"])

@ -1,68 +0,0 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=4 format=3 uid="uid://bho4qh41asyk1"]
[ext_resource type="Script" path="res://scripts/mission/mission_objective.gd" id="1_dhx01"]
[ext_resource type="Script" path="res://scripts/mission/mission_data.gd" id="2_mum3p"]
[sub_resource type="Resource" id="Resource_c06be"]
script = ExtResource("1_dhx01")
type = 3
target_count = 40
current_count = 0
description = "Build 40 residential buildings with construction workers"
structure_index = -1
completed = false
[resource]
script = ExtResource("2_mum3p")
id = "4"
title = "City Expansion Project"
description = "Your city needs to grow! Build 40 residential buildings using your team of construction workers. Watch as they construct each building from the ground up!"
objectives = Array[ExtResource("1_dhx01")]([SubResource("Resource_c06be")])
rewards = {
"cash": 0
}
next_mission_id = "5"
graph_path = ""
full_screen_path = ""
intro_text = "Your city is expanding rapidly! Now that you've learned which construction company is more efficient, it's time to put them to work."
question_text = ""
correct_answer = ""
feedback_text = ""
incorrect_feedback = ""
company_data = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])
companion_dialog = {
"hint_request": {
"animation": "thinking",
"duration": 5000,
"text": "Try placing the construction workers near where you want to build. They'll automatically start constructing buildings!"
},
"mission_completed": {
"animation": "celebration",
"duration": 5000,
"text": "Amazing job! You've successfully built all 40 residential buildings. The city looks fantastic!"
},
"mission_progress_25": {
"animation": "happy",
"duration": 5000,
"text": "Great progress! You've completed 25% of the buildings. Keep it up!"
},
"mission_progress_50": {
"animation": "excited",
"duration": 5000,
"text": "Halfway there! The city is really taking shape now."
},
"mission_progress_75": {
"animation": "excited",
"duration": 5000,
"text": "Almost there! Just a few more buildings to go."
},
"mission_started": {
"animation": "greeting",
"duration": 5000,
"text": "Welcome to the City Expansion Project! We need to build 40 residential buildings using our construction workers."
}
}
unlocked_items = Array[String]([])

@ -1,63 +0,0 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=4 format=3 uid="uid://cjr36hqnmyn0x"]
[ext_resource type="Script" path="res://scripts/mission/mission_objective.gd" id="1_dhx01"]
[ext_resource type="Script" path="res://scripts/mission/mission_data.gd" id="2_mum3p"]
[sub_resource type="Resource" id="Resource_7c02e"]
script = ExtResource("1_dhx01")
type = 3
target_count = 1
current_count = 0
description = "Build a residential building"
structure_index = -1
completed = false
[resource]
script = ExtResource("2_mum3p")
id = "2"
title = "Building Homes"
description = "Now that we have a road, let's build a residential building for our citizens!"
objectives = Array[ExtResource("1_dhx01")]([SubResource("Resource_7c02e")])
rewards = {
"cash": 250
}
next_mission_id = "3"
graph_path = ""
full_screen_path = ""
intro_text = "Great job on the road! Now let's build a residential building where our citizens can live."
question_text = ""
correct_answer = ""
feedback_text = ""
incorrect_feedback = ""
company_data = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])
companion_dialog = {
"correct_answer": {
"animation": "happy",
"duration": 5000,
"text": ["Great job! That's the right answer!", "Perfect! You got it right!"]
},
"incorrect_answer": {
"animation": "sad",
"duration": 5000,
"text": ["Hmm, that doesn't look right. Let's try again.", "Not quite right. Don't worry, you can try again!"]
},
"mission_completed": {
"animation": "happy",
"duration": 6000,
"text": ["You've completed your second mission! Great job building your first residential building!"]
},
"mission_started": {
"animation": "excited",
"duration": 6000,
"text": ["Now it's time to build homes for our citizens!", "Let's add a residential building to our city!"]
},
"objective_completed_3": {
"animation": "excited",
"duration": 5000,
"text": ["Amazing! You built your first residential building where people can live.", "You just built a home for your city residents! The population is growing."]
}
}
unlocked_items = Array[String](["res://models/grass-trees.glb", "res://models/grass.glb"])

@ -1,68 +0,0 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=4 format=3 uid="uid://bv4r7ebpjdce4"]
[ext_resource type="Script" path="res://scripts/mission/mission_objective.gd" id="1_nxtw6"]
[ext_resource type="Script" path="res://scripts/mission/mission_data.gd" id="2_83mjp"]
[sub_resource type="Resource" id="Resource_power_plant"]
script = ExtResource("1_nxtw6")
type = 10
target_count = 1
current_count = 0
description = "Build a power plant to provide electricity to your city"
structure_index = 7
completed = false
[resource]
script = ExtResource("2_83mjp")
id = "6"
title = "Powering Your City"
description = "Now that you've calculated how many power plants you need, it's time to build one to power your growing city of 40 houses."
objectives = Array[ExtResource("1_nxtw6")]([SubResource("Resource_power_plant")])
rewards = {
"cash": 1000
}
next_mission_id = ""
graph_path = ""
full_screen_path = ""
intro_text = "Your calculations showed that one power plant will be sufficient to power all 40 houses in your city. Let's build that power plant now to keep your residents happy!"
question_text = ""
correct_answer = ""
feedback_text = ""
incorrect_feedback = ""
company_data = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])
companion_dialog = {
"building_selected": {
"animation": "happy",
"duration": 5000,
"text": "Great choice! The power plant will supply all the energy our residents need."
},
"hint_request": {
"animation": "thinking",
"duration": 5000,
"text": "Click on the power plant in your building menu and then place it in an open area of your city."
},
"mission_completed": {
"animation": "celebration",
"duration": 5000,
"text": "Congratulations! Your city now has power for all residents. You've learned how to use systems of equations to solve real-world problems!"
},
"mission_started": {
"animation": "greeting",
"duration": 5000,
"text": "Time to power up our city! Based on our calculations, we need to build a power plant to provide electricity to all 40 houses."
},
"placement_hint": {
"animation": "thinking",
"duration": 5000,
"text": "Try to place the power plant away from residential areas but close enough to supply power efficiently."
},
"placement_success": {
"animation": "excited",
"duration": 5000,
"text": "Perfect placement! Now the power plant can distribute electricity to all the houses in your city."
}
}
unlocked_items = Array[String]([])

@ -1,63 +0,0 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=4 format=3 uid="uid://dykbopx8n3c3v"]
[ext_resource type="Script" path="res://scripts/mission/mission_objective.gd" id="1_l3spi"]
[ext_resource type="Script" path="res://scripts/mission/mission_data.gd" id="2_b4llw"]
[sub_resource type="Resource" id="Resource_c06be"]
script = ExtResource("1_l3spi")
type = 7
target_count = 1
current_count = 0
description = "Compare two construction companies and determine which one is more efficient for building 40 houses in a week."
structure_index = -1
completed = false
[resource]
script = ExtResource("2_b4llw")
id = "3"
title = "Compare Construction Companies"
description = "As your city grows, you need to choose the most efficient construction company."
objectives = Array[ExtResource("1_l3spi")]([SubResource("Resource_c06be")])
rewards = {
"cash": 500
}
next_mission_id = "4"
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. 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 = "Which company requires fewer workers to build 40 houses in a week? (A or B)"
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 = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])
companion_dialog = {
"correct_answer": {
"animation": "happy",
"duration": 5000,
"text": ["Perfect! Company A is more efficient because each worker builds more houses per week.", "Exactly right! You found the company that can build the houses with fewer workers."]
},
"incorrect_answer": {
"animation": "sad",
"duration": 5000,
"text": ["Let's think about this differently. Look at how many houses each worker can build per week and calculate from there.", "Not quite right. Check the rates carefully: how many houses can one worker build in a week for each company?"]
},
"mission_completed": {
"animation": "happy",
"duration": 6000,
"text": "Great job selecting the most efficient construction company! Your city will grow faster now."
},
"mission_started": {
"animation": "excited",
"duration": 6000,
"text": ["It's time to expand our city! We need to choose the most efficient construction company.", "We have two construction companies offering their services. Let's analyze which one is better!"]
},
"objective_completed_7": {
"animation": "happy",
"duration": 5000,
"text": ["You've solved the construction company problem! That's good mathematical thinking.", "You've made a wise decision based on the data. That's how city planners work!"]
}
}
unlocked_items = Array[String](["res://models/road-corner.glb"])

Binary file not shown.

@ -1,36 +0,0 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://h0vrvst3cumo"
path="res://.godot/imported/building-small-d.glb-5e1b608dc43429cd8ce16f120ad53455.scn"
[deps]
source_file="res://models/building-small-d.glb"
dest_files=["res://.godot/imported/building-small-d.glb-5e1b608dc43429cd8ce16f120ad53455.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

@ -25,6 +25,8 @@ general/default_playback_type.web=0
[autoload]
SoundManager="*res://scripts/sound_manager.gd"
EventBus="*res://global/event_bus.gd"
Globals="*res://global/globals.gd"
[display]

@ -2,7 +2,7 @@ extends Resource
class_name GenericText
@export_enum("intro", "outro") var panel_type
@export_enum("intro", "outro", "mission") var panel_type
@export var title: String = "Welcome to Stem City"
@export_multiline var body_text: String = "Some sample body"
@export var button_text: String = "Close"

@ -1,4 +1,4 @@
[gd_scene load_steps=33 format=3 uid="uid://vgwrcfy1qawf"]
[gd_scene load_steps=63 format=3 uid="uid://vgwrcfy1qawf"]
[ext_resource type="Script" path="res://scripts/builder.gd" id="1_jybm7"]
[ext_resource type="Environment" uid="uid://jbptgqvstei3" path="res://scenes/main-environment.tres" id="1_yndf3"]
@ -8,43 +8,66 @@
[ext_resource type="Resource" uid="uid://cntgl86ianngh" path="res://structures/building-small-a.tres" id="5_v5o2m"]
[ext_resource type="Resource" uid="uid://ccb475jeg7ym5" path="res://structures/grass-trees.tres" id="6_fwsy4"]
[ext_resource type="Script" path="res://scripts/view.gd" id="8_yovpv"]
[ext_resource type="Resource" uid="uid://tm532uesguhk" path="res://structures/grass.tres" id="9_2t3p4"]
[ext_resource type="Resource" uid="uid://d2jplegnkl6u2" path="res://structures/road-corner.tres" id="10_ii8xx"]
[ext_resource type="Script" path="res://scripts/mission/mission_manager.gd" id="10_oe3re"]
[ext_resource type="Resource" uid="uid://mxrnqinnsqnt" path="res://structures/road-straight-lightposts.tres" id="11_20frt"]
[ext_resource type="Resource" uid="uid://c4qbn3d85prxx" path="res://structures/power-plant.tres" id="12_xtc7p"]
[ext_resource type="PackedScene" uid="uid://dmsy06s02tcw4" path="res://scenes/generic_text_panel.tscn" id="13_7i6dj"]
[ext_resource type="Script" path="res://scripts/mission/mission_ui.gd" id="13_xvw5w"]
[ext_resource type="Script" path="res://resources/generic_text_panel.resource.gd" id="14_76jlq"]
[ext_resource type="Script" path="res://scripts/mission/learning_panel.gd" id="14_q2ymb"]
[ext_resource type="Script" path="res://scripts/mission/mission_data.gd" id="14_vcrh8"]
[ext_resource type="Resource" uid="uid://x5h4xutbldq3" path="res://mission/first_mission.tres" id="15_obmwc"]
[ext_resource type="Resource" uid="uid://cjr36hqnmyn0x" path="res://mission/second_mission.tres" id="16_rl54y"]
[ext_resource type="Resource" uid="uid://dykbopx8n3c3v" path="res://mission/third_mission.tres" id="17_rrdy6"]
[ext_resource type="Resource" uid="uid://x5h4xutbldq3" path="res://mission/unit_1.02/census_planning_1.tres" id="15_obmwc"]
[ext_resource type="Resource" uid="uid://cjr36hqnmyn0x" path="res://mission/unit_1.02/census_planning_2.tres" id="16_rl54y"]
[ext_resource type="Resource" uid="uid://dykbopx8n3c3v" path="res://mission/unit_1.02/census_planning_3.tres" id="17_rrdy6"]
[ext_resource type="FontFile" uid="uid://d0cxd77jybrcn" path="res://fonts/lilita_one_regular.ttf" id="17_vlub6"]
[ext_resource type="PackedScene" uid="uid://b4gkfwf4i3ydl" path="res://scenes/character.tscn" id="18_8lrh8"]
[ext_resource type="Resource" uid="uid://bho4qh41asyk1" path="res://mission/fourth_mission.tres" id="18_h4fpv"]
[ext_resource type="PackedScene" uid="uid://cgk66f6rg03mj" path="res://scenes/hud.tscn" id="18_hud"]
[ext_resource type="PackedScene" uid="uid://bqjnp7uypupog" path="res://scenes/controls_panel.tscn" id="19_controls"]
[ext_resource type="Resource" uid="uid://p3xwn2mp6bm6" path="res://mission/fifth_mission.tres" id="19_e8sub"]
[ext_resource type="Script" path="res://scripts/game_manager.gd" id="20_game_manager"]
[ext_resource type="Resource" uid="uid://bv4r7ebpjdce4" path="res://mission/sixth_mission.tres" id="20_r0ysx"]
[ext_resource type="Resource" uid="uid://442cwthak2pa" path="res://mission/unit_1.02/market_research_1.tres" id="20_ngu16"]
[ext_resource type="PackedScene" uid="uid://b4s46k58ddpyc" path="res://scenes/sound_panel.tscn" id="21_sound_panel"]
[ext_resource type="Resource" uid="uid://dtal0tl2ee336" path="res://structures/store.tres" id="21_y11qv"]
[ext_resource type="Resource" uid="uid://bom5bu47dy5kp" path="res://mission/unit_1.02/market_research_2.tres" id="24_xud6a"]
[ext_resource type="Resource" uid="uid://csrqvfwp63ygr" path="res://mission/unit_1.02/market_research_3.tres" id="25_6hx7u"]
[ext_resource type="Resource" uid="uid://qwiwim2pg88f" path="res://mission/unit_1.02/market_research_4.tres" id="26_lvk23"]
[ext_resource type="Resource" uid="uid://cfgw8dblm55c5" path="res://mission/unit_1.03_1.05/grid_growth_1.tres" id="27_s0e58"]
[ext_resource type="Resource" uid="uid://ba3ndftq7dht7" path="res://mission/unit_1.03_1.05/grid_growth_2.tres" id="28_hurxs"]
[ext_resource type="Resource" uid="uid://dgimr2v12rjqu" path="res://mission/unit_1.03_1.05/grid_growth_3.tres" id="29_rhn1n"]
[ext_resource type="Resource" uid="uid://dm2o4dq2oml53" path="res://mission/unit_1.03_1.05/grid_growth_4.tres" id="30_4rwkv"]
[ext_resource type="Resource" uid="uid://btwrfq37q8vey" path="res://mission/unit_1.03_1.05/traffic_flow_1.tres" id="31_j2idb"]
[ext_resource type="Resource" uid="uid://cf7gpb4j7gq1g" path="res://mission/unit_1.03_1.05/traffic_flow_2.tres" id="32_ipu0c"]
[ext_resource type="Resource" uid="uid://ddmxjjyxgxyxo" path="res://mission/unit_1.03_1.05/traffic_flow_3.tres" id="33_c0l5e"]
[ext_resource type="Resource" uid="uid://doxd30r8qbgdq" path="res://mission/unit_1.03_1.05/traffic_flow_4.tres" id="34_21t20"]
[ext_resource type="Resource" uid="uid://duaxn13myfx22" path="res://mission/unit_1.06/sustainable_dev_1.tres" id="35_o0bjh"]
[ext_resource type="Resource" uid="uid://fuxb3pfbbwjm" path="res://mission/unit_1.06/sustainable_dev_2.tres" id="36_2wodh"]
[ext_resource type="Resource" uid="uid://byd5jxiutxpky" path="res://mission/unit_1.06/sustainable_dev_3.tres" id="37_psgx1"]
[ext_resource type="Resource" uid="uid://daug1o7kppqit" path="res://mission/unit_1.06/sustainable_dev_4.tres" id="38_hw762"]
[ext_resource type="Resource" uid="uid://cp7tcpktwlrkt" path="res://mission/unit_1.06/urban_planning_1.tres" id="39_ymw5p"]
[ext_resource type="Resource" uid="uid://c3q1afcvwi4rk" path="res://mission/unit_1.06/urban_planning_2.tres" id="40_uggp1"]
[ext_resource type="Resource" uid="uid://ct1k7n2oopwdu" path="res://mission/unit_1.06/urban_planning_3.tres" id="41_f0dxf"]
[ext_resource type="Resource" uid="uid://d1fykuxfmh2q1" path="res://mission/unit_1.06/urban_planning_4.tres" id="42_fv8gl"]
[ext_resource type="Resource" uid="uid://detwnqsq87r30" path="res://mission/unit_1.07/economic_forecast_1.tres" id="43_qvne6"]
[ext_resource type="Resource" uid="uid://bj7tjuknfaeyg" path="res://mission/unit_1.07/economic_forecast_2.tres" id="44_haub2"]
[ext_resource type="Resource" uid="uid://ctyrlnq5cxuiu" path="res://mission/unit_1.07/economic_forecast_3.tres" id="45_xs8xk"]
[ext_resource type="Resource" uid="uid://ct45gjmw5b7pa" path="res://mission/unit_1.07/economic_forecast_4.tres" id="46_pob6d"]
[ext_resource type="Resource" uid="uid://bwrkqv42wk8f" path="res://mission/unit_1.07/resource_alloc_1.tres" id="47_6w4y8"]
[ext_resource type="Resource" uid="uid://d0nblitd4ixir" path="res://mission/unit_1.07/resource_alloc_2.tres" id="48_ck35a"]
[ext_resource type="Resource" uid="uid://cxh8dgf54oimx" path="res://mission/unit_1.07/resource_alloc_3.tres" id="49_cvgxw"]
[ext_resource type="Resource" uid="uid://cpfr2xnjtpcog" path="res://mission/unit_1.07/resource_alloc_4.tres" id="50_6ke0d"]
[sub_resource type="Resource" id="Resource_1gdbm"]
script = ExtResource("14_76jlq")
panel_type = 0
title = "Welcome to Stem City "
body_text = "Hi League Community,
body_text = "Welcome to Stem City!
Your goal is to build a thriving community from the ground up. As you complete missions, you'll unlock new structures to expand and improve your city.
Each mission introduces important Math concepts used in urban planning and city management. You'll apply mathematics while watching your city grow.
As the new city planner, you need to establish a baseline understanding of your growing community. The mayor has requested a comprehensive census to guide future development.
You are the very first group of students who get to test this. So keep in mind there will be bugs, but do note them.
We are aware of the following bugs:
- Population count may be off by 1
- Lighting Baking in Web Builds are too bright
- We don't restrict building off of roads which will cause workers not to reach buildings
- Building overlap
- No builders for Power Plant
Ready to start planning your city? Click Close to see the controls and begin your first mission!
@ -126,7 +149,7 @@ outro_text_resource = SubResource("Resource_ja86h")
[node name="Builder" type="Node3D" parent="." node_paths=PackedStringArray("selector", "selector_container", "view_camera", "gridmap", "cash_display")]
script = ExtResource("1_jybm7")
structures = Array[ExtResource("2_54v6r")]([ExtResource("2_bwyku"), ExtResource("5_v5o2m"), ExtResource("6_fwsy4")])
structures = Array[ExtResource("2_54v6r")]([ExtResource("2_bwyku"), ExtResource("5_v5o2m"), ExtResource("6_fwsy4"), ExtResource("21_y11qv"), ExtResource("9_2t3p4"), ExtResource("10_ii8xx"), ExtResource("11_20frt"), ExtResource("12_xtc7p")])
selector = NodePath("Selector")
selector_container = NodePath("Selector/Container")
view_camera = NodePath("../View/Camera")
@ -175,7 +198,7 @@ resource_data = ExtResource("14_76jlq")
[node name="MissionManager" type="Node" parent="." node_paths=PackedStringArray("mission_ui", "builder")]
script = ExtResource("10_oe3re")
missions = Array[ExtResource("14_vcrh8")]([ExtResource("15_obmwc"), ExtResource("16_rl54y"), ExtResource("17_rrdy6"), ExtResource("18_h4fpv"), ExtResource("19_e8sub"), ExtResource("20_r0ysx")])
missions = Array[ExtResource("14_vcrh8")]([ExtResource("15_obmwc"), ExtResource("16_rl54y"), ExtResource("17_rrdy6"), ExtResource("20_ngu16"), ExtResource("24_xud6a"), ExtResource("25_6hx7u"), ExtResource("26_lvk23"), ExtResource("27_s0e58"), ExtResource("28_hurxs"), ExtResource("29_rhn1n"), ExtResource("30_4rwkv"), ExtResource("31_j2idb"), ExtResource("32_ipu0c"), ExtResource("33_c0l5e"), ExtResource("34_21t20"), ExtResource("35_o0bjh"), ExtResource("36_2wodh"), ExtResource("37_psgx1"), ExtResource("38_hw762"), ExtResource("39_ymw5p"), ExtResource("40_uggp1"), ExtResource("41_f0dxf"), ExtResource("42_fv8gl"), ExtResource("43_qvne6"), ExtResource("44_haub2"), ExtResource("45_xs8xk"), ExtResource("46_pob6d"), ExtResource("47_6w4y8"), ExtResource("48_ck35a"), ExtResource("49_cvgxw"), ExtResource("50_6ke0d")])
mission_ui = NodePath("MissionPanel")
builder = NodePath("../Builder")
character_scene = ExtResource("18_8lrh8")
@ -213,6 +236,7 @@ theme_override_fonts/font = ExtResource("17_vlub6")
theme_override_font_sizes/font_size = 24
text = "Mission Title"
horizontal_alignment = 1
autowrap_mode = 2
[node name="HSeparator" type="HSeparator" parent="MissionManager/MissionPanel/MarginContainer/VBoxContainer"]
layout_mode = 2
@ -362,4 +386,5 @@ offset_right = 40.0
offset_bottom = 23.0
[connection signal="all_missions_completed" from="MissionManager" to="." method="_on_mission_manager_all_missions_completed"]
[connection signal="mission_started" from="MissionManager" to="." method="_on_mission_manager_mission_started"]
[connection signal="pressed" from="MissionManager/LearningPanel/MarginContainer/VBoxContainer/HBoxContainer/CompleteButton" to="MissionManager/LearningPanel" method="_on_complete_button_pressed"]

@ -1,4 +1,4 @@
[gd_scene load_steps=2 format=3 uid="uid://cam5blhxixlnb"]
[gd_scene load_steps=2 format=3 uid="uid://bo6c8g64ixmo"]
[ext_resource type="Script" path="res://scripts/mission/mission_ui.gd" id="1_wl28p"]

@ -1,13 +0,0 @@
[gd_scene format=3 uid="uid://b7oa3hlu1ki3v"]
[node name="ObjectiveLabel" type="Control"]
layout_mode = 3
anchors_preset = 0
offset_right = 40.0
offset_bottom = 23.0
[node name="Label" type="Label" parent="."]
layout_mode = 0
offset_right = 40.0
offset_bottom = 23.0
text = "Objective"

@ -36,7 +36,6 @@ func _ready() -> void:
func _start_initial_movement():
await get_tree().process_frame
pick_random_target()
print("Initial movement target set for character at ", global_position)
# Force movement to a specific target
func _unhandled_input(event: InputEvent) -> void:

@ -106,7 +106,6 @@ func connect_to_sound_manager() -> bool:
})();
"""
// Set up the callback
js.set_callback("godot_audio_state_callback", Callable(self, "_on_audio_state_received"))
result = js.eval(post_message_script)

@ -66,7 +66,7 @@ func _ready():
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:
else:
# Scale buildings and roads to be consistent (3x)
transform = transform.scaled(Vector3(3.0, 3.0, 3.0))
@ -254,8 +254,7 @@ 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)
#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))
@ -493,16 +492,10 @@ func update_structure():
_model.scale = Vector3(0.5, 0.5, 0.5)
# Center the power plant model within the selector
_model.position = Vector3(-3.0, 0.0, 3.0) # Reset position
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")):
else:
# Scale buildings, roads, and decorative terrain to match (3x)
_model.scale = Vector3(3.0, 3.0, 3.0)
_model.position.y += 0.0 # No need for Y adjustment with scaling
else:
# Standard positioning for other structures
_model.position.y += 0.25
# Get the selector scale from the structure resource
var scale_factor = structures[index].selector_scale
@ -646,11 +639,9 @@ func _update_mission_objective_on_demolish():
var mission_manager = get_node_or_null("/root/Main/MissionManager")
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 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)
mission_manager.update_objective_progress(mission_id, MissionObjective.ObjectiveType, -1)
# Function to remove terrain (grass or trees)
func _remove_terrain(position: Vector3):

@ -1,6 +1,11 @@
extends Node
# This script handles overall game management tasks
# This script handles overall game management tasks, including audio management and UI interactions.
var config = ConfigFile.new()
var music_player: AudioStreamPlayer
var building_sfx: AudioStreamPlayer
@ -14,6 +19,11 @@ var construction_sfx: AudioStreamPlayer
func _ready():
# Load data from a file.
var err = config.load("global://config.cfg")
# If the file didn't load, ignore it.
if err != OK:
return
# Register SoundManager in the main loop for JavaScript bridge to find
Engine.get_main_loop().set_meta("sound_manager", get_node_or_null("/root/SoundManager"))
@ -296,3 +306,21 @@ func _on_mission_manager_all_missions_completed() -> void:
if generic_text_panel and outro_text_resource:
generic_text_panel.apply_resource_data(outro_text_resource)
generic_text_panel.show_panel()
func _on_mission_manager_mission_started(mission: MissionData) -> void:
var mission_manager: Node = get_node_or_null("/root/Main/MissionManager")
if mission_manager and mission_manager.mission_ui:
mission_manager.mission_ui.update_mission_display(mission)
var mission_text = GenericText.new()
mission_text.panel_type = 2
mission_text.title = mission.title
mission_text.body_text = mission.description
mission_text.button_text = "Start Mission"
print(generic_text_panel)
if generic_text_panel:
generic_text_panel.apply_resource_data(mission_text)
generic_text_panel.show_panel()

@ -1,16 +1,15 @@
extends Node
# Signals
signal population_updated(new_population)
signal electricity_updated(usage, production)
signal electricity_updated(usage, production)
# Variables
var total_population: int = 0
var total_kW_usage: float = 0.0
var total_kW_production: float = 0.0
# References
var builder
var buildeJuj
var building_construction_manager
var population_label: Label
var electricity_label: Label
@ -19,14 +18,17 @@ var population_tooltip: Control
var electricity_tooltip: Control
var controls_panel: PanelContainer
var sound_panel: PanelContainer
var builder:Node
func _ready():
# Connect to signals from the builder
builder = get_node_or_null("/root/Main/Builder")
population_updated.connect(update_population_count)
if builder:
builder.structure_placed.connect(_on_structure_placed)
builder.structure_removed.connect(_on_structure_removed)
# EventBus.population_update.connect(set_population_count)
# Initialize UI elements
population_label = $HBoxContainer/PopulationItem/PopulationLabel
@ -34,6 +36,8 @@ func _ready():
electricity_indicator = $HBoxContainer/ElectricityItem/ElectricityValues/ElectricityIndicator
population_tooltip = $PopulationTooltip
electricity_tooltip = $ElectricityTooltip
# Ensure electricity indicator starts with red color
if electricity_indicator:
@ -58,7 +62,15 @@ func _ready():
electricity_tooltip.visible = false
# Update HUD
update_hud()
update_hud()
func _process(delta):
# Update the population label if it changes
if population_label and Globals.population != total_population:
total_population = Globals.population
population_label.text = str(total_population)
# Called when a structure is placed
func _on_structure_placed(structure_index, position):
@ -119,7 +131,7 @@ func _on_structure_removed(structure_index, position):
# Update Population
func update_population_count(count: int):
func set_population_count(count: int):
total_population += count
population_label.text = str(total_population)

@ -1,13 +1,19 @@
extends Node
class_name BuildingConstructionManager
# Constants
const ObjectiveType = preload("res://configs/data.config.gd").ObjectiveType
# Signals
signal construction_completed(position)
signal worker_construction_started
signal worker_construction_ended
const CONSTRUCTION_TIME = 10.0 # seconds to build a building
# References to necessary scenes and resources
var worker_scene: PackedScene
var hud_manager: Node
@ -15,14 +21,18 @@ var nav_region: NavigationRegion3D
var builder: Node3D
var building_plot_scene: PackedScene
var final_building_scene: PackedScene
var mission_manager: MissionManager
# Keep track of all construction sites
var construction_sites = {} # position (Vector3) -> construction data (dict)
func _ready():
# Load the worker character scene - add more fallbacks to ensure we get a valid model
builder = get_node_or_null('/root/Main/Builder')
worker_scene = load("res://people/character-male-a.glb")
hud_manager = get_node_or_null("/root/Main/CanvasLayer/HUD")
mission_manager = builder.get_node_or_null("/root/Main/MissionManager")
if not worker_scene:
worker_scene = load("res://people/character-female-a.glb")
if not worker_scene:
@ -259,8 +269,8 @@ 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()
func _on_update_population(count: int):
hud_manager.population_updated.emit(count)
func update_population(count: int):
Globals.set_population_count(count)
@ -293,7 +303,7 @@ func _complete_construction(position: Vector3):
_place_final_building(position, site["structure_index"])
# Update mission objective now that construction is complete
_update_mission_objective_on_completion(site["structure_index"])
# _update_mission_objective_on_completion(site["structure_index"])
# Check if we should spawn a resident
var mission_manager = builder.get_node_or_null("/root/Main/MissionManager")
@ -307,40 +317,11 @@ func _complete_construction(position: Vector3):
# Spawn a resident from the new building (except for first building in mission 1)
if should_spawn_resident:
_spawn_resident_from_building(position)
# Update population in the HUD when construction is complete
# Try different possible paths to find the HUD
var hud = get_node_or_null("/root/Main/CanvasLayer/HUD")
# If not found, try to find it by group (we added the HUD to "hud" group)
if not hud:
var hud_nodes = get_tree().get_nodes_in_group("hud")
if hud_nodes.size() > 0:
hud = hud_nodes[0]
# If not found, try other common paths
if not 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")
break
# Last resort - try to find using builder's cash_display
if not hud and builder and builder.cash_display:
var parent = builder.cash_display.get_parent()
while parent and parent.get_parent():
if "HUD" in parent.name:
hud = parent
break
parent = parent.get_parent()
if hud and site["structure_index"] >= 0 and site["structure_index"] < builder.structures.size():
var structure = builder.structures[site["structure_index"]]
if structure.type == Structure.StructureType.RESIDENTIAL_BUILDING and structure.population_count > 0:
_on_update_population(structure.population_count)
var structure = builder.structures[site["structure_index"]]
if structure.type == Structure.StructureType.RESIDENTIAL_BUILDING and structure.population_count > 0:
update_population(structure.population_count)
# hud.total_population += structure.population_count
# hud.update_hud()
# hud.population_updated.emit(hud.total_population)
@ -365,35 +346,8 @@ func handle_demolition(position: Vector3):
# Remove the entry from the dictionary
construction_sites.erase(position)
# Function to update mission objective when construction is complete
func _update_mission_objective_on_completion(structure_index: int):
# Get reference to mission manager
var mission_manager = builder.get_node_or_null("/root/Main/MissionManager")
if mission_manager and mission_manager.current_mission:
# Check if this is a residential building
if structure_index >= 0 and structure_index < builder.structures.size():
var structure = builder.structures[structure_index]
if structure.type == Structure.StructureType.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
pass
# Special handling for mission 1
elif mission_manager.current_mission.id == "1":
# For mission 1, we need to make sure the objectives are updated
mission_manager.update_objective_progress(
mission_manager.current_mission.id,
MissionObjective.ObjectiveType.BUILD_RESIDENTIAL
)
# Trigger an immediate progress check
mission_manager.check_mission_progress(mission_manager.current_mission.id)
# Place the final building at the construction site
func _place_final_building(position: Vector3, structure_index: int):

@ -216,24 +216,21 @@ func loop_construction_sound():
# Play the sound again
construction_sound.play()
print("DEBUG: Looping sound for worker " + str(my_sound_id))
else:
print("DEBUG: Cannot loop sound - either worker not active or sound not set up")
func finish_construction():
print("DEBUG: Worker " + str(my_sound_id) + " finishing construction")
is_construction_active = false
construction_finished = true
# Stop the construction sound
if construction_sound and construction_sound.playing:
construction_sound.stop()
print("DEBUG: Stopped sound for worker " + str(my_sound_id))
# Stop the sound loop timer
if loop_timer and loop_timer.is_inside_tree():
loop_timer.stop()
print("DEBUG: Stopped timer for worker " + str(my_sound_id))
# Emit signal for compatibility
construction_ended.emit()

@ -4,6 +4,7 @@ class_name MissionManager
# Add the JavaScript bridge for HTML5 export
# This ensures JavaScript is available and degrades gracefully on other platforms
const JSBridge = preload("res://scripts/javascript_bridge.gd")
const ObjectiveType = preload("res://configs/data.config.gd").ObjectiveType
signal mission_started(mission: MissionData)
signal mission_completed(mission: MissionData)
@ -18,14 +19,15 @@ signal all_missions_completed()
@export var character_scene: PackedScene
var current_mission: MissionData
var current_objective: MissionObjective
var active_missions: Dictionary = {} # mission_id: MissionData
var character_spawned: bool = false
var learning_companion_connected: bool = false
# Panel state tracking
var is_unlocked_panel_showing = false
var delayed_mission_start_queue = [] # Queue of missions to start after unlocked panel closes
var is_unlocked_panel_showing: bool = false
var delayed_mission_start_queue = [] # Queue of missions to start after unlocked panel closes
# Mission skip variables
var skip_key_presses: int = 0
@ -39,7 +41,8 @@ var learning_panel
var fullscreen_learning_panel
func _ready():
EventBus.population_update.connect(population_updated)
if builder:
# Connect to builder signals
builder.connect("structure_placed", _on_structure_placed)
@ -225,138 +228,14 @@ func start_mission(mission: MissionData):
current_mission = mission
active_missions[mission.id] = mission
mission_started.emit(current_mission)
# Send mission started event to the learning companion
# This will also send the companion dialog data
_on_mission_started_for_companion(mission)
# Set the first objective as the current objective
if mission.objectives.size() > 0:
current_objective = mission.objectives[0]
print("Set current objective: " + str(current_objective.type) + " - " + current_objective.description)
# Fix for mission 3 to ensure accurate count
if mission.id == "3":
# Reset the residential building count to 0 to avoid any double counting
for objective in mission.objectives:
if objective.type == MissionObjective.ObjectiveType.BUILD_RESIDENTIAL:
objective.current_count = 0
objective.completed = false
# Load and run the fix script to count actual buildings
var FixMissionScript = load("res://scripts/fix_mission.gd")
if FixMissionScript:
var fix_node = Node.new()
fix_node.set_script(FixMissionScript)
fix_node.name = "FixMissionHelper"
add_child(fix_node)
# Add decorative structures and curved roads
# Use more robust checking - fallback to ID for backward compatibility
var is_construction_or_expansion = (mission.id == "2" or mission.id == "3")
if is_construction_or_expansion and builder:
# Check if we need to add the road-corner and decoration structures
var has_road_corner = false
var has_grass_trees_tall = false
var has_grass = false
# Look through existing structures to see if we already have them
for structure in builder.structures:
if structure.model.resource_path.contains("road-corner"):
has_road_corner = true
elif structure.model.resource_path.contains("grass-trees-tall"):
has_grass_trees_tall = true
elif structure.model.resource_path.contains("grass") and not structure.model.resource_path.contains("trees"):
has_grass = true
# Add the road-corner if missing
if not has_road_corner:
var road_corner = load("res://structures/road-corner.tres")
if road_corner:
builder.structures.append(road_corner)
# Add the grass-trees-tall if missing
if not has_grass_trees_tall:
var grass_trees_tall = load("res://structures/grass-trees-tall.tres")
if grass_trees_tall:
builder.structures.append(grass_trees_tall)
# Add the grass if missing
if not has_grass:
var grass = load("res://structures/grass.tres")
if grass:
builder.structures.append(grass)
# Special handling for power plant mission: add power plant
# Use more robust checking for power missions - check power_math_content as well
elif (mission.id == "5" or mission.power_math_content != "") 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:
builder.structures.append(power_plant)
# Update the mesh library to include the new structures
if builder.gridmap and builder.gridmap.mesh_library:
var mesh_library = builder.gridmap.mesh_library
# Update mesh library for any new structures
for i in range(builder.structures.size()):
var structure = builder.structures[i]
if i >= mesh_library.get_item_list().size():
var id = mesh_library.get_last_unused_item_id()
mesh_library.create_item(id)
mesh_library.set_item_mesh(id, builder.get_mesh(structure.model))
# Apply appropriate scaling for all road types, buildings, and terrain
var transform = Transform3D()
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")):
# Scale buildings, roads, and decorative terrain to be consistent (3x)
transform = transform.scaled(Vector3(3.0, 3.0, 3.0))
mesh_library.set_item_mesh_transform(id, transform)
# Make sure the builder's structure selector is updated
builder.update_structure()
# Check if mission has a learning objective
var has_learning_objective = false
# Make sure mission has valid objectives data
if mission != null and mission.objectives != null:
for objective in mission.objectives:
if objective != null and objective.type == MissionObjective.ObjectiveType.LEARNING:
has_learning_objective = true
break
# Show learning panel if mission has a learning objective
if has_learning_objective:
# Determine which panel to use based on whether full_screen_path is provided
if not mission.full_screen_path.is_empty():
# Use fullscreen panel for fullscreen missions
if fullscreen_learning_panel:
fullscreen_learning_panel.show_fullscreen_panel(mission)
else:
print("ERROR: Fullscreen learning panel not available but mission requires it")
else:
# Use regular panel for traditional missions
if learning_panel:
learning_panel.show_learning_panel(mission)
else:
print("ERROR: Regular learning panel not available")
# Emit signal and update UI
mission_started.emit(mission)
update_mission_ui()
func complete_mission(mission_id: String):
if not active_missions.has(mission_id):
@ -376,8 +255,8 @@ func complete_mission(mission_id: String):
print("Handling structure unlocking for mission: " + mission.id)
_handle_structure_unlocking(mission)
# Remove from active missions
active_missions.erase(mission_id)
# Keep a copy of the mission for UI display during transition
var completed_mission = mission
# Figure out if there's a next mission
var next_mission: MissionData
@ -391,45 +270,74 @@ func complete_mission(mission_id: String):
# Emit mission completed signal
mission_completed.emit(mission)
# Start the next mission if one is available
# Only remove from active missions after we're ready to show the next one
# This ensures the UI always has a mission to display
if next_mission:
# Start the next mission after a short delay
# Keep the active mission during the delay
await get_tree().create_timer(2.0).timeout
# Only now remove the old mission
active_missions.erase(mission_id)
# Start the next mission
start_mission(next_mission)
else:
# Only remove after delay for last mission too
await get_tree().create_timer(2.0).timeout
active_missions.erase(mission_id)
all_missions_completed.emit()
print("No more missions available - all complete!")
# Send the "end" event to the companion
await get_tree().create_timer(2.0).timeout
func update_objective_progress(mission_id, objective_type, count_change = 1):
if not active_missions.has(mission_id):
return
func update_objective_progress(structure:Structure = null):
match current_objective.type:
ObjectiveType.BUILD_RESIDENTIAL:
current_objective.current_count += structure.population_count
if current_objective.target_count <= current_objective.current_count:
current_objective.completed = true
objective_completed.emit(current_objective)
var dialog_key = "objective_completed_" + str(current_objective.type)
_send_companion_dialog(dialog_key, current_mission) ## So Companion can react
update_current_objective(current_mission)
ObjectiveType.BUILD_STRUCTURE:
current_objective.current_count += 1
if current_objective.target_count <= current_objective.current_count:
current_objective.completed = true
objective_completed.emit(current_objective)
var dialog_key = "objective_completed_" + str(current_objective.type)
_send_companion_dialog(dialog_key, current_mission) ## So Companion can react
update_current_objective(current_mission) # Go ahead and progress to nex objective if it exists.
ObjectiveType.REACH_POPULATION:
if Globals.population >= current_objective.target_count:
current_objective.completed = true
objective_completed.emit(current_objective)
var dialog_key = "objective_completed_" + str(current_objective.type)
_send_companion_dialog(dialog_key, current_mission) ## So Companion can react
update_current_objective(current_mission) # Go ahead and progress to nex objective if it exists.
# IF this is true then objectives are completed
var mission = active_missions[mission_id]
for objective in mission.objectives:
if objective.type == objective_type:
objective.current_count += count_change
# Only update to completed if we've reached the target
if objective.current_count >= objective.target_count and not objective.completed:
objective.completed = true
objective_completed.emit(objective)
# Send dialog event if available
var dialog_key = "objective_completed_" + str(objective.type)
_send_companion_dialog(dialog_key, mission)
# Update UI
update_mission_ui()
# Emit progress signal for objective
objective_progress.emit(objective, objective.current_count)
# Check if the mission is complete
check_mission_completion(mission_id)
break
update_mission_ui()
objective_progress.emit(current_objective, current_objective.current_count)
func is_structure_of_current_mission(structure:Structure):
if not current_mission:
print("ERROR: No current mission to check structure against")
return false
if current_objective.structure == structure:
return true
else:
return false
func check_objective_completion(mission_id, objective_type):
if not active_missions.has(mission_id):
@ -469,6 +377,9 @@ func reset_objective_count(objective_type, new_count):
# Send dialog event if available
var dialog_key = "objective_completed_" + str(objective.type)
_send_companion_dialog(dialog_key, mission)
# Update current objective to next incomplete one
update_current_objective(mission)
# Update UI
update_mission_ui()
@ -511,16 +422,18 @@ func _on_structure_placed(structure_index, position):
var structure = builder.structures[structure_index]
print("Structure placed: " + structure.model.resource_path)
# Update objectives based on structure type
# Check if this structure is needed for the current objective
if current_mission and is_structure_of_current_mission(structure):
# Update the objective progress
update_objective_progress(structure)
if current_mission:
if structure.type == Structure.StructureType.ROAD:
update_objective_progress(current_mission.id, MissionObjective.ObjectiveType.BUILD_ROAD)
elif structure.type == Structure.StructureType.RESIDENTIAL_BUILDING:
if structure.type == Structure.StructureType.RESIDENTIAL_BUILDING:
# Note: for mission 3, the objective update happens after construction is complete
# See builder.gd -> _on_construction_completed
# Special check for mission 1 since we might need to manually spawn a character
if current_mission.id == "1" and not character_spawned:
if character_spawned:
# Only spawn a new character if:
# 1. This is mission 1
# 2. We haven't spawned a character yet
@ -535,16 +448,6 @@ func _on_structure_placed(structure_index, position):
if spawn_character:
# This will be done after construction completes in mission_manager._on_construction_completed
print("Character will be spawned after construction completes")
else:
# Update the objective progress for building a residential structure
update_objective_progress(current_mission.id, MissionObjective.ObjectiveType.BUILD_RESIDENTIAL)
else:
# Normal case - not mission 1 or character already spawned
update_objective_progress(current_mission.id, MissionObjective.ObjectiveType.BUILD_RESIDENTIAL)
elif structure.type == Structure.StructureType.POWER_PLANT:
# For mission 5, we update the economy/power objective when a power plant is built
if current_mission.id == "6":
update_objective_progress(current_mission.id, MissionObjective.ObjectiveType.ECONOMY)
# Check for power plant unlocking in normal gameplay
if structure.type == Structure.StructureType.POWER_PLANT:
@ -558,21 +461,6 @@ func _on_structure_placed(structure_index, position):
hud.total_kW_production += power_produced
hud.update_hud()
# # Check for residential building placement to update population
# if structure.type == Structure.StructureType.RESIDENTIAL_BUILDING:
# # This should increase the city's population
# var population_added = structure.population_count
# if population_added > 0:
# # Get the HUD if available
# var hud = get_node_or_null("/root/Main/CanvasLayer/HUD")
# if hud:
# # Update the population display
# hud.total_population += population_added
# hud.update_hud()
#
# # Emit signal for population update
# hud.population_updated.emit(hud.total_population)
# Only used for mission 3, to disable builder functionality during the companion dialog
func _on_learning_panel_opened():
if builder:
@ -765,9 +653,6 @@ func _spawn_character_on_road(building_position: Vector3):
# Set character as spawned to prevent multiple spawns
character_spawned = true
# Update the objective progress for meeting a character
if current_mission and current_mission.id == "1":
update_objective_progress(current_mission.id, MissionObjective.ObjectiveType.MEET_CHARACTER)
# Make sure the character has auto-patrol is enabled if the character supports it
if character.get("auto_patrol") != null:
@ -998,33 +883,13 @@ func _handle_structure_unlocking(mission):
# If we already have explicit unlocked items defined, skip the hardcoded rules
var has_explicit_unlocks = mission is Resource and "unlocked_items" in mission and mission.unlocked_items.size() > 0
# Check for power plant unlocking in power-related missions (only if no explicit unlocks)
if (not has_explicit_unlocks) and (mission.id == "4" or mission.id == "5" or mission.power_math_content != ""):
print("Using hardcoded power plant unlocks for mission: " + mission.id)
for structure in builder.structures:
if structure.model and structure.model.resource_path.contains("power_plant"):
# Make sure structure has the unlocked property before setting it
if "unlocked" in structure:
structure.unlocked = true
# Only add to unlocked_structures if not already there
if not unlocked_structures.has(structure):
unlocked_structures.append(structure)
else:
print("WARNING: Power plant structure doesn't have an 'unlocked' property")
# Check for curved roads and decorations in city expansion missions (only if no explicit unlocks)
if (not has_explicit_unlocks) and (mission.id == "2" or mission.id == "3"):
print("Using hardcoded curved roads and decorations for mission: " + mission.id)
for structure in builder.structures:
if structure.model and (structure.model.resource_path.contains("road-corner") or structure.model.resource_path.contains("grass-trees-tall")):
# Make sure structure has the unlocked property before setting it
if "unlocked" in structure:
structure.unlocked = true
# Only add to unlocked_structures if not already there
if not unlocked_structures.has(structure):
unlocked_structures.append(structure)
else:
print("WARNING: Road/decoration structure doesn't have an 'unlocked' property")
# Commented out hardcoded power plant unlocking
# Only use explicit unlocks from the mission data's unlocked_items array
# No more hardcoded behavior for specific mission IDs
# Commented out hardcoded curved roads and decorations unlocking
# Only use explicit unlocks from the mission data's unlocked_items array
# No more hardcoded behavior for specific mission IDs
# Make sure the builder starts with a valid unlocked structure selected
var found_unlocked = false
@ -1223,6 +1088,30 @@ func _send_companion_dialog(dialog_key, mission):
JSBridge.get_interface().sendCompanionDialog(dialog_key, dialog_data)
return true
return false
# Helper function to update the current objective to the next incomplete one
func update_current_objective(mission = null):
# If no mission was provided, use the current mission
if mission == null:
mission = current_mission
if not mission:
return
# Look for the first incomplete objective
for objective in mission.objectives:
if not objective.completed:
current_objective = objective
print("Updated current objective: " + str(current_objective.type) + " - " + current_objective.description)
return
# If all objectives are complete, keep the last one as current and complete the mission
if mission.objectives.size() > 0:
current_objective = mission.objectives[-1]
print("All objectives complete, keeping last one as current objective")
# Complete the mission when we've found that all objectives are complete
if mission.id in active_missions:
check_mission_completion(mission.id)
# Fallback to force a connection if the normal method doesn't work
func _force_learning_companion_connection():
@ -1239,3 +1128,8 @@ func _force_learning_companion_connection():
# Send initial event if we've already started
if current_mission:
_on_mission_started_for_companion(current_mission)
func population_updated(new_population: Variant) -> void:
if current_objective.type == ObjectiveType.REACH_POPULATION:
update_objective_progress()

@ -1,27 +1,17 @@
extends Resource
class_name MissionObjective
enum ObjectiveType {
BUILD_STRUCTURE,
BUILD_SPECIFIC_STRUCTURE,
BUILD_ROAD,
BUILD_RESIDENTIAL,
BUILD_COMMERCIAL,
BUILD_INDUSTRIAL,
REACH_CASH_AMOUNT,
LEARNING,
CUSTOM,
MEET_CHARACTER,
ECONOMY
}
const ObjectiveType = preload("res://configs/data.config.gd").ObjectiveType
@export var type: ObjectiveType
@export var target_count: int = 1
@export var current_count: int = 0
@export var description: String = ""
@export var structure_index: int = -1 # For BUILD_SPECIFIC_STRUCTURE type
@export var completed: bool = false
@export_subgroup("Structure")
@export var structure: Structure
func is_completed() -> bool:
return current_count >= target_count

@ -54,11 +54,11 @@ func update_mission_display(mission: MissionData):
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
# Format the objective text
var progress = ""
if objective.target_count > 1:
progress = " (%d/%d)" % [objective.current_count, objective.target_count]
# var progress = ""
# if objective.target_count && objective.type > 1:
# progress = " (%d/%d)" % [objective.current_count, objective.target_count]
label.text = "%s%s" % [objective.description, progress]
label.text = objective.description
# Style completed objectives differently
if objective.completed:

@ -11,7 +11,7 @@ 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.1
var music_volume: float = 0.0
var sfx_volume: float = 0.1
# Mute states

@ -1,14 +0,0 @@
[gd_resource type="Resource" script_class="Structure" load_steps=3 format=3 uid="uid://dtal0tl2ee336"]
[ext_resource type="PackedScene" uid="uid://h0vrvst3cumo" path="res://models/building-small-d.glb" id="1_164xq"]
[ext_resource type="Script" uid="uid://smbpvh2nwds4" path="res://scripts/structure.gd" id="2_8ewai"]
[resource]
script = ExtResource("2_8ewai")
model = ExtResource("1_164xq")
type = 1
price = 70
population_count = 1
kW_usage = 1.0
kW_production = 0.0
selector_scale = 2.8