Fixes for broken merging

pull/18/head
jc-stemuli 2025-04-22 16:08:31 +07:00
commit ac096592bf
148 changed files with 2200 additions and 1098 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

@ -38,7 +38,7 @@ body {
}
#status {
background-color: #242424;
background-color: #ececf5;
display: flex;
flex-direction: column;
justify-content: center;
@ -52,6 +52,20 @@ body {
margin: auto;
}
#status-splash.show-image--false {
display: none;
}
#status-splash.fullsize--true {
height: 100%;
width: 100%;
object-fit: contain;
}
#status-splash.use-filter--false {
image-rendering: pixelated;
}
#status-progress, #status-notice {
display: none;
}
@ -90,14 +104,14 @@ body {
</noscript>
<div id="status">
<img id="status-splash" src="Starter Kit City Builder.png" alt="">
<img id="status-splash" class="show-image--true fullsize--true use-filter--true" src="Starter Kit City Builder.png" alt="">
<progress id="status-progress"></progress>
<div id="status-notice"></div>
</div>
<script src="Starter Kit City Builder.js"></script>
<script>
const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"ensureCrossOriginIsolationHeaders":false,"executable":"Starter Kit City Builder","experimentalVK":false,"fileSizes":{"Starter Kit City Builder.pck":14139952,"Starter Kit City Builder.wasm":1620216},"focusCanvas":true,"gdextensionLibs":[]};
const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"ensureCrossOriginIsolationHeaders":false,"executable":"Starter Kit City Builder","experimentalVK":false,"fileSizes":{"Starter Kit City Builder.pck":20929568,"Starter Kit City Builder.wasm":1625538},"focusCanvas":true,"gdextensionLibs":[]};
const GODOT_THREADS_ENABLED = false;
const engine = new Engine(GODOT_CONFIG);
@ -142,7 +156,7 @@ const engine = new Engine(GODOT_CONFIG);
} else if (typeof err === 'string') {
setStatusNotice(err);
} else {
setStatusNotice('An unknown error occured');
setStatusNotice('An unknown error occurred.');
}
setStatusMode('notice');
initializing = false;
@ -154,9 +168,15 @@ const engine = new Engine(GODOT_CONFIG);
if (missing.length !== 0) {
if (GODOT_CONFIG['serviceWorker'] && GODOT_CONFIG['ensureCrossOriginIsolationHeaders'] && 'serviceWorker' in navigator) {
let serviceWorkerRegistrationPromise;
try {
serviceWorkerRegistrationPromise = navigator.serviceWorker.getRegistration();
} catch (err) {
serviceWorkerRegistrationPromise = Promise.reject(new Error('Service worker registration failed.'));
}
// There's a chance that installing the service worker would fix the issue
Promise.race([
navigator.serviceWorker.getRegistration().then((registration) => {
serviceWorkerRegistrationPromise.then((registration) => {
if (registration != null) {
return Promise.reject(new Error('Service worker already exists.'));
}
@ -166,10 +186,11 @@ const engine = new Engine(GODOT_CONFIG);
new Promise((resolve) => {
setTimeout(() => resolve(), 2000);
}),
]).catch((err) => {
console.error('Error while registering service worker:', err);
}).then(() => {
]).then(() => {
// Reload if there was no error.
window.location.reload();
}).catch((err) => {
console.error('Error while registering service worker:', err);
});
} else {
// Display the message as usual

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because one or more lines are too long

@ -1 +1 @@
uid://dh1kcnetfc7is
uid://bjvd4c3rrwi43

@ -1 +1 @@
uid://cy2u2bghkbn5y
uid://bvp2gbl7k85j3

@ -1 +1 @@
uid://bndyjskd5fqfr
uid://dqmfkr0gd4tli

@ -1,7 +1,7 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=5 format=3 uid="uid://x5h4xutbldq3"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="1_nv6c6"]
[ext_resource type="Script" uid="uid://be2nkvjhpebhi" path="res://scripts/mission/mission_objective.gd" id="1_yfbrc"]
[ext_resource type="Script" uid="uid://b8b67y260mq6g" path="res://scripts/mission/mission_objective.gd" id="1_yfbrc"]
[ext_resource type="Resource" uid="uid://dv14kkhb6umkv" path="res://structures/road-straight.tres" id="2_d0ffl"]
[sub_resource type="Resource" id="Resource_ywws1"]
@ -17,7 +17,7 @@ structure = ExtResource("2_d0ffl")
script = ExtResource("1_nv6c6")
id = "1"
title = "Census Planning"
description = "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."
description = "As the new city planner, you need to collect data on two categorical variables: infrastructure types and citizen demographics. Building roads will allow you to collect this data for a two-way frequency table analysis."
objectives = Array[ExtResource("1_yfbrc")]([SubResource("Resource_ywws1")])
rewards = {
"cash": 250

@ -1,6 +1,6 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=5 format=3 uid="uid://cjr36hqnmyn0x"]
[ext_resource type="Script" uid="uid://be2nkvjhpebhi" path="res://scripts/mission/mission_objective.gd" id="1_dhx01"]
[ext_resource type="Script" uid="uid://b8b67y260mq6g" path="res://scripts/mission/mission_objective.gd" id="1_dhx01"]
[ext_resource type="Resource" uid="uid://cntgl86ianngh" path="res://structures/building-small-a.tres" id="2_em5vq"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="2_mum3p"]
@ -17,7 +17,7 @@ structure = ExtResource("2_em5vq")
script = ExtResource("2_mum3p")
id = "2"
title = "Census Planning"
description = "Now that we have a road, let's build a residential building for our citizens!"
description = "Now that we have roads, we need to collect data on housing preferences. Build residential buildings to gather categorical data on housing types and resident occupations for your two-way table analysis."
objectives = Array[ExtResource("1_dhx01")]([SubResource("Resource_7c02e")])
rewards = {
"cash": 250

@ -1,6 +1,6 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=4 format=3 uid="uid://dykbopx8n3c3v"]
[ext_resource type="Script" uid="uid://be2nkvjhpebhi" path="res://scripts/mission/mission_objective.gd" id="1_l3spi"]
[ext_resource type="Script" uid="uid://b8b67y260mq6g" path="res://scripts/mission/mission_objective.gd" id="1_l3spi"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="2_b4llw"]
[sub_resource type="Resource" id="Resource_c06be"]
@ -15,7 +15,7 @@ completed = false
script = ExtResource("2_b4llw")
id = "3"
title = "Census Planning"
description = "As your city grows, you need to choose the most efficient construction company."
description = "As your city grows, you need to interpret your two-way frequency tables. Increase your population to see relative frequencies and possible associations between resident demographics and infrastructure usage."
objectives = Array[ExtResource("1_l3spi")]([SubResource("Resource_c06be")])
rewards = {
"cash": 500

@ -1,6 +1,6 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=5 format=3 uid="uid://442cwthak2pa"]
[ext_resource type="Script" uid="uid://be2nkvjhpebhi" path="res://scripts/mission/mission_objective.gd" id="1_tcpuf"]
[ext_resource type="Script" uid="uid://b8b67y260mq6g" path="res://scripts/mission/mission_objective.gd" id="1_tcpuf"]
[ext_resource type="Resource" uid="uid://dtal0tl2ee336" path="res://structures/store.tres" id="2_d7dn8"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="3_viy3t"]
@ -17,7 +17,7 @@ structure = ExtResource("2_d7dn8")
script = ExtResource("3_viy3t")
id = "4"
title = "Market Research"
description = "Local businesses are interested in setting up shop in your city, but they need data on where to locate for maximum profit."
description = "Local businesses are analyzing your two-way tables to find associations between infrastructure and demographics. Build stores to further explore relationships between these categorical variables."
objectives = Array[ExtResource("1_tcpuf")]([SubResource("Resource_1rxby")])
rewards = {
"cash": 0

@ -17,7 +17,7 @@ structure = ExtResource("2_ktjdq")
script = ExtResource("3_w1cq1")
id = "5"
title = "Market Research"
description = "To support the growing commercial district, you need to improve city infrastructure by adding more roads."
description = "Your two-way tables show an interesting association between road access and commercial success. Add more roads to gather more data on this categorical relationship pattern."
objectives = Array[ExtResource("1_pfd6v")]([SubResource("Resource_a42kn")])
rewards = {
"cash": 300

@ -46,4 +46,4 @@ companion_dialog = {
"text": ["Let's light up the commercial district! Well-lit streets attract more shoppers in the evening."]
}
}
unlocked_items = Array[String]([])
unlocked_items = Array[String](["res://models/road-intersection.glb"])

@ -17,7 +17,7 @@ structure = ExtResource("2_evy85")
script = ExtResource("3_mtrpd")
id = "8"
title = "Grid Growth"
description = "City planners have proposed a grid-based layout for efficient city expansion. Begin implementing this pattern with strategic road placement."
description = "City planners are using scatter plots to visualize how distance from city center relates to property values. Create a grid road layout to represent this bivariate data pattern."
objectives = Array[ExtResource("1_fvshb")]([SubResource("Resource_pj2vg")])
rewards = {
"cash": 300

@ -17,7 +17,7 @@ structure = ExtResource("2_r3vpw")
script = ExtResource("3_mdu3m")
id = "9"
title = "Grid Growth"
description = "Now that you've established your grid pattern, it's time to add residential zones to the grid. This will demonstrate how building placement follows the road network."
description = "Our scatter plots show a clustering pattern between housing density and road network. Add residential buildings to explore this bivariate relationship further."
objectives = Array[ExtResource("1_miqck")]([SubResource("Resource_utswr")])
rewards = {
"cash": 350

@ -46,4 +46,4 @@ companion_dialog = {
"text": ["Let's ensure our grid has enough power! Every growing city needs reliable electricity."]
}
}
unlocked_items = Array[String]([])
unlocked_items = Array[String](["res://models/grass-trees-tall.glb", "res://models/grass.glb"])

@ -1,24 +1,35 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=5 format=3 uid="uid://dm2o4dq2oml53"]
[gd_resource type="Resource" script_class="MissionData" load_steps=7 format=3 uid="uid://dm2o4dq2oml53"]
[ext_resource type="Script" uid="uid://be2nkvjhpebhi" path="res://scripts/mission/mission_objective.gd" id="1_x4c68"]
[ext_resource type="Resource" uid="uid://ccb475jeg7ym5" path="res://structures/grass-trees.tres" id="2_hcf87"]
[ext_resource type="Resource" uid="uid://tm532uesguhk" path="res://structures/grass.tres" id="2_ckbw6"]
[ext_resource type="Resource" uid="uid://y6jafhfnhbrp" path="res://structures/grass-trees-tall.tres" id="3_i0070"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="3_mjpfs"]
[sub_resource type="Resource" id="Resource_mldwf"]
script = ExtResource("1_x4c68")
type = 0
target_count = 4
target_count = 5
current_count = 0
description = "Add 4 grass tiles with trees"
description = "Create 5 grass tiles"
completed = false
structure = ExtResource("2_hcf87")
structure = ExtResource("2_ckbw6")
[sub_resource type="Resource" id="Resource_ckbw6"]
script = ExtResource("1_x4c68")
type = 0
target_count = 5
current_count = 0
description = "Create 5 tree tiles"
completed = false
structure = ExtResource("3_i0070")
metadata/_custom_type_script = "uid://be2nkvjhpebhi"
[resource]
script = ExtResource("3_mjpfs")
id = "11"
title = "Grid Growth"
description = "Complete your grid pattern with green spaces! Add trees and grass to create a more balanced and livable city pattern."
objectives = Array[ExtResource("1_x4c68")]([SubResource("Resource_mldwf")])
objectives = Array[ExtResource("1_x4c68")]([SubResource("Resource_mldwf"), SubResource("Resource_ckbw6")])
rewards = {
"cash": 300
}

@ -17,7 +17,7 @@ structure = ExtResource("2_exdai")
script = ExtResource("3_kexcf")
id = "12"
title = "Traffic Flow Analysis"
description = "The Transportation Department wants to analyze how curved roads affect traffic flow. Add corner roads to create more natural pathways."
description = "The Transportation Department is creating scatter plots showing the relationship between road curvature and traffic speed. Add curved roads to investigate this bivariate relationship pattern."
objectives = Array[ExtResource("1_ajvs5")]([SubResource("Resource_qykcx")])
rewards = {
"cash": 300

@ -17,7 +17,7 @@ structure = ExtResource("2_4a8uo")
script = ExtResource("3_aihyv")
id = "13"
title = "Traffic Flow Analysis"
description = "To better analyze traffic patterns, we need more residents using the road network. Add residential buildings to generate traffic flow."
description = "Our scatter plots reveal interesting associations between residential density and traffic flow. Add more housing to gather data points for our bivariate analysis."
objectives = Array[ExtResource("1_8k2sf")]([SubResource("Resource_fmsjh")])
rewards = {
"cash": 350

@ -15,7 +15,7 @@ completed = false
script = ExtResource("2_x8j3r")
id = "16"
title = "Sustainable Development"
description = "Your city is growing rapidly, but you need to ensure this growth happens sustainably. Model population growth to ensure you can meet everyone's needs."
description = "Our linear model shows a strong association between population size and resource demands. Grow your population to test how closely actual data points fit this predictive line."
objectives = Array[ExtResource("1_40kcw")]([SubResource("Resource_qlixo")])
rewards = {
"cash": 500

@ -1,8 +1,7 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=7 format=3 uid="uid://fuxb3pfbbwjm"]
[gd_resource type="Resource" script_class="MissionData" load_steps=5 format=3 uid="uid://fuxb3pfbbwjm"]
[ext_resource type="Script" uid="uid://be2nkvjhpebhi" path="res://scripts/mission/mission_objective.gd" id="1_75i2t"]
[ext_resource type="Resource" uid="uid://cntgl86ianngh" path="res://structures/building-small-a.tres" id="2_lh8y5"]
[ext_resource type="Resource" uid="uid://dtal0tl2ee336" path="res://structures/store.tres" id="3_3t66o"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="4_30pdy"]
[sub_resource type="Resource" id="Resource_qb5w1"]
@ -14,28 +13,19 @@ description = "Have 6 residential buildings"
completed = false
structure = ExtResource("2_lh8y5")
[sub_resource type="Resource" id="Resource_vcbou"]
script = ExtResource("1_75i2t")
type = 0
target_count = 3
current_count = 0
description = "Have 3 store buildings"
completed = false
structure = ExtResource("3_3t66o")
[resource]
script = ExtResource("4_30pdy")
id = "17"
title = "Sustainable Development"
description = "A balanced mix of residential and commercial zones is key to sustainable development. Model the ideal ratio for your city."
objectives = Array[ExtResource("1_75i2t")]([SubResource("Resource_qb5w1"), SubResource("Resource_vcbou")])
title = "Residential Development"
description = "Build a residential zone to accommodate growth in your city's population. Adequate housing is the foundation of city planning."
objectives = Array[ExtResource("1_75i2t")]([SubResource("Resource_qb5w1")])
rewards = {
"cash": 600
"cash": 300
}
next_mission_id = "18"
next_mission_id = "17b"
graph_path = ""
full_screen_path = ""
intro_text = "Let's model the perfect balance between homes and businesses! You need 6 residential buildings and 3 store buildings."
intro_text = "Let's focus on developing the residential area first! You need to build 6 residential buildings to accommodate your growing population."
question_text = ""
correct_answer = ""
feedback_text = ""
@ -48,12 +38,12 @@ companion_dialog = {
"mission_completed": {
"animation": "happy",
"duration": 6000,
"text": ["Perfect balance! This ratio of homes to businesses creates a sustainable economic model."]
"text": ["Great job creating housing for your citizens! Now we can think about commercial needs."]
},
"mission_started": {
"animation": "excited",
"duration": 6000,
"text": ["Time to model the perfect ratio! A sustainable city needs the right balance of homes and businesses."]
"text": ["Every city needs housing first! Let's build our residential zone to support our growing population."]
}
}
unlocked_items = Array[String]([])

@ -0,0 +1,49 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=5 format=3 uid="uid://bsic030rpgh08"]
[ext_resource type="Script" uid="uid://be2nkvjhpebhi" path="res://scripts/mission/mission_objective.gd" id="1_75i2t"]
[ext_resource type="Resource" uid="uid://dtal0tl2ee336" path="res://structures/store.tres" id="2_3t66o"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="3_30pdy"]
[sub_resource type="Resource" id="Resource_vcbou"]
script = ExtResource("1_75i2t")
type = 0
target_count = 3
current_count = 0
description = "Have 3 store buildings"
completed = false
structure = ExtResource("2_3t66o")
[resource]
script = ExtResource("3_30pdy")
id = "17b"
title = "Commercial Development"
description = "Every community needs stores and businesses. Add commercial zones to create a balanced urban environment."
objectives = Array[ExtResource("1_75i2t")]([SubResource("Resource_vcbou")])
rewards = {
"cash": 300
}
next_mission_id = "18"
graph_path = ""
full_screen_path = ""
intro_text = "Now that we have housing, our residents need places to shop and work! Build 3 store buildings to support your community's commercial needs."
question_text = ""
correct_answer = ""
feedback_text = ""
incorrect_feedback = ""
company_data = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])
companion_dialog = {
"mission_completed": {
"animation": "happy",
"duration": 6000,
"text": ["Perfect balance! This ratio of homes to businesses creates a sustainable economic model."]
},
"mission_started": {
"animation": "excited",
"duration": 6000,
"text": ["Let's add some shops! A thriving city needs commercial zones to serve residents and create jobs."]
}
}
unlocked_items = Array[String](["res://structures/store.tres"])

@ -1,7 +1,7 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=5 format=3 uid="uid://daug1o7kppqit"]
[ext_resource type="Script" uid="uid://be2nkvjhpebhi" path="res://scripts/mission/mission_objective.gd" id="1_oa16m"]
[ext_resource type="Resource" uid="uid://ccb475jeg7ym5" path="res://structures/grass-trees.tres" id="2_wudjj"]
[ext_resource type="Resource" uid="uid://y6jafhfnhbrp" path="res://structures/grass-trees-tall.tres" id="2_x4xh6"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="3_3tpnw"]
[sub_resource type="Resource" id="Resource_8dxyl"]
@ -11,7 +11,7 @@ target_count = 5
current_count = 0
description = "Add 5 grass tiles with trees"
completed = false
structure = ExtResource("2_wudjj")
structure = ExtResource("2_x4xh6")
[resource]
script = ExtResource("3_3tpnw")

@ -17,7 +17,7 @@ structure = ExtResource("2_a71iq")
script = ExtResource("3_38y5t")
id = "20"
title = "Urban Planning"
description = "Good urban planning requires commercial zones strategically placed throughout the city. Plan and build new commercial districts."
description = "Our scatter plots show a linear association between commercial density and economic growth. Build stores to implement a city plan based on this linear relationship model."
objectives = Array[ExtResource("1_fywct")]([SubResource("Resource_wq8mf")])
rewards = {
"cash": 450

@ -17,7 +17,7 @@ structure = ExtResource("2_6f04c")
script = ExtResource("3_7jlc0")
id = "21"
title = "Urban Planning"
description = "Residential zoning is a critical component of urban planning. Create new residential areas to accommodate your growing population."
description = "Our scatter plot suggests a linear association between residential zones and transit usage. Build housing to further test the fit of our linear model."
objectives = Array[ExtResource("1_0s0wv")]([SubResource("Resource_3fy3g")])
rewards = {
"cash": 500

@ -46,4 +46,4 @@ companion_dialog = {
"text": ["Time to plan street lighting! Good urban planning includes well-lit public spaces."]
}
}
unlocked_items = Array[String]([])
unlocked_items = Array[String](["res://models/pavement-fountain.glb"])

@ -2,7 +2,7 @@
[ext_resource type="Script" uid="uid://be2nkvjhpebhi" path="res://scripts/mission/mission_objective.gd" id="1_wnlf0"]
[ext_resource type="Resource" uid="uid://dqqe3iofnleup" path="res://structures/pavement-fountain.tres" id="2_bmmdj"]
[ext_resource type="Resource" uid="uid://ccb475jeg7ym5" path="res://structures/grass-trees.tres" id="3_ycm66"]
[ext_resource type="Resource" uid="uid://y6jafhfnhbrp" path="res://structures/grass-trees-tall.tres" id="3_fegi2"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="4_8gmvv"]
[sub_resource type="Resource" id="Resource_1ht3m"]
@ -10,7 +10,7 @@ script = ExtResource("1_wnlf0")
type = 0
target_count = 1
current_count = 0
description = "Create 1 fountain"
description = "Add 1 fountain"
completed = false
structure = ExtResource("2_bmmdj")
@ -19,9 +19,9 @@ script = ExtResource("1_wnlf0")
type = 0
target_count = 4
current_count = 0
description = "Add 4 grass tiles with trees surrounding the fountain"
description = "Add 4 grass tiles with trees for your fountain"
completed = false
structure = ExtResource("3_ycm66")
structure = ExtResource("3_fegi2")
[resource]
script = ExtResource("4_8gmvv")

@ -15,7 +15,7 @@ completed = false
script = ExtResource("2_pv8r1")
id = "24"
title = "Economic Forecasting"
description = "Economic models predict substantial growth for your city. Use population data to forecast economic trends and prepare for the future."
description = "Our linear model helps forecast economic needs based on population size. Grow your population to 150 to test real-world predictions from this mathematical model."
objectives = Array[ExtResource("1_cjqfg")]([SubResource("Resource_e2jx7")])
rewards = {
"cash": 700
@ -44,4 +44,4 @@ companion_dialog = {
"text": ["Time to forecast economic growth! Population trends are key indicators for economic planning."]
}
}
unlocked_items = Array[String]([])
unlocked_items = Array[String](["res://models/building-small-c.glb"])

@ -1,47 +0,0 @@
[gd_resource type="Resource" script_class="MissionData" load_steps=4 format=3 uid="uid://ct45gjmw5b7pa"]
[ext_resource type="Script" uid="uid://be2nkvjhpebhi" path="res://scripts/mission/mission_objective.gd" id="1_6j12t"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="2_t4o2b"]
[sub_resource type="Resource" id="Resource_f1uf2"]
script = ExtResource("1_6j12t")
type = 2
target_count = 1000
current_count = 0
description = "Maintain a minimum of 1000 money"
completed = false
[resource]
script = ExtResource("2_t4o2b")
id = "27"
title = "Economic Forecasting"
description = "Financial reserves are critical for economic stability. Use your forecasting model to ensure your city maintains a healthy treasury."
objectives = Array[ExtResource("1_6j12t")]([SubResource("Resource_f1uf2")])
rewards = {
"cash": 500
}
next_mission_id = "28"
graph_path = ""
full_screen_path = ""
intro_text = "Let's forecast financial stability! Maintain a minimum treasury of 1000 money to ensure your city's economic health."
question_text = ""
correct_answer = ""
feedback_text = ""
incorrect_feedback = ""
company_data = ""
power_math_content = ""
num_of_user_inputs = 1
input_labels = Array[String]([])
companion_dialog = {
"mission_completed": {
"animation": "happy",
"duration": 6000,
"text": ["Well done maintaining a healthy treasury! Your financial forecasting model shows strong fiscal health."]
},
"mission_started": {
"animation": "excited",
"duration": 6000,
"text": ["Time to focus on fiscal forecasting! A strong treasury indicates good economic management."]
}
}
unlocked_items = Array[String]([])

@ -15,14 +15,14 @@ structure = ExtResource("2_qlnvj")
[resource]
script = ExtResource("3_4udbq")
id = "28"
id = "27"
title = "Resource Allocation"
description = "Efficient resource allocation starts with housing. Create a residential expansion plan that maximizes land use efficiency."
description = "Using our linear model of housing-to-population growth, build residential zones to see how the rate of change and y-intercept affect real-world outcomes."
objectives = Array[ExtResource("1_1k37f")]([SubResource("Resource_0fxto")])
rewards = {
"cash": 600
}
next_mission_id = "29"
next_mission_id = "28"
graph_path = ""
full_screen_path = ""
intro_text = "Let's practice residential resource allocation! Build 5 residential buildings while optimizing land use patterns."

@ -15,14 +15,14 @@ structure = ExtResource("2_hh0xb")
[resource]
script = ExtResource("3_k5ivn")
id = "29"
id = "28"
title = "Resource Allocation"
description = "Commercial resource allocation requires strategic planning. Develop a commercial sector that balances land use with economic benefits."
description = "Our linear model predicts commercial success based on location factors. Build stores to test the real-world predictions from our mathematical model."
objectives = Array[ExtResource("1_vdhxy")]([SubResource("Resource_21csl")])
rewards = {
"cash": 650
}
next_mission_id = "30"
next_mission_id = "29"
graph_path = ""
full_screen_path = ""
intro_text = "Let's practice commercial resource allocation! Build 4 store buildings in locations that maximize their effectiveness."

@ -15,14 +15,14 @@ structure = ExtResource("2_4rkoa")
[resource]
script = ExtResource("3_iqaae")
id = "30"
id = "29"
title = "Resource Allocation"
description = "Transportation infrastructure requires careful resource allocation. Develop an efficient road network that connects all parts of your city."
objectives = Array[ExtResource("1_c76o5")]([SubResource("Resource_r01e3")])
rewards = {
"cash": 700
}
next_mission_id = "31"
next_mission_id = "30"
graph_path = ""
full_screen_path = ""
intro_text = "Let's optimize transportation resource allocation! Build 8 road segments to create an efficient transit network."

@ -15,14 +15,14 @@ structure = ExtResource("2_y8alv")
[resource]
script = ExtResource("3_p34yh")
id = "31"
id = "30"
title = "Resource Allocation"
description = "Green space allocation improves quality of life. Create a strategic plan for natural areas throughout your city."
objectives = Array[ExtResource("1_4b0uw")]([SubResource("Resource_grdmr")])
rewards = {
"cash": 600
}
next_mission_id = "32"
next_mission_id = "31"
graph_path = ""
full_screen_path = ""
intro_text = "Let's allocate resources for green spaces! Add 6 grass tiles with trees to create natural areas in your city."

@ -1 +1 @@
uid://cv86hhyvcveeg
uid://b5etbadvyo86n

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dbgcy0sa3hq4v"
path.s3tc="res://.godot/imported/building-arcology_colormap.png-e2782f9aa8500ff8a35c6b2a1cb6af84.s3tc.ctex"
path.etc2="res://.godot/imported/building-arcology_colormap.png-e2782f9aa8500ff8a35c6b2a1cb6af84.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/building-arcology_colormap.png"
dest_files=["res://.godot/imported/building-arcology_colormap.png-e2782f9aa8500ff8a35c6b2a1cb6af84.s3tc.ctex", "res://.godot/imported/building-arcology_colormap.png-e2782f9aa8500ff8a35c6b2a1cb6af84.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://bg21cb3aolpll"
uid="uid://lusq3mbpymj"
path.s3tc="res://.godot/imported/building-small-a_colormap.png-5c3df3cd6e4a6c3a9b3ecf5882af7d0c.s3tc.ctex"
path.etc2="res://.godot/imported/building-small-a_colormap.png-5c3df3cd6e4a6c3a9b3ecf5882af7d0c.etc2.ctex"
metadata={

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://kwcq4oagouj3"
path.s3tc="res://.godot/imported/building-small-b_colormap.png-88fece0a14f60320dabbfa87ba219d29.s3tc.ctex"
path.etc2="res://.godot/imported/building-small-b_colormap.png-88fece0a14f60320dabbfa87ba219d29.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/building-small-b_colormap.png"
dest_files=["res://.godot/imported/building-small-b_colormap.png-88fece0a14f60320dabbfa87ba219d29.s3tc.ctex", "res://.godot/imported/building-small-b_colormap.png-88fece0a14f60320dabbfa87ba219d29.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bu37xeerjb4rg"
path.s3tc="res://.godot/imported/building-small-c_colormap.png-c006c810bfe7f2b5ff9ffddb2a614e72.s3tc.ctex"
path.etc2="res://.godot/imported/building-small-c_colormap.png-c006c810bfe7f2b5ff9ffddb2a614e72.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/building-small-c_colormap.png"
dest_files=["res://.godot/imported/building-small-c_colormap.png-c006c810bfe7f2b5ff9ffddb2a614e72.s3tc.ctex", "res://.godot/imported/building-small-c_colormap.png-c006c810bfe7f2b5ff9ffddb2a614e72.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

@ -2,10 +2,12 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://dq26m4wm2ukpf"
path="res://.godot/imported/grass-trees-tall_colormap.png-039581d4038a95a52ec1edb7e14a14ad.ctex"
uid="uid://nr4il1mslsty"
path.s3tc="res://.godot/imported/grass-trees-tall_colormap.png-039581d4038a95a52ec1edb7e14a14ad.s3tc.ctex"
path.etc2="res://.godot/imported/grass-trees-tall_colormap.png-039581d4038a95a52ec1edb7e14a14ad.etc2.ctex"
metadata={
"vram_texture": false
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
@ -14,11 +16,11 @@ generator_parameters={
[deps]
source_file="res://models/grass-trees-tall_colormap.png"
dest_files=["res://.godot/imported/grass-trees-tall_colormap.png-039581d4038a95a52ec1edb7e14a14ad.ctex"]
dest_files=["res://.godot/imported/grass-trees-tall_colormap.png-039581d4038a95a52ec1edb7e14a14ad.s3tc.ctex", "res://.godot/imported/grass-trees-tall_colormap.png-039581d4038a95a52ec1edb7e14a14ad.etc2.ctex"]
[params]
compress/mode=0
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
@ -34,4 +36,4 @@ process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bpcjjboqqpva0"
path.s3tc="res://.godot/imported/grass-trees_colormap.png-dfa57c874305a3beb22f3746cd6c2447.s3tc.ctex"
path.etc2="res://.godot/imported/grass-trees_colormap.png-dfa57c874305a3beb22f3746cd6c2447.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/grass-trees_colormap.png"
dest_files=["res://.godot/imported/grass-trees_colormap.png-dfa57c874305a3beb22f3746cd6c2447.s3tc.ctex", "res://.godot/imported/grass-trees_colormap.png-dfa57c874305a3beb22f3746cd6c2447.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://rgjlbnpscmcx"
path.s3tc="res://.godot/imported/grass_colormap.png-65dd6ce9d27477b7e7324a3ab6dee258.s3tc.ctex"
path.etc2="res://.godot/imported/grass_colormap.png-65dd6ce9d27477b7e7324a3ab6dee258.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/grass_colormap.png"
dest_files=["res://.godot/imported/grass_colormap.png-65dd6ce9d27477b7e7324a3ab6dee258.s3tc.ctex", "res://.godot/imported/grass_colormap.png-65dd6ce9d27477b7e7324a3ab6dee258.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://btnuo8yvjhy72"
path.s3tc="res://.godot/imported/orb_RainbowColors.png-2d51321055a3a12d1e6bcc85d1ce04af.s3tc.ctex"
path.etc2="res://.godot/imported/orb_RainbowColors.png-2d51321055a3a12d1e6bcc85d1ce04af.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "5cb645f3a6895ac3a963ac6f29d24d20"
}
[deps]
source_file="res://models/orb_RainbowColors.png"
dest_files=["res://.godot/imported/orb_RainbowColors.png-2d51321055a3a12d1e6bcc85d1ce04af.s3tc.ctex", "res://.godot/imported/orb_RainbowColors.png-2d51321055a3a12d1e6bcc85d1ce04af.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c27x4wfj6uifw"
path.s3tc="res://.godot/imported/orb_colormap.png-ced5b4e7d6e64fa19ecb895f59467c27.s3tc.ctex"
path.etc2="res://.godot/imported/orb_colormap.png-ced5b4e7d6e64fa19ecb895f59467c27.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/orb_colormap.png"
dest_files=["res://.godot/imported/orb_colormap.png-ced5b4e7d6e64fa19ecb895f59467c27.s3tc.ctex", "res://.godot/imported/orb_colormap.png-ced5b4e7d6e64fa19ecb895f59467c27.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://oo7o4pp68xgr"
path.s3tc="res://.godot/imported/pavement-fountain_colormap.png-d827cc6b1a4dc607aa80cf851d86b35e.s3tc.ctex"
path.etc2="res://.godot/imported/pavement-fountain_colormap.png-d827cc6b1a4dc607aa80cf851d86b35e.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/pavement-fountain_colormap.png"
dest_files=["res://.godot/imported/pavement-fountain_colormap.png-d827cc6b1a4dc607aa80cf851d86b35e.s3tc.ctex", "res://.godot/imported/pavement-fountain_colormap.png-d827cc6b1a4dc607aa80cf851d86b35e.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bxv7gyds50dps"
path.s3tc="res://.godot/imported/pavement_colormap.png-49f7e71226550d61a313322246614540.s3tc.ctex"
path.etc2="res://.godot/imported/pavement_colormap.png-49f7e71226550d61a313322246614540.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/pavement_colormap.png"
dest_files=["res://.godot/imported/pavement_colormap.png-49f7e71226550d61a313322246614540.s3tc.ctex", "res://.godot/imported/pavement_colormap.png-49f7e71226550d61a313322246614540.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://rmdd8v6vmym5"
path.s3tc="res://.godot/imported/road-intersection_colormap.png-c61e4b104cdf6bfa6866d3f91af681fd.s3tc.ctex"
path.etc2="res://.godot/imported/road-intersection_colormap.png-c61e4b104cdf6bfa6866d3f91af681fd.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/road-intersection_colormap.png"
dest_files=["res://.godot/imported/road-intersection_colormap.png-c61e4b104cdf6bfa6866d3f91af681fd.s3tc.ctex", "res://.godot/imported/road-intersection_colormap.png-c61e4b104cdf6bfa6866d3f91af681fd.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dcamohwwb5prk"
path="res://.godot/imported/road-split_colormap.png-d20fd957a58f362b47c3e36589efd30c.ctex"
metadata={
"vram_texture": false
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/road-split_colormap.png"
dest_files=["res://.godot/imported/road-split_colormap.png-d20fd957a58f362b47c3e36589efd30c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,39 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cckpaetm6elpf"
path.s3tc="res://.godot/imported/road-straight-lightposts_colormap.png-c8d1253418f218a084df50c53c513432.s3tc.ctex"
path.etc2="res://.godot/imported/road-straight-lightposts_colormap.png-c8d1253418f218a084df50c53c513432.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "007d2d2af08d0a09042bcd8910fe8d8f"
}
[deps]
source_file="res://models/road-straight-lightposts_colormap.png"
dest_files=["res://.godot/imported/road-straight-lightposts_colormap.png-c8d1253418f218a084df50c53c513432.s3tc.ctex", "res://.godot/imported/road-straight-lightposts_colormap.png-c8d1253418f218a084df50c53c513432.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://cucaawfsny5n6"
uid="uid://ddl8lrcoeovpw"
path="res://.godot/imported/road-straight_colormap.png-b6a117d52548e61c263bb6e65713a481.ctex"
metadata={
"vram_texture": false

Binary file not shown.

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://h0vrvst3cumo"
path="res://.godot/imported/store.glb-eb219227761a994cd73630b580ffec3c.scn"
[deps]
source_file="res://models/store.glb"
dest_files=["res://.godot/imported/store.glb-eb219227761a994cd73630b580ffec3c.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
nodes/use_node_type_suffixes=true
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

@ -1 +1 @@
uid://n26k1k7243dd
uid://bt3emc1vt40gq

@ -1,6 +1,6 @@
[gd_scene load_steps=8 format=3 uid="uid://b4gkfwf4i3ydl"]
[ext_resource type="Script" uid="uid://c2u81fdo7uou3" path="res://scripts/mission/character_controller.gd" id="1_g3a7y"]
[ext_resource type="Script" uid="uid://dexknr7it5val" path="res://scripts/mission/character_controller.gd" id="1_g3a7y"]
[ext_resource type="PackedScene" uid="uid://h2jjkvc4edsp" path="res://people/character-female-d.glb" id="2_d5jmi"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_b4w3n"]
@ -80,7 +80,7 @@ transform = Transform3D(0.6, 0, 0, 0, 0.6, 0, 0, 0, 0.6, 0, 0, 0)
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
"": SubResource("AnimationLibrary_8ib1s")
&"": SubResource("AnimationLibrary_8ib1s")
}
autoplay = "walk"
speed_scale = 3.0

@ -1 +1 @@
uid://c7qrkyfuarnfa
uid://biocll6runtqr

@ -33,6 +33,7 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_6vdxc")
show_mission_select = true
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 1
@ -151,6 +152,22 @@ texture_normal = ExtResource("6_i1y88")
ignore_texture_size = true
stretch_mode = 5
[node name="Separator5" type="VSeparator" parent="HBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="MissionSelectItem" type="HBoxContainer" parent="HBoxContainer"]
layout_mode = 2
mouse_filter = 0
theme_override_constants/separation = 8
[node name="MissionSelectButton" type="Button" parent="HBoxContainer/MissionSelectItem"]
custom_minimum_size = Vector2(120, 40)
layout_mode = 2
size_flags_vertical = 4
mouse_default_cursor_shape = 2
text = "Missions"
[node name="PopulationTooltip" type="Control" parent="."]
visible = false
layout_mode = 1
@ -207,3 +224,4 @@ autowrap_mode = 3
[connection signal="mouse_exited" from="HBoxContainer/ElectricityItem/ElectricityIcon" to="." method="_on_electricity_icon_mouse_exited"]
[connection signal="pressed" from="HBoxContainer/SoundItem/SoundButton" to="." method="_on_sound_button_pressed"]
[connection signal="pressed" from="HBoxContainer/HelpItem/HelpButton" to="." method="_on_help_button_pressed"]
[connection signal="pressed" from="HBoxContainer/MissionSelectItem/MissionSelectButton" to="." method="_on_mission_select_button_pressed"]

@ -1,8 +1,8 @@
[gd_scene load_steps=33 format=3 uid="uid://vgwrcfy1qawf"]
[gd_scene load_steps=34 format=3 uid="uid://b6eb1v02n61vv"]
[ext_resource type="Script" uid="uid://snp3i7itqnqc" path="res://scripts/builder.gd" id="1_jybm7"]
[ext_resource type="Script" uid="uid://c37h6na3e4twn" path="res://scripts/builder.gd" id="1_jybm7"]
[ext_resource type="Environment" uid="uid://jbptgqvstei3" path="res://scenes/main-environment.tres" id="1_yndf3"]
[ext_resource type="Script" uid="uid://cw0jlq5d8r66g" path="res://scripts/structure.gd" id="2_54v6r"]
[ext_resource type="Script" uid="uid://uxn26t1x4ehr" path="res://scripts/structure.gd" id="2_54v6r"]
[ext_resource type="Resource" uid="uid://dv14kkhb6umkv" path="res://structures/road-straight.tres" id="2_bwyku"]
[ext_resource type="Texture2D" uid="uid://cbk07cxgshg26" path="res://sprites/selector.png" id="4_wr1wv"]
[ext_resource type="Resource" uid="uid://cntgl86ianngh" path="res://structures/building-small-a.tres" id="5_v5o2m"]
@ -11,24 +11,22 @@
[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" uid="uid://otnxt4e77gmc" path="res://scripts/mission/mission_manager.gd" id="10_oe3re"]
[ext_resource type="PackedScene" uid="uid://c03crmaif3bss" path="res://scenes/economy_manager.tscn" id="11_2f3dj"]
[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="Script" uid="uid://ctqqmg4ydlbse" path="res://scripts/mission/mission_ui.gd" id="13_xvw5w"]
[ext_resource type="Script" uid="uid://bt3emc1vt40gq" path="res://resources/generic_text_panel.resource.gd" id="14_76jlq"]
[ext_resource type="Script" uid="uid://dnquivpg0ead" path="res://scripts/mission/learning_panel.gd" id="14_q2ymb"]
[ext_resource type="Script" uid="uid://n26k1k7243dd" path="res://resources/generic_text_panel.resource.gd" id="14_76jlq"]
[ext_resource type="Script" uid="uid://52tdmmosdbpa" path="res://scripts/mission/learning_panel.gd" id="14_q2ymb"]
[ext_resource type="Script" uid="uid://cjaik5ku37xqx" path="res://scripts/mission/mission_data.gd" id="14_vcrh8"]
[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="Resource" uid="uid://dykbopx8n3c3v" path="res://mission/third_mission.tres" id="17_rrdy6_2"]
[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="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="Script" uid="uid://b5nubrdeseay4" path="res://scripts/game_manager.gd" id="20_game_manager"]
[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="PackedScene" uid="uid://b4s46k58ddpyc" path="res://scenes/sound_panel.tscn" id="22_generic_text"]
[sub_resource type="Resource" id="Resource_1gdbm"]
script = ExtResource("14_76jlq")
@ -170,11 +168,11 @@ shadow_opacity = 0.75
[node name="ControlsPanel" parent="CanvasLayer" instance=ExtResource("19_controls")]
[node name="SoundPanel" parent="CanvasLayer" instance=ExtResource("21_sound_panel")]
[node name="SoundPanel" parent="CanvasLayer" instance=ExtResource("22_generic_text")]
anchors_preset = 8
[node name="GenericTextPanel" parent="CanvasLayer" instance=ExtResource("13_7i6dj")]
resource_data = ExtResource("14_76jlq")
[node name="GenericTextPanel" parent="CanvasLayer" instance=ExtResource("22_generic_text")]
anchors_preset = 8
[node name="MissionManager" type="Node" parent="." node_paths=PackedStringArray("mission_ui", "builder")]
script = ExtResource("10_oe3re")

@ -0,0 +1,80 @@
[gd_scene load_steps=3 format=3 uid="uid://btfwonjc8uj0w"]
[ext_resource type="Script" uid="uid://cm44v0080tvuo" path="res://scripts/mission_select_menu.gd" id="1_h1yuf"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bprw3"]
bg_color = Color(0.145098, 0.172549, 0.231373, 0.941176)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.356863, 0.670588, 0.768627, 1)
corner_radius_top_left = 8
corner_radius_top_right = 8
corner_radius_bottom_right = 8
corner_radius_bottom_left = 8
[node name="MissionSelectMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_h1yuf")
[node name="Panel" type="Panel" parent="."]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -200.0
offset_top = 40.0
offset_right = 200.0
offset_bottom = 560.0
grow_horizontal = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_bprw3")
[node name="Label" type="Label" parent="Panel"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -150.0
offset_top = 10.0
offset_right = 150.0
offset_bottom = 50.0
grow_horizontal = 2
theme_override_font_sizes/font_size = 24
text = "Mission Select (Debug)"
horizontal_alignment = 1
[node name="ScrollContainer" type="ScrollContainer" parent="."]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -180.0
offset_top = 80.0
offset_right = 180.0
offset_bottom = 540.0
grow_horizontal = 2
[node name="MissionContainer" type="VBoxContainer" parent="ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 10
[node name="CloseButton" type="Button" parent="."]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -620.0
offset_top = 40.0
offset_right = -580.0
offset_bottom = 80.0
grow_horizontal = 0
text = "X"
[connection signal="pressed" from="CloseButton" to="." method="hide"]

@ -1 +1 @@
uid://buvxhq2ipa8gt
uid://ckfghf3quyfwv

@ -1 +1 @@
uid://b1014mouu2vqu
uid://dsdnejv05yxyb

@ -1 +1 @@
uid://csqc100kx2nsm
uid://co2nthxf7p3yg

@ -26,35 +26,43 @@ var disabled: bool = false # Used to disable building functionality
signal structure_placed(structure_index, position) # For our mission flow
var invalid_placement_material: StandardMaterial3D
func _ready():
map = DataMap.new()
plane = Plane(Vector3.UP, Vector3.ZERO)
hud_manager = get_node_or_null("/root/Main/CanvasLayer/HUD")
# Create new MeshLibrary dynamically, can also be done in the editor
# See: https://docs.godotengine.org/en/stable/tutorials/3d/using_gridmaps.html
# Create invalid placement material
invalid_placement_material = StandardMaterial3D.new()
invalid_placement_material.albedo_color = Color(1, 0, 0, 0.5) # Semi-transparent red
invalid_placement_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
var mesh_library = MeshLibrary.new()
# Create construction manager
construction_manager = BuildingConstructionManager.new()
add_child(construction_manager)
# Setup the navigation region if it doesn't exist
setup_navigation_region()
# Setup construction manager
construction_manager = BuildingConstructionManager.new()
construction_manager.name = "BuildingConstructionManager" # Set a proper node name
add_child(construction_manager)
# Give the construction manager references it needs
construction_manager.builder = self
construction_manager.gridmap = gridmap
construction_manager.structures = structures
# Set gridmap cell size to 3 units
if gridmap:
gridmap.cell_size = Vector3(3, 3, 3)
# Find first unlocked structure
# Sound effects now handled in game_manager.gd
# Ensure we start with an unlocked structure
var found_unlocked = false
for i in range(structures.size()):
if "unlocked" in structures[i] and structures[i].unlocked:
index = i
found_unlocked = true
if structures[i].model != null:
print("Starting with unlocked structure: " + structures[i].model.resource_path)
else:
print("Starting with unlocked structure: (no model)")
print("Starting with unlocked structure: " + structures[i].model.resource_path)
break
if not found_unlocked:
@ -87,10 +95,19 @@ func _process(delta):
view_camera.project_ray_origin(get_viewport().get_mouse_position()),
view_camera.project_ray_normal(get_viewport().get_mouse_position()))
var gridmap_position = Vector3(round(world_position.x), 0, round(world_position.z))
# Snap to 3-unit grid
var gridmap_position = Vector3(
round(world_position.x / 3.0) * 3.0,
0,
round(world_position.z / 3.0) * 3.0
)
selector.position = lerp(selector.position, gridmap_position, delta * 40)
action_build(gridmap_position)
# Check for collisions and update visual feedback
var can_place = check_can_place(gridmap_position)
update_placement_visual(can_place)
action_build(gridmap_position, can_place)
action_demolish(gridmap_position)
# Function to check if the mouse is over any UI elements
@ -107,7 +124,7 @@ func is_mouse_over_ui() -> bool:
# Get HUD dimensions for debug
var hud = get_node_or_null("/root/Main/CanvasLayer/HUD")
if hud:
var _hud_rect = hud.get_global_rect()
var hud_rect = hud.get_global_rect()
# Get HBoxContainer dimensions - this is the actual content area
var hbox = hud.get_node_or_null("HBoxContainer")
@ -147,9 +164,6 @@ func is_mouse_over_ui() -> bool:
func get_mesh(packed_scene):
# Instantiate the scene to access its properties
if packed_scene == null:
return
var scene_instance = packed_scene.instantiate()
var mesh_instance = null
@ -186,12 +200,16 @@ func find_mesh_instance(node):
# Build (place) a structure
func action_build(gridmap_position):
func action_build(gridmap_position, can_place: bool):
if Input.is_action_just_pressed("build"):
# Check if the mouse is over any UI elements before building
if is_mouse_over_ui():
return
# Check if we can place here
if not can_place:
return
# Check if the current structure is unlocked before allowing placement
if "unlocked" in structures[index] and not structures[index].unlocked:
print("Cannot build locked structure: " + structures[index].model.resource_path)
@ -204,13 +222,13 @@ func action_build(gridmap_position):
# 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 != null and structures[index].model.resource_path.contains("power_plant")
# For grass and trees (terrain), we need special handling
var is_power_plant = structures[index].model.resource_path.contains("power_plant")
# For grass and trees (terrain), we need special handling
var is_terrain = structures[index].type == Structure.StructureType.TERRAIN
# Function to check if we're in mission 3 (when we should use construction workers)
var use_worker_construction = true
var _mission_manager = get_node_or_null("/root/Main/MissionManager")
# Check if we're in mission 3 (when we should use construction workers)
var use_worker_construction = structures[index].spawn_builder
var mission_manager = get_node_or_null("/root/Main/MissionManager")
# Sound effects are handled via game_manager.gd through the structure_placed signal
if is_road:
@ -225,13 +243,15 @@ func action_build(gridmap_position):
# Create a visible road model as a child of the NavRegion3D
_add_road_to_navregion(gridmap_position, index)
# Also add to gridmap for mission tracking
gridmap.set_cell_item(gridmap_position, index, gridmap.get_orthogonal_index_from_basis(selector.basis))
# Rebake the navigation mesh after adding the road
rebake_navigation_mesh()
# 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
@ -242,7 +262,7 @@ func action_build(gridmap_position):
# 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:
elif is_residential or use_worker_construction:
# For residential buildings in mission 3, use construction workers
# Pass the current selector basis to preserve rotation
var selector_basis = selector.basis
@ -332,7 +352,7 @@ func action_demolish(gridmap_position):
# Check for building model in the scene as a direct child of builder
var building_model_name = "Building_" + str(int(gridmap_position.x)) + "_" + str(int(gridmap_position.z))
var _has_building_model = has_node(building_model_name)
var has_building_model = has_node(building_model_name)
# Store structure index before removal for signaling
var structure_index = -1
@ -400,7 +420,7 @@ func action_demolish(gridmap_position):
# This function is no longer needed since we're using a single NavRegion3D
# Keeping it for compatibility, but it doesn't do anything now
func remove_navigation_region(_pos: Vector3):
func remove_navigation_region(position: Vector3):
# With our new approach using a single nav region, we just rebake
# the entire navigation mesh when roads are added or removed
@ -457,48 +477,23 @@ func update_structure():
for n in selector_container.get_children():
selector_container.remove_child(n)
# Check if we have a valid structure and model
if index < 0 or index >= structures.size():
print("ERROR: Invalid structure index: ", index)
return
var current_structure = structures[index]
if not current_structure:
print("ERROR: Structure at index ", index, " is null")
return
# Create new structure preview in selector
var _model = null
if current_structure.model:
_model = current_structure.model.instantiate()
if not _model:
print("ERROR: Failed to instantiate model for structure at index: ", index)
return
selector_container.add_child(_model)
var _model = structures[index].model.instantiate()
selector_container.add_child(_model)
# Get reference to the selector sprite
var selector_sprite = selector.get_node("Sprite")
if not selector_sprite:
print("ERROR: No Sprite node found in selector")
return
# Apply appropriate scaling based on structure type
if _model:
if current_structure.model.resource_path != null and current_structure.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)
# Center the power plant model within the selector
_model.position = Vector3(-3.0, 0.0, 3.0) # Reset position
elif (current_structure.type == Structure.StructureType.RESIDENTIAL_BUILDING
or current_structure.type == Structure.StructureType.ROAD
or current_structure.type == Structure.StructureType.TERRAIN
or (current_structure.model.resource_path != null and current_structure.model.resource_path.contains("grass"))):
# 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
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)
# Center the power plant model within the selector
_model.position = Vector3(-3.0, 0.0, 3.0) # Reset position
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
# Get the selector scale from the structure resource
var scale_factor = structures[index].selector_scale
@ -510,30 +505,20 @@ func update_cash():
cash_display.text = "$" + str(map.cash)
# Function to add a road model as a child of the navigation region
func _add_road_to_navregion(_pos: Vector3, structure_index: int):
func _add_road_to_navregion(position: Vector3, structure_index: int):
# Make sure we have a navigation region
if not nav_region:
setup_navigation_region()
# Create a unique name for this road based on its position
var road_name = "Road_" + str(int(_pos.x)) + "_" + str(int(_pos.z))
var road_name = "Road_" + str(int(position.x)) + "_" + str(int(position.z))
# Check if a road with this name already exists
if nav_region.has_node(road_name):
return
# Instantiate the road model - get the actual model based on road type
var road_model
var model_path = structures[structure_index].model.resource_path if structures[structure_index].model != null else ""
if model_path.contains("road-straight"):
# Use the specific road-straight model that works with navmesh
road_model = load("res://models/road-straight.glb").instantiate()
elif model_path.contains("road-corner"):
# Use the specific road-corner model
road_model = load("res://models/road-corner.glb").instantiate()
else:
# Fall back to the structure's model for other road types
road_model = structures[structure_index].model.instantiate()
# Instantiate the road model using the actual selected structure
var road_model = structures[structure_index].model.instantiate()
road_model.name = road_name
@ -541,27 +526,25 @@ func _add_road_to_navregion(_pos: Vector3, structure_index: int):
nav_region.add_child(road_model)
# Create the transform directly matching the exact one from pathing.tscn
var _transform = Transform3D()
var transform = Transform3D()
# Set scale first
_transform.basis = Basis().scaled(Vector3(3.0, 3.0, 3.0))
transform.basis = Basis().scaled(Vector3(3.0, 3.0, 3.0))
# Then apply rotation from the selector to preserve the rotation the player chose
_transform.basis = _transform.basis * selector.basis
transform.basis = transform.basis * selector.basis
# Set position
_transform.origin = _pos
_transform.origin.y = -0.065 # From the pathing scene y offset
transform.origin = position
transform.origin.y = -0.065 # From the pathing scene y offset
# Apply the complete transform in one go
road_model.transform = _transform
road_model.transform = transform
# Function to add a power plant as a direct child of the builder
func _add_power_plant(_pos: Vector3, structure_index: int):
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(_pos.x)) + "_" + str(int(_pos.z))
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):
@ -575,30 +558,30 @@ func _add_power_plant(_pos: Vector3, structure_index: int):
add_child(power_plant_model)
# Create the transform
var _transform = Transform3D()
var transform = Transform3D()
# Set scale (using the smaller 0.5x scale)
_transform.basis = Basis().scaled(Vector3(0.5, 0.5, 0.5))
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
transform.basis = transform.basis * selector.basis
# Set position with offset to center the model at the grid position
_transform.origin = _pos
transform.origin = position
# Apply position offset to center the model (matching the preview)
# These offsets need to be transformed based on the current rotation
var offset = selector.basis * Vector3(0.25, 0, -0.25)
_transform.origin += offset
var offset = selector.basis * Vector3(-3, 0, 3)
transform.origin += offset
# Apply the complete transform in one go
power_plant_model.transform = _transform
power_plant_model.transform = transform
# Function to remove a power plant
func _remove_power_plant(_pos: Vector3):
func _remove_power_plant(position: Vector3):
# Get the power plant name based on its position
var power_plant_name = "PowerPlant_" + str(int(_pos.x)) + "_" + str(int(_pos.z))
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):
@ -611,21 +594,21 @@ func _remove_power_plant(_pos: Vector3):
pass
# Function to remove a resident model when a residential building is demolished
func _remove_resident_for_building(_pos: Vector3):
func _remove_resident_for_building(position: Vector3):
# First, check if we have a nav region reference
if not nav_region and has_node("NavRegion3D"):
nav_region = get_node("NavRegion3D")
if nav_region:
# Look for resident with matching position in the name
var resident_name = "Resident_" + str(int(_pos.x)) + "_" + str(int(_pos.z))
var resident_name = "Resident_" + str(int(position.x)) + "_" + str(int(position.z))
# First try to find by exact name
var _found = false
var found = false
for child in nav_region.get_children():
if child.name.begins_with(resident_name):
child.queue_free()
_found = true
found = true
# Update the HUD population count
var hud = get_node_or_null("/root/Main/CanvasLayer/HUD")
@ -642,17 +625,14 @@ 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(_pos: Vector3):
func _remove_terrain(position: Vector3):
# Get the terrain name based on its position
var terrain_name = "Terrain_" + str(int(_pos.x)) + "_" + str(int(_pos.z))
var grass_name = "Grass_" + str(int(_pos.x)) + "_" + str(int(_pos.z))
var terrain_name = "Terrain_" + str(int(position.x)) + "_" + str(int(position.z))
# Check if terrain with this name exists
if has_node(terrain_name):
@ -664,15 +644,15 @@ func _remove_terrain(_pos: Vector3):
pass
# Function to remove building model from scene
func _remove_building_model(_pos: Vector3):
func _remove_building_model(position: Vector3):
# Try multiple possible naming patterns
var building_patterns = [
"Building_" + str(int(_pos.x)) + "_" + str(int(_pos.z)),
"building-small-a_" + str(int(_pos.x)) + "_" + str(int(_pos.z)),
"building-small-b_" + str(int(_pos.x)) + "_" + str(int(_pos.z)),
"building-small-c_" + str(int(_pos.x)) + "_" + str(int(_pos.z)),
"building-small-d_" + str(int(_pos.x)) + "_" + str(int(_pos.z)),
"building-garage_" + str(int(_pos.x)) + "_" + str(int(_pos.z))
"Building_" + str(int(position.x)) + "_" + str(int(position.z)),
"building-small-a_" + str(int(position.x)) + "_" + str(int(position.z)),
"building-small-b_" + str(int(position.x)) + "_" + str(int(position.z)),
"building-small-c_" + str(int(position.x)) + "_" + str(int(position.z)),
"building-small-d_" + str(int(position.x)) + "_" + str(int(position.z)),
"building-garage_" + str(int(position.x)) + "_" + str(int(position.z))
]
# Check if we can find the building model with any of the pattern names
@ -693,7 +673,7 @@ func _remove_building_model(_pos: Vector3):
continue
# Check if this building is at our position (with some tolerance)
var pos_diff = (child.global_transform.origin - _pos).abs()
var pos_diff = (child.global_transform.origin - position).abs()
if pos_diff.x < 0.5 and pos_diff.z < 0.5:
child.queue_free()
found = true
@ -709,7 +689,7 @@ func _remove_building_model(_pos: Vector3):
continue
# Check if this building is at our position (with some tolerance)
var pos_diff = (child.global_transform.origin - _pos).abs()
var pos_diff = (child.global_transform.origin - position).abs()
if pos_diff.x < 0.5 and pos_diff.z < 0.5:
child.queue_free()
found = true
@ -719,20 +699,20 @@ func _remove_building_model(_pos: Vector3):
if !found and gridmap:
for child in gridmap.get_children():
# Check if this is any model at our position (with some tolerance)
var pos_diff = (child.global_transform.origin - _pos).abs()
var pos_diff = (child.global_transform.origin - position).abs()
if pos_diff.x < 0.5 and pos_diff.z < 0.5:
child.queue_free()
found = true
break
# Function to remove a road model from the navigation region
func _remove_road_from_navregion(_pos: Vector3):
func _remove_road_from_navregion(position: Vector3):
# Make sure we have a navigation region
if not nav_region:
return
# Get the road name based on its position
var road_name = "Road_" + str(int(_pos.x)) + "_" + str(int(_pos.z))
var road_name = "Road_" + str(int(position.x)) + "_" + str(int(position.z))
# Check if a road with this name exists
if nav_region.has_node(road_name):
@ -755,7 +735,7 @@ func _add_existing_roads_to_navregion():
child.queue_free()
# Find all road cells in the gridmap
var _added_count = 0
var added_count = 0
# We need to convert any existing roads in the GridMap to our new system
# Find existing road cells and add them to the NavRegion3D, then clear from GridMap
@ -767,7 +747,7 @@ func _add_existing_roads_to_navregion():
_add_road_to_navregion(cell, structure_index)
# Remove from the GridMap since we're now handling roads differently
gridmap.set_cell_item(cell, -1)
_added_count += 1
added_count += 1
# Function to move all character NPCs to be children of the navigation region
func _move_characters_to_navregion():
@ -795,28 +775,60 @@ func _move_characters_to_navregion():
character.global_transform.origin = global_pos
# Function to add terrain (grass or trees) as a direct child
func _add_terrain(position: Vector3, structure_index: int):
# Create a unique name for this terrain element based on its position
var terrain_name = "Terrain_" + str(int(position.x)) + "_" + str(int(position.z))
# Check if terrain with this name already exists
if has_node(terrain_name):
return
# Instantiate the terrain model
var terrain_model = structures[structure_index].model.instantiate()
terrain_model.name = terrain_name
# Add the terrain model directly to the builder (this node)
add_child(terrain_model)
# Create the transform
var transform = Transform3D()
# Set scale (using 3.0 scale as per other terrain elements)
transform.basis = Basis().scaled(Vector3(3.0, 3.0, 3.0))
# 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
terrain_model.transform = transform
# Callback for when construction is completed
func _on_construction_completed(_pos: Vector3):
# We need to find a residential structure index to add to gridmap
var residential_index = -1
for i in range(structures.size()):
if structures[i].type == Structure.StructureType.RESIDENTIAL_BUILDING:
residential_index = i
break
func _on_construction_completed(position: Vector3):
# Get the original structure index that was selected for construction
var structure_index = -1
var rotation_index = 0
if construction_manager and construction_manager.construction_sites.has(position):
var site = construction_manager.construction_sites[position]
if site.has("structure_index"):
structure_index = site["structure_index"]
if site.has("rotation_index"):
rotation_index = site["rotation_index"]
# If we couldn't get the original structure index, fall back to finding any residential building
if structure_index < 0 or structure_index >= structures.size():
for i in range(structures.size()):
if structures[i].type == Structure.StructureType.RESIDENTIAL_BUILDING:
structure_index = i
break
if residential_index >= 0:
# Get the rotation index from the construction manager if available
var rotation_index = 0
# Try to get the rotation index from the construction manager
if construction_manager and construction_manager.construction_sites.has(_pos):
var site = construction_manager.construction_sites[_pos]
if site.has("rotation_index"):
rotation_index = site["rotation_index"]
# Add the completed residential building to the gridmap with the correct rotation
gridmap.set_cell_item(_pos, residential_index, rotation_index)
if structure_index >= 0:
# Add the completed building to the gridmap with the correct rotation and structure index
gridmap.set_cell_item(position, structure_index, rotation_index)
# Check if we need to spawn a character for mission 1
var mission_manager = get_node_or_null("/root/Main/MissionManager")
@ -828,7 +840,7 @@ func _on_construction_completed(_pos: Vector3):
# Now check if we need to manually handle mission 1 character spawning
if mission_manager.current_mission and mission_manager.current_mission.id == "1" and not mission_manager.character_spawned:
mission_manager.character_spawned = true
mission_manager._spawn_character_on_road(_pos)
mission_manager._spawn_character_on_road(position)
# NOTE: We removed the structure_placed signal emission here to fix the population double-counting
else:
@ -886,3 +898,126 @@ func action_load():
# Make sure any existing NPCs are children of the navigation region
_move_characters_to_navregion()
# Function to check if a structure can be placed at the given position
func check_can_place(pos: Vector3) -> bool:
# Check for existing structures in the gridmap
for cell in gridmap.get_used_cells():
var distance = Vector2(abs(cell.x - pos.x), abs(cell.z - pos.z))
var min_distance = 3.0 # Minimum distance between centers
# If either structure is a road and they're exactly adjacent, allow it
var existing_item = gridmap.get_cell_item(cell)
if (structures[index].type == Structure.StructureType.ROAD and
structures[existing_item].type == Structure.StructureType.ROAD):
if (distance.x == 1 and distance.y == 0) or (distance.x == 0 and distance.y == 1):
continue
# Check if too close
if distance.x < min_distance and distance.y < min_distance:
return false
# Check for roads in the navigation region
if nav_region:
for child in nav_region.get_children():
if child.name.begins_with("Road_"):
var road_pos = Vector3(
float(child.name.split("_")[1]),
0,
float(child.name.split("_")[2])
)
var distance = Vector2(abs(road_pos.x - pos.x), abs(road_pos.z - pos.z))
# If placing a road and they're exactly adjacent, allow it
if structures[index].type == Structure.StructureType.ROAD:
if (distance.x == 1 and distance.y == 0) or (distance.x == 0 and distance.y == 1):
continue
# Check if too close
if distance.x < 3 and distance.y < 3:
return false
# Check for power plants
for child in get_children():
if child.name.begins_with("PowerPlant_"):
var plant_pos = Vector3(
float(child.name.split("_")[1]),
0,
float(child.name.split("_")[2])
)
var distance = Vector2(abs(plant_pos.x - pos.x), abs(plant_pos.z - pos.z))
if distance.x < 3 and distance.y < 3:
return false
# Check for terrain
for child in get_children():
if child.name.begins_with("Terrain_"):
var terrain_pos = Vector3(
float(child.name.split("_")[1]),
0,
float(child.name.split("_")[2])
)
var distance = Vector2(abs(terrain_pos.x - pos.x), abs(terrain_pos.z - pos.z))
if distance.x < 2 and distance.y < 2:
return false
# Check for construction sites
if construction_manager:
for site_pos in construction_manager.construction_sites:
var distance = Vector2(abs(site_pos.x - pos.x), abs(site_pos.z - pos.z))
if distance.x < 3 and distance.y < 3:
return false
# Check for plots (transparent previews of buildings being constructed)
for child in get_children():
if child.name.begins_with("Plot_"):
var plot_pos = Vector3(
float(child.name.split("_")[1]),
0,
float(child.name.split("_")[2])
)
var distance = Vector2(abs(plot_pos.x - pos.x), abs(plot_pos.z - pos.z))
if distance.x < 3 and distance.y < 3:
return false
return true
# Update the visual feedback for placement
func update_placement_visual(can_place: bool):
if not selector_container or selector_container.get_child_count() == 0:
return
# Get the first child (the model)
var model = selector_container.get_child(0)
# Apply materials recursively to all mesh instances
for mesh_instance in _get_all_mesh_instances(model):
if can_place:
# Restore original materials
if mesh_instance.has_meta("original_materials"):
var original_materials = mesh_instance.get_meta("original_materials")
for i in range(original_materials.size()):
mesh_instance.set_surface_override_material(i, original_materials[i])
else:
# Store original materials if not already stored
if not mesh_instance.has_meta("original_materials"):
var materials = []
for i in range(mesh_instance.get_surface_override_material_count()):
materials.append(mesh_instance.get_surface_override_material(i))
mesh_instance.set_meta("original_materials", materials)
# Apply red transparent material
for i in range(mesh_instance.get_surface_override_material_count()):
mesh_instance.set_surface_override_material(i, invalid_placement_material)
# Helper function to get all MeshInstance3D nodes recursively
func _get_all_mesh_instances(node: Node) -> Array:
var mesh_instances = []
if node is MeshInstance3D:
mesh_instances.append(node)
for child in node.get_children():
mesh_instances.append_array(_get_all_mesh_instances(child))
return mesh_instances

@ -1 +1 @@
uid://cklb2ypicv33a
uid://c37h6na3e4twn

@ -1 +1 @@
uid://b2vlnidm83s8y
uid://drdr16kowbvmd

@ -1 +1 @@
uid://pwnd0fyfkjed
uid://do0ohmafavnum

@ -1 +1 @@
uid://3r6pls585y3h
uid://blwoflycbd7of

@ -1 +1 @@
uid://b50gqdyny3llp
uid://dlrwvmmi2pw3p

@ -3,9 +3,8 @@ extends Node
# This script handles overall game management tasks, including audio management and UI interactions.
var config = ConfigFile.new()
# Sound manager reference
var sound_manager: Node
var music_player: AudioStreamPlayer
var building_sfx: AudioStreamPlayer
@ -17,50 +16,75 @@ var construction_sfx: AudioStreamPlayer
@export var intro_text_resource: GenericText
@export var outro_text_resource: GenericText
func _ready():
print("GameManager: Initializing...")
# Load data from a file.
var err = config.load("global://config.cfg")
var err = config.load("user://config.cfg")
# If the file didn't load, ignore it.
if err != OK:
return
print("GameManager: No config file found, using defaults")
config = ConfigFile.new()
# Get sound manager reference
sound_manager = get_node_or_null("/root/SoundManager")
if sound_manager:
print("GameManager: Found sound manager, connecting signals")
# Connect to sound manager signals
sound_manager.music_volume_changed.connect(_on_music_volume_changed)
sound_manager.sfx_volume_changed.connect(_on_sfx_volume_changed)
sound_manager.music_muted_changed.connect(_on_music_muted_changed)
sound_manager.sfx_muted_changed.connect(_on_sfx_muted_changed)
# Load saved volume settings
var saved_music_volume = config.get_value("audio", "music_volume", 0.1)
var saved_sfx_volume = config.get_value("audio", "sfx_volume", 0.1)
var saved_music_muted = config.get_value("audio", "music_muted", false)
var saved_sfx_muted = config.get_value("audio", "sfx_muted", false)
print("GameManager: Loading saved settings - Music: ", saved_music_volume, " SFX: ", saved_sfx_volume)
print("GameManager: Loading saved mute states - Music: ", saved_music_muted, " SFX: ", saved_sfx_muted)
# Apply saved settings
sound_manager.music_volume = saved_music_volume
sound_manager.sfx_volume = saved_sfx_volume
sound_manager.music_muted = saved_music_muted
sound_manager.sfx_muted = saved_sfx_muted
else:
print("GameManager: Warning - Sound manager not found!")
# 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"))
Engine.get_main_loop().set_meta("sound_manager", sound_manager)
# Reference to the controls panel, sound panel, and HUD
# Reference to the controls panel and HUD
var controls_panel = $CanvasLayer/ControlsPanel
var sound_panel = $CanvasLayer/SoundPanel
var hud = $CanvasLayer/HUD
# Set up the HUD's reference to the panels
hud.controls_panel = controls_panel
hud.sound_panel = sound_panel
# Show intro text if available
if generic_text_panel and intro_text_resource:
print(generic_text_panel.resource_data)
generic_text_panel.apply_resource_data(intro_text_resource)
generic_text_panel.show_panel()
generic_text_panel.closed.connect(func():
if generic_text_panel.resource_data.panel_type == 0 and controls_panel:
if generic_text_panel and generic_text_panel.resource_data and generic_text_panel.resource_data.panel_type == 0 and controls_panel:
controls_panel.show_panel()
)
# Auto-show controls at start
# Connect controls panel closed signal
if controls_panel:
controls_panel.closed.connect(_on_controls_panel_closed)
# Check for audio initialization status (important for web)
var sound_manager = get_node_or_null("/root/SoundManager")
var can_initialize_audio = true
#
# if OS.has_feature("web") and sound_manager:
# can_initialize_audio = sound_manager.audio_initialized
#
# if not can_initialize_audio:
# # For web, wait for the audio_ready signal before initializing audio
# sound_manager.audio_ready.connect(_initialize_game_audio)
if OS.has_feature("web") and sound_manager:
can_initialize_audio = sound_manager.audio_initialized
if not can_initialize_audio:
# For web, wait for the audio_ready signal before initializing audio
sound_manager.audio_ready.connect(_initialize_game_audio)
# Set up audio if allowed (immediate for desktop, after interaction for web)
if can_initialize_audio:
@ -97,13 +121,36 @@ func _ready():
economy_manager.money_changed.connect(hud_manager.update_money)
economy_manager.population_changed.connect(hud_manager.update_population_count)
economy_manager.energy_balance_changed.connect(hud_manager.update_energy_balance)
# Initialize HUD with current values
hud_manager.update_money(economy_manager.money)
hud_manager.update_population_count(economy_manager.population)
hud_manager.update_energy_balance(economy_manager.energy_production, economy_manager.energy_consumption)
# Initialize all game audio - called immediately on desktop, after user interaction on web
func _on_music_volume_changed(new_volume: float):
print("GameManager: Music volume changed to ", new_volume)
config.set_value("audio", "music_volume", new_volume)
var err = config.save("user://config.cfg")
if err != OK:
print("GameManager: Error saving music volume: ", err)
func _on_sfx_volume_changed(new_volume: float):
print("GameManager: SFX volume changed to ", new_volume)
config.set_value("audio", "sfx_volume", new_volume)
var err = config.save("user://config.cfg")
if err != OK:
print("GameManager: Error saving SFX volume: ", err)
func _on_music_muted_changed(is_muted: bool):
print("GameManager: Music mute changed to ", is_muted)
config.set_value("audio", "music_muted", is_muted)
var err = config.save("user://config.cfg")
if err != OK:
print("GameManager: Error saving music mute: ", err)
func _on_sfx_muted_changed(is_muted: bool):
print("GameManager: SFX mute changed to ", is_muted)
config.set_value("audio", "sfx_muted", is_muted)
var err = config.save("user://config.cfg")
if err != OK:
print("GameManager: Error saving SFX mute: ", err)
# Initialize all game audio - called immediately on desktop, after user interaction for web
func _initialize_game_audio():
# Set up all audio systems
setup_background_music()
@ -113,17 +160,6 @@ func _initialize_game_audio():
# This function is called when the controls panel is closed
func _on_controls_panel_closed():
pass
# This is the perfect place to initialize audio for web builds
# since we know the user has interacted with the game
# if OS.has_feature("web"):
# # Force initialize the sound manager (will have no effect if already initialized)
# var sound_manager = get_node_or_null("/root/SoundManager")
# if sound_manager and not sound_manager.audio_initialized:
# sound_manager._initialize_web_audio()
#
# # Make sure our music is playing
# if music_player and music_player.stream and not music_player.playing:
# music_player.play()
# Function to set up the sound buses
func _setup_sound_buses():
@ -175,6 +211,10 @@ func setup_background_music():
music_player.stream = music
music_player.bus = "Music" # Use the Music bus
# Set initial volume
if sound_manager:
music_player.volume_db = sound_manager.linear_to_db(sound_manager.music_volume)
# Check if we can play audio immediately (desktop) or need to wait (web)
var can_play_now = true
if OS.has_feature("web"):
@ -190,12 +230,17 @@ func setup_background_music():
if can_play_now:
_start_background_music()
else:
print("GameManager: Warning - Could not load music file!")
# Try a fallback sound as music
var fallback_sound = load("res://sounds/building_placing.wav")
if fallback_sound:
music_player.stream = fallback_sound
music_player.bus = "Music"
# Set initial volume
if sound_manager:
music_player.volume_db = sound_manager.linear_to_db(sound_manager.music_volume)
# Check if we can play immediately
var can_play_now = true
if OS.has_feature("web"):
@ -205,6 +250,8 @@ func setup_background_music():
if can_play_now:
music_player.play()
else:
print("GameManager: Error - Could not load fallback sound!")
# Start background music playing (called directly or via signal)
func _start_background_music():
@ -306,8 +353,6 @@ func stop_construction_sound():
# but we need to keep this function for backward compatibility
pass
# Removed duplicate _retry_music_play function that was here
# Setup construction signals properly
func _setup_construction_signals():
var builder = get_node_or_null("/root/Main/Builder")

@ -1 +1 @@
uid://7upjj8h5aqo6
uid://b5nubrdeseay4

@ -1,16 +1,21 @@
extends Node
# Signals
signal population_updated(new_population)
signal electricity_updated(usage, production)
signal population_updated(count)
# Variables
var total_population: int = 0
var total_kW_usage: float = 0.0
var total_kW_production: float = 0.0
@export var show_mission_select: bool = false:
set(value):
show_mission_select = value
_update_mission_select_visibility()
# References
var builder
var mission_select_menu: Control
var mission_select_button: Button
var building_construction_manager
var population_label: Label
var electricity_label: Label
@ -19,30 +24,35 @@ var population_tooltip: Control
var electricity_tooltip: Control
var controls_panel: PanelContainer
var sound_panel: PanelContainer
var cash_label: Label
@onready var _builder = get_node_or_null("/root/Main/Builder")
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)
if _builder:
_builder.structure_placed.connect(_on_structure_placed)
_builder.structure_removed.connect(_on_structure_removed)
# Initialize UI elements
population_label = $HBoxContainer/PopulationItem/PopulationLabel
electricity_label = $HBoxContainer/ElectricityItem/ElectricityValues/ElectricityLabel
electricity_label = $HBoxContainer/ElectricityItem/ElectricityValues/ElectricityLabel
electricity_indicator = $HBoxContainer/ElectricityItem/ElectricityValues/ElectricityIndicator
population_tooltip = $PopulationTooltip
electricity_tooltip = $ElectricityTooltip
if not electricity_tooltip:
push_error("Electricity tooltip not found in HUD!")
return
cash_label = $HBoxContainer/CashItem/CashLabel
if not cash_label:
push_error("Cash label not found in HUD!")
return
mission_select_button = $HBoxContainer/MissionSelectItem/MissionSelectButton
# Get references to panels
controls_panel = get_node_or_null("/root/Main/CanvasLayer/ControlsPanel")
sound_panel = get_node_or_null("/root/Main/CanvasLayer/SoundPanel")
# Setup mission select button
if mission_select_button:
mission_select_button.connect("pressed", _on_mission_select_button_pressed)
# Setup mission select menu
_setup_mission_select_menu()
# Update mission select visibility based on export variable
_update_mission_select_visibility()
# Ensure electricity indicator starts with red color
if electricity_indicator:
@ -69,12 +79,51 @@ func _ready():
# Update HUD
update_hud()
# Set up the mission select menu
func _setup_mission_select_menu():
# Check if the mission select menu already exists
mission_select_menu = get_node_or_null("/root/Main/CanvasLayer/MissionSelectMenu")
# If not, instantiate and add it
if not mission_select_menu:
var mission_select_scene = load("res://scenes/mission_select_menu.tscn")
if mission_select_scene:
mission_select_menu = mission_select_scene.instantiate()
var canvas_layer = get_node_or_null("/root/Main/CanvasLayer")
if canvas_layer:
canvas_layer.add_child(mission_select_menu)
#mission_select_menu.hide() # Initially hidden
# Update mission select visibility based on export variable
func _update_mission_select_visibility():
var mission_select_item = $HBoxContainer/MissionSelectItem
if mission_select_item:
mission_select_item.visible = show_mission_select
# Handle mission select button press
func _on_mission_select_button_pressed():
if mission_select_menu:
mission_select_menu.toggle_visibility()
else:
# Try to set up the menu if it doesn't exist yet
_setup_mission_select_menu()
if mission_select_menu:
mission_select_menu.show()
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):
if !builder or structure_index < 0 or structure_index >= builder.structures.size():
if !_builder or structure_index < 0 or structure_index >= _builder.structures.size():
return
var structure = builder.structures[structure_index]
var structure = _builder.structures[structure_index]
# Only update population for non-residential buildings or if we're NOT in the construction mission
var is_residential = structure.type == Structure.StructureType.RESIDENTIAL_BUILDING
@ -96,13 +145,13 @@ func _on_structure_placed(structure_index, position):
# Called when a structure is removed
func _on_structure_removed(structure_index, position):
if !builder or structure_index < 0 or structure_index >= builder.structures.size():
if !_builder or structure_index < 0 or structure_index >= _builder.structures.size():
return
var structure = builder.structures[structure_index]
var structure = _builder.structures[structure_index]
# Update population (but only for non-residential buildings in mission 3)
# For residential buildings in mission 3, we handle population separately in builder._remove_resident_for_building
# For residential buildings in mission 3, we handle population separately in _builder._remove_resident_for_building
var skip_population_update = false
var mission_manager = get_node_or_null("/root/Main/MissionManager")
@ -111,7 +160,7 @@ func _on_structure_removed(structure_index, position):
# Only update population for one resident, since we're removing them one by one
# We don't do total reset based on structure.population_count
skip_population_update = true
# We decrement by 1 in builder._remove_resident_for_building instead
# We decrement by 1 in _builder._remove_resident_for_building instead
if !skip_population_update:
total_population = max(0, total_population - structure.population_count)
@ -128,7 +177,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)
@ -207,3 +256,19 @@ func _on_help_button_pressed():
if controls_panel:
controls_panel.show_panel()
# Called when the music volume is changed
func _on_music_volume_changed(new_volume):
pass # Sound panel handles this through signals
# Called when the sfx volume is changed
func _on_sfx_volume_changed(new_volume):
pass # Sound panel handles this through signals
# Called when the music is muted
func _on_music_muted_changed(is_muted):
pass # Sound panel handles this through signals
# Called when the sfx is muted
func _on_sfx_muted_changed(is_muted):
pass # Sound panel handles this through signals

@ -1 +1 @@
uid://d02v1gsthvh03
uid://delou5bqioyxe

@ -1 +1 @@
uid://b40gjqb6qr1w7
uid://dmhl1875gg8mt

@ -19,6 +19,8 @@ var worker_scene: PackedScene
var hud_manager: Node
var nav_region: NavigationRegion3D
var builder: Node3D
var gridmap: GridMap
var structures: Array[Structure]
var building_plot_scene: PackedScene
var final_building_scene: PackedScene
var mission_manager: MissionManager
@ -81,7 +83,16 @@ func start_construction(position: Vector3, structure_index: int, rotation_basis
}
# Place plot marker (outline/transparent version of the building)
var plot = building_plot_scene.instantiate()
var plot
# Use the actual structure model for the transparent preview if available
if structure_index >= 0 and structure_index < builder.structures.size():
var structure = builder.structures[structure_index]
plot = structure.model.instantiate()
else:
# Fallback to default building model
plot = building_plot_scene.instantiate()
plot.name = "Plot_" + str(int(position.x)) + "_" + str(int(position.z))
# Make it a transparent outline by applying transparency to all materials
@ -100,8 +111,26 @@ func start_construction(position: Vector3, structure_index: int, rotation_basis
# Store reference
construction_sites[position]["plot"] = plot
# Find a road position to spawn the worker
_spawn_worker_for_construction(position)
# Check if we should spawn a worker based on structure type
var should_spawn_worker = false
if structure_index >= 0 and structure_index < builder.structures.size():
var structure = builder.structures[structure_index]
# Check if this structure type should spawn a builder
if structure.spawn_builder:
should_spawn_worker = true
else:
# Default to true if we can't determine the structure type
should_spawn_worker = true
# Find a road position to spawn the worker if needed
if should_spawn_worker:
_spawn_worker_for_construction(position)
else:
# For structures without workers, start a timer to complete construction automatically
var timer = get_tree().create_timer(CONSTRUCTION_TIME)
timer.timeout.connect(func(): _complete_construction(position))
# Send building_selected dialog to learning companion if available in the current mission
var mission_manager = builder.get_node_or_null("/root/Main/MissionManager")
@ -305,26 +334,23 @@ func _complete_construction(position: Vector3):
# Update mission objective now that construction is complete
# _update_mission_objective_on_completion(site["structure_index"])
# Check if we should spawn a resident
# Check if we should spawn a resident (only for residential buildings)
var mission_manager = builder.get_node_or_null("/root/Main/MissionManager")
var should_spawn_resident = true
var should_spawn_resident = false
# Only spawn residents for residential buildings
# Don't spawn a resident for the first building in mission 1
# (let the mission manager handle that case to avoid double spawning)
if mission_manager and mission_manager.current_mission and mission_manager.current_mission.id == "1" and !mission_manager.character_spawned:
should_spawn_resident = false
var structure = builder.structures[site["structure_index"]]
if structure.type == Structure.StructureType.RESIDENTIAL_BUILDING:
should_spawn_resident = true
# Spawn a resident from the new building (except for first building in mission 1)
# Spawn a resident from the new residential building if appropriate
if should_spawn_resident:
_spawn_resident_from_building(position)
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)
#
# Emit completion signal
construction_completed.emit(position)
@ -351,8 +377,14 @@ func handle_demolition(position: Vector3):
# Place the final building at the construction site
func _place_final_building(position: Vector3, structure_index: int):
# Create the final building
var building = final_building_scene.instantiate()
# Create the final building using the actual selected structure model
var building
if structure_index >= 0 and structure_index < builder.structures.size():
building = builder.structures[structure_index].model.instantiate()
else:
# Fallback to our default model if structure index is invalid
building = final_building_scene.instantiate()
building.name = "Building_" + str(int(position.x)) + "_" + str(int(position.z))
# Add to scene at the correct position and scale

@ -1 +1 @@
uid://cegv6v6dfcsyt
uid://ddmndkteb5eb4

@ -1 +1 @@
uid://dexknr7it5val
uid://c2u81fdo7uou3

@ -1 +1 @@
uid://dfytqya2vsi81
uid://b5k0gonwfq366

@ -1 +1 @@
uid://dptrv1nv60fl2
uid://demokh201veqe

@ -1 +1 @@
uid://d1wadrwuwmuuk
uid://c0nwxrf3du5vv

@ -1 +1 @@
uid://d4isuuaa51b7x
uid://b30jnxc4070o6

@ -1 +1 @@
uid://l2b10qh2u8n4
uid://4k22p5v74jvs

@ -1 +1 @@
uid://ynwv56k844ps
uid://d0h5p0ybjqtfp

@ -1 +1 @@
uid://52tdmmosdbpa
uid://dnquivpg0ead

@ -1 +1 @@
uid://dmelvshrg8l56
uid://bjgjswpopgire

@ -1 +1 @@
uid://bqxaa6voplja8
uid://cjaik5ku37xqx

@ -234,8 +234,11 @@ func start_mission(mission: MissionData):
if mission.objectives.size() > 0:
current_objective = mission.objectives[0]
print("Set current objective: " + str(current_objective.type) + " - " + current_objective.description)
# Immediately update the mission UI with the new mission
if mission_ui:
print("Updating mission UI with new mission")
mission_ui.update_mission_display(mission)
func complete_mission(mission_id: String):
if not active_missions.has(mission_id):
@ -293,39 +296,45 @@ func complete_mission(mission_id: String):
await get_tree().create_timer(2.0).timeout
func update_objective_progress(structure:Structure = null):
print("Updating objective progress for type: " + str(current_objective.type))
print("Current count: " + str(current_objective.current_count) + " / " + str(current_objective.target_count))
match current_objective.type:
ObjectiveType.BUILD_RESIDENTIAL:
current_objective.current_count += structure.population_count
if current_objective.target_count <= current_objective.current_count:
print("Residential building objective completed!")
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
_send_companion_dialog(dialog_key, current_mission)
update_current_objective(current_mission)
ObjectiveType.BUILD_STRUCTURE:
current_objective.current_count += 1
if current_objective.target_count <= current_objective.current_count:
print("Build structure objective completed!")
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.
_send_companion_dialog(dialog_key, current_mission)
update_current_objective(current_mission)
ObjectiveType.REACH_POPULATION:
if Globals.population >= current_objective.target_count:
print("Population objective completed!")
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.
_send_companion_dialog(dialog_key, current_mission)
update_current_objective(current_mission)
# IF this is true then objectives are completed
update_mission_ui()
print("Emitting objective progress signal")
objective_progress.emit(current_objective, current_objective.current_count)
# Force UI update after objective progress
if mission_ui:
print("Forcing mission UI update")
mission_ui.update_missions(active_missions)
@ -333,100 +342,95 @@ 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):
return false
var mission = active_missions[mission_id]
for objective in mission.objectives:
if objective.type == objective_type:
return objective.completed
return false
# Function to reset an objective's count to a specific value
func reset_objective_count(objective_type, new_count):
if not current_mission:
print("ERROR: No current mission to reset objective count for")
return
var mission_id = current_mission.id
if not active_missions.has(mission_id):
print("ERROR: Current mission ID " + mission_id + " not found in active_missions!")
return
print("Checking structure against current objective:")
print("- Current objective type: " + str(current_objective.type))
print("- Current objective structure type: " + str(current_objective.structure.type))
print("- Placed structure type: " + str(structure.type))
var mission = active_missions[mission_id]
for objective in mission.objectives:
if objective.type == objective_type:
print("Resetting objective count for type " + str(objective_type) + " from " + str(objective.current_count) + " to " + str(new_count))
objective.current_count = new_count
# Update completion status based on new count
objective.completed = objective.current_count >= objective.target_count
# If newly completed, emit signal
if objective.completed and objective.current_count >= objective.target_count:
objective_completed.emit(objective)
# 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()
# Handle BUILD_STRUCTURE objective type
if current_objective.type == ObjectiveType.BUILD_STRUCTURE:
# For commercial buildings, check if the placed structure is also a commercial building
if current_objective.structure.type == Structure.StructureType.COMMERCIAL_BUILDING:
if structure.type == Structure.StructureType.COMMERCIAL_BUILDING:
print("Commercial building match for store objective")
return true
# For roads, check if the placed structure is a road
elif current_objective.structure.type == Structure.StructureType.ROAD:
if structure.type == Structure.StructureType.ROAD:
print("Road match for road objective")
return true
# For other structures, do exact match
elif current_objective.structure == structure:
print("Exact structure match")
return true
# Emit progress signal for objective
objective_progress.emit(objective, objective.current_count)
# Check if the mission is complete
check_mission_completion(mission_id)
break
func check_mission_completion(mission_id):
if not active_missions.has(mission_id):
return
var mission = active_missions[mission_id]
var all_complete = true
for objective in mission.objectives:
if not objective.completed:
all_complete = false
break
if all_complete:
# All objectives complete, complete the mission
complete_mission(mission_id)
return true
print("No match found")
return false
func update_mission_ui():
if mission_ui:
mission_ui.update_missions(active_missions)
func _on_structure_placed(structure_index, position):
# Get the structure that was placed
if structure_index < 0 or structure_index >= builder.structures.size():
print("ERROR: Invalid structure index: " + str(structure_index))
return
var structure = builder.structures[structure_index]
print("Structure placed: " + structure.model.resource_path)
print("Structure placed: " + structure.model.resource_path + " at position: " + str(position))
# 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 and current_objective:
print("Current objective type: " + str(current_objective.type))
# Handle structure-based objectives
if current_objective.type == ObjectiveType.BUILD_STRUCTURE and is_structure_of_current_mission(structure):
print("Structure matches current objective")
# For roads, check if they are placed next to each other
if structure.type == Structure.StructureType.ROAD:
# Check adjacent positions for roads
var adjacent_roads = 0
var directions = [Vector3.RIGHT * 3, Vector3.LEFT * 3, Vector3.FORWARD * 3, Vector3.BACK * 3]
for direction in directions:
var check_pos = position + direction
# Check if there's a road at this position in the NavRegion3D
var road_name = "Road_" + str(int(check_pos.x)) + "_" + str(int(check_pos.z))
if builder.nav_region and builder.nav_region.has_node(road_name):
adjacent_roads += 1
print("Found adjacent road at: " + str(check_pos))
print("Total adjacent roads: " + str(adjacent_roads))
print("Current objective count: " + str(current_objective.current_count))
# For the first road, always count it
# For subsequent roads, only count if they're adjacent to another road
if adjacent_roads > 0 or current_objective.current_count == 0:
print("Counting road placement")
update_objective_progress(structure)
print("Updated count: " + str(current_objective.current_count))
# Check if objective is complete
if current_objective.current_count >= current_objective.target_count:
print("Objective complete!")
current_objective.completed = true
objective_completed.emit(current_objective)
update_current_objective(current_mission)
else:
print("Road not counted - not adjacent to another road and not first road")
else:
# For non-road structures, update progress normally
print("Updating progress for non-road structure")
update_objective_progress(structure)
elif current_objective.type == ObjectiveType.BUILD_RESIDENTIAL and structure.type == Structure.StructureType.RESIDENTIAL_BUILDING:
# For residential buildings, update progress after construction completes
print("Residential building placed, will update after construction completes")
elif current_objective.type == ObjectiveType.REACH_POPULATION:
# Population objectives are handled by the population_updated signal
print("Population objective - will update when population changes")
else:
print("Structure does not match current objective type")
else:
print("No current mission or objective to check against")
if current_mission:
if structure.type == Structure.StructureType.RESIDENTIAL_BUILDING:
# Note: for mission 3, the objective update happens after construction is complete
@ -826,15 +830,6 @@ func _handle_structure_unlocking(mission):
print("Looking for structure with path: " + item_path)
var found = false
# DEBUG: Print all builder structures for comparison
print("Available builder structures:")
for i in range(builder.structures.size()):
var s = builder.structures[i]
if s.model:
print(str(i) + ": " + s.model.resource_path + " (type: " + str(s.type) + ")")
else:
print(str(i) + ": <no model>")
# Find the structure in builder's structures that matches this path
for structure in builder.structures:
if structure.model:
@ -880,17 +875,6 @@ func _handle_structure_unlocking(mission):
if not found:
print("ERROR: Couldn't find any structure with path: " + item_path)
# 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
# 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
for i in range(builder.structures.size()):
@ -938,24 +922,17 @@ func _handle_structure_unlocking(mission):
structure.unlocked = true
print("Confirmed structure is unlocked: " + structure.model.resource_path)
# If we have no structures explicitly unlocked from mission data,
# show all currently unlocked structures
if unlocked_structures.size() == 0 and builder.structures.size() > 0:
var all_unlocked = []
for structure in builder.structures:
if "unlocked" in structure and structure.unlocked:
all_unlocked.append(structure)
if all_unlocked.size() > 0:
print("No new structures, showing all " + str(all_unlocked.size()) + " unlocked structures")
unlocked_structures = all_unlocked
_show_unlocked_items_panel(unlocked_structures)
else:
print("No structures unlocked, not showing panel")
# Shows a panel with the newly unlocked items
func _show_unlocked_items_panel(unlocked_structures):
# Check if panel is already showing
if is_unlocked_panel_showing:
print("WARNING: Panel is already showing, skipping duplicate show")
return
print("Showing unlocked items panel with " + str(unlocked_structures.size()) + " structures")
# Set panel state to showing - prevents mission starts while panel is visible
@ -1028,6 +1005,11 @@ func _show_unlocked_items_panel(unlocked_structures):
# Make sure the game is unpaused
get_tree().paused = false
# Update the mission UI with the current objective
if mission_ui and current_mission:
print("Updating mission UI after unlocked panel closed")
mission_ui.update_mission_display(current_mission)
# Process any delayed mission starts
_process_delayed_mission_starts()
)
@ -1035,7 +1017,7 @@ func _show_unlocked_items_panel(unlocked_structures):
push_error("Could not load unlocked_items_panel scene")
# Even if we couldn't load the panel, make sure to reset the state
is_unlocked_panel_showing = false
# Public function to show all unlocked structures when requested
func show_unlocked_structures_panel():
if not builder:
@ -1091,26 +1073,45 @@ func _send_companion_dialog(dialog_key, mission):
# Helper function to update the current objective to the next incomplete one
func update_current_objective(mission = null):
print("Updating current objective")
# If no mission was provided, use the current mission
if mission == null:
mission = current_mission
if not mission:
print("ERROR: No mission provided for update_current_objective")
return
print("Mission objectives count: " + str(mission.objectives.size()))
# Look for the first incomplete objective
for objective in mission.objectives:
if not objective.completed:
print("Found incomplete objective: " + str(objective.type))
current_objective = objective
print("Updated current objective: " + str(current_objective.type) + " - " + current_objective.description)
# Force UI update
if mission_ui:
print("Forcing mission UI update after objective change")
mission_ui.update_mission_display(mission)
return
# If all objectives are complete, keep the last one as current and complete the mission
if mission.objectives.size() > 0:
print("All objectives complete")
current_objective = mission.objectives[-1]
print("All objectives complete, keeping last one as current objective")
print("Keeping last objective as current: " + str(current_objective.type))
# Force UI update
if mission_ui:
print("Forcing mission UI update after all objectives complete")
mission_ui.update_mission_display(mission)
# Complete the mission when we've found that all objectives are complete
if mission.id in active_missions:
print("Checking mission completion")
check_mission_completion(mission.id)
# Fallback to force a connection if the normal method doesn't work
@ -1131,5 +1132,95 @@ func _force_learning_companion_connection():
func population_updated(new_population: Variant) -> void:
if current_objective.type == ObjectiveType.REACH_POPULATION:
update_objective_progress()
if current_mission and current_objective and current_objective.type == ObjectiveType.REACH_POPULATION:
print("Population updated: " + str(new_population) + " / " + str(current_objective.target_count))
# Update the current count to match the actual population
current_objective.current_count = new_population
# Check if objective is complete
if current_objective.current_count >= current_objective.target_count:
current_objective.completed = true
objective_completed.emit(current_objective)
update_current_objective(current_mission)
# Emit progress signal
objective_progress.emit(current_objective, current_objective.current_count)
# Update the mission UI
if mission_ui:
mission_ui.update_mission_display(current_mission)
func check_objective_completion(mission_id, objective_type):
if not active_missions.has(mission_id):
return false
var mission = active_missions[mission_id]
for objective in mission.objectives:
if objective.type == objective_type:
return objective.completed
return false
# Function to reset an objective's count to a specific value
func reset_objective_count(objective_type, new_count):
if not current_mission:
print("ERROR: No current mission to reset objective count for")
return
var mission_id = current_mission.id
if not active_missions.has(mission_id):
print("ERROR: Current mission ID " + mission_id + " not found in active_missions!")
return
var mission = active_missions[mission_id]
for objective in mission.objectives:
if objective.type == objective_type:
print("Resetting objective count for type " + str(objective_type) + " from " + str(objective.current_count) + " to " + str(new_count))
objective.current_count = new_count
# Update completion status based on new count
objective.completed = objective.current_count >= objective.target_count
# If newly completed, emit signal
if objective.completed and objective.current_count >= objective.target_count:
objective_completed.emit(objective)
# 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()
# Emit progress signal for objective
objective_progress.emit(objective, objective.current_count)
# Check if the mission is complete
check_mission_completion(mission_id)
break
func check_mission_completion(mission_id):
if not active_missions.has(mission_id):
return
var mission = active_missions[mission_id]
var all_complete = true
for objective in mission.objectives:
if not objective.completed:
all_complete = false
break
if all_complete:
# All objectives complete, complete the mission
complete_mission(mission_id)
return true
return false
func update_mission_ui():
if mission_ui:
mission_ui.update_missions(active_missions)

@ -1 +1 @@
uid://qlxrdpwtysyp
uid://otnxt4e77gmc

Some files were not shown because too many files have changed in this diff Show More