Buildings are now constructed by citizens.

This commit is contained in:
Nekojimi 2025-05-05 11:12:05 +01:00
parent 412736b9d5
commit 78bfcde7a0
22 changed files with 470 additions and 97 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,14 @@
[gd_scene load_steps=7 format=3 uid="uid://b1fnsl3k1mo5c"] [gd_scene load_steps=10 format=3 uid="uid://b1fnsl3k1mo5c"]
[ext_resource type="Script" uid="uid://dvgo3kpjr2mmj" path="res://scripts/building_components/building.gd" id="1_k07no"] [ext_resource type="Script" uid="uid://dvgo3kpjr2mmj" path="res://scripts/building_components/building.gd" id="1_k07no"]
[ext_resource type="Script" uid="uid://bcp3fwf3nds3k" path="res://scripts/nav_obstacle.gd" id="2_0gk2u"] [ext_resource type="Script" uid="uid://bcp3fwf3nds3k" path="res://scripts/nav_obstacle.gd" id="2_0gk2u"]
[ext_resource type="Script" uid="uid://kcdpck5ufgcc" path="res://scripts/item.gd" id="2_fahfx"]
[ext_resource type="Resource" uid="uid://dxlb2ixt3fx7l" path="res://items/ore.tres" id="3_w18nb"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0gk2u"]
[sub_resource type="BoxMesh" id="BoxMesh_cee1v"] [sub_resource type="BoxMesh" id="BoxMesh_cee1v"]
material = SubResource("StandardMaterial3D_0gk2u")
size = Vector3(2, 2, 2) size = Vector3(2, 2, 2)
[sub_resource type="BoxShape3D" id="BoxShape3D_k07no"] [sub_resource type="BoxShape3D" id="BoxShape3D_k07no"]
@ -18,10 +23,13 @@ agent_radius = 0.2
size = Vector2(2, 2) size = Vector2(2, 2)
orientation = 1 orientation = 1
[node name="Building" type="StaticBody3D"] [node name="Building" type="StaticBody3D" groups=["Buildings"]]
collision_layer = 2 collision_layer = 2
collision_mask = 15 collision_mask = 15
script = ExtResource("1_k07no") script = ExtResource("1_k07no")
build_materials_needed = Dictionary[ExtResource("2_fahfx"), int]({
ExtResource("3_w18nb"): 5
})
[node name="MeshInstance3D" type="MeshInstance3D" parent="."] [node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)

View File

@ -1,14 +1,16 @@
[gd_scene load_steps=6 format=3 uid="uid://chd531mdaek3"] [gd_scene load_steps=7 format=3 uid="uid://chd531mdaek3"]
[ext_resource type="PackedScene" uid="uid://b1fnsl3k1mo5c" path="res://objects/buildings/building.tscn" id="1_8r86l"] [ext_resource type="PackedScene" uid="uid://b1fnsl3k1mo5c" path="res://objects/buildings/building.tscn" id="1_8r86l"]
[ext_resource type="Script" uid="uid://6dy54s70qf0x" path="res://scripts/buildings/miner.gd" id="2_k13eg"] [ext_resource type="Script" uid="uid://6dy54s70qf0x" path="res://scripts/buildings/miner.gd" id="2_k13eg"]
[ext_resource type="PackedScene" uid="uid://dx13fyjv0d8st" path="res://assets/blends/Miner.blend" id="2_la0h1"] [ext_resource type="PackedScene" uid="uid://dx13fyjv0d8st" path="res://assets/blends/Miner.blend" id="2_la0h1"]
[ext_resource type="Script" uid="uid://kcdpck5ufgcc" path="res://scripts/item.gd" id="3_8y6s2"]
[ext_resource type="Resource" uid="uid://dxlb2ixt3fx7l" path="res://items/ore.tres" id="3_k13eg"] [ext_resource type="Resource" uid="uid://dxlb2ixt3fx7l" path="res://items/ore.tres" id="3_k13eg"]
[ext_resource type="Script" uid="uid://c4fquatkjmsgu" path="res://scripts/building_components/producer.gd" id="5_65oni"] [ext_resource type="Script" uid="uid://c4fquatkjmsgu" path="res://scripts/building_components/producer.gd" id="5_65oni"]
[node name="Miner" instance=ExtResource("1_8r86l")] [node name="Miner" instance=ExtResource("1_8r86l")]
script = ExtResource("2_k13eg") script = ExtResource("2_k13eg")
mine_period = 1.0 mine_period = 1.0
build_materials_needed = Dictionary[ExtResource("3_8y6s2"), int]({})
can_stack = false can_stack = false
[node name="MeshInstance3D" parent="." index="0"] [node name="MeshInstance3D" parent="." index="0"]

View File

@ -1,14 +1,20 @@
[gd_scene load_steps=10 format=3 uid="uid://c6e1nifka3h4v"] [gd_scene load_steps=12 format=3 uid="uid://c6e1nifka3h4v"]
[ext_resource type="PackedScene" uid="uid://b1fnsl3k1mo5c" path="res://objects/buildings/building.tscn" id="1_hxugg"] [ext_resource type="PackedScene" uid="uid://b1fnsl3k1mo5c" path="res://objects/buildings/building.tscn" id="1_hxugg"]
[ext_resource type="Script" uid="uid://c3bqfgoof2c83" path="res://scripts/buildings/processor.gd" id="2_evfwj"] [ext_resource type="Script" uid="uid://c3bqfgoof2c83" path="res://scripts/buildings/processor.gd" id="2_evfwj"]
[ext_resource type="Script" uid="uid://kcdpck5ufgcc" path="res://scripts/item.gd" id="3_3h7kv"] [ext_resource type="Script" uid="uid://kcdpck5ufgcc" path="res://scripts/item.gd" id="3_dx8de"]
[ext_resource type="Resource" uid="uid://ed64yksg1y6m" path="res://items/bullets.tres" id="4_3h7kv"] [ext_resource type="Resource" uid="uid://ed64yksg1y6m" path="res://items/bullets.tres" id="4_3h7kv"]
[ext_resource type="Script" uid="uid://bshiyw2k3op02" path="res://scripts/building_components/consumer.gd" id="4_dx8de"] [ext_resource type="Script" uid="uid://bshiyw2k3op02" path="res://scripts/building_components/consumer.gd" id="4_dx8de"]
[ext_resource type="Shader" uid="uid://c7ahmb00sly4d" path="res://shaders/hologram.gdshader" id="5_dx8de"]
[ext_resource type="Script" uid="uid://c4fquatkjmsgu" path="res://scripts/building_components/producer.gd" id="5_p3dou"] [ext_resource type="Script" uid="uid://c4fquatkjmsgu" path="res://scripts/building_components/producer.gd" id="5_p3dou"]
[ext_resource type="Resource" uid="uid://dxlb2ixt3fx7l" path="res://items/ore.tres" id="7_dx8de"] [ext_resource type="Resource" uid="uid://dxlb2ixt3fx7l" path="res://items/ore.tres" id="7_dx8de"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_p3dou"]
render_priority = 0
shader = ExtResource("5_dx8de")
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_evfwj"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_evfwj"]
next_pass = SubResource("ShaderMaterial_p3dou")
albedo_color = Color(0.023159, 1, 0, 1) albedo_color = Color(0.023159, 1, 0, 1)
[sub_resource type="BoxMesh" id="BoxMesh_3h7kv"] [sub_resource type="BoxMesh" id="BoxMesh_3h7kv"]
@ -17,7 +23,7 @@ size = Vector3(2, 2, 2)
[node name="Processor" instance=ExtResource("1_hxugg")] [node name="Processor" instance=ExtResource("1_hxugg")]
script = ExtResource("2_evfwj") script = ExtResource("2_evfwj")
ingredients = Dictionary[ExtResource("3_3h7kv"), int]({ ingredients = Dictionary[ExtResource("3_dx8de"), int]({
ExtResource("7_dx8de"): 2 ExtResource("7_dx8de"): 2
}) })
process_time = 1.0 process_time = 1.0
@ -27,7 +33,7 @@ mesh = SubResource("BoxMesh_3h7kv")
[node name="Consumer" type="Node" parent="." index="2"] [node name="Consumer" type="Node" parent="." index="2"]
script = ExtResource("4_dx8de") script = ExtResource("4_dx8de")
accepted_items = Array[ExtResource("3_3h7kv")]([ExtResource("7_dx8de")]) accepted_items = Array[ExtResource("3_dx8de")]([ExtResource("7_dx8de")])
metadata/_custom_type_script = "uid://bshiyw2k3op02" metadata/_custom_type_script = "uid://bshiyw2k3op02"
[node name="Producer" type="Node" parent="." index="3"] [node name="Producer" type="Node" parent="." index="3"]

View File

@ -30,7 +30,7 @@ fire_cooldown_time = 0.2
reload_time = 1.0 reload_time = 1.0
bullet_scene = ExtResource("3_kpsgq") bullet_scene = ExtResource("3_kpsgq")
fire_position = Vector3(0, 2.1, 0) fire_position = Vector3(0, 2.1, 0)
shot_velocity = 20.0 shot_velocity = 50.0
lead_shots = true lead_shots = true
compensate_for_gravity = true compensate_for_gravity = true
use_artillery_firing_solution = false use_artillery_firing_solution = false

View File

@ -105,12 +105,13 @@ size = Vector2(0.1, 0.1)
[node name="Bullet" type="RigidBody3D"] [node name="Bullet" type="RigidBody3D"]
collision_layer = 16 collision_layer = 16
collision_mask = 5 collision_mask = 21
continuous_cd = true continuous_cd = true
contact_monitor = true contact_monitor = true
max_contacts_reported = 1 max_contacts_reported = 1
angular_velocity = Vector3(0, 0, 6.28319)
script = ExtResource("1_rsjgb") script = ExtResource("1_rsjgb")
damage_per_speed = 100.0 damage_per_speed = 2.0
min_damage = 1.0 min_damage = 1.0
lifetime = 5.0 lifetime = 5.0
@ -120,7 +121,7 @@ shape = SubResource("CapsuleShape3D_3ndsa")
[node name="GPUTrail3D" type="GPUParticles3D" parent="."] [node name="GPUTrail3D" type="GPUParticles3D" parent="."]
physics_interpolation_mode = 2 physics_interpolation_mode = 2
transform = Transform3D(0.0233971, -0.0972244, 0, 0.0972244, 0.0233971, 0, 0, 0, 0.1, 0, 0, 0) transform = Transform3D(0.0233971, -0.0972244, 0, 0.0972244, 0.0233971, 0, 0, 0, 0.1, 0, 0.0926043, -0.000724986)
amount = 19 amount = 19
lifetime = 19.0 lifetime = 19.0
explosiveness = 1.0 explosiveness = 1.0

View File

@ -1,10 +1,19 @@
@tool
extends Unit extends Unit
class_name Citizen class_name Citizen
@onready var body: Sprite3D = $Body @onready var body: Sprite3D = $Body
@onready var hands: Sprite3D = $Body/Hands @onready var hands: Sprite3D = $Body/Hands
var task: Task = null
var working: bool = false
enum WorkStatus { NONE, CONSTRUCTING_BUILDING }
var work_status: WorkStatus
var work_building: Building = null
func _ready() -> void:
held_item_meshinstance = $"Body/Hands/Held Item"
func _process(delta: float) -> void: func _process(delta: float) -> void:
super(delta) super(delta)
var camera: Camera3D var camera: Camera3D
@ -19,6 +28,48 @@ func _process(delta: float) -> void:
var facing_right: bool = camera_local_facing.x > 0 var facing_right: bool = camera_local_facing.x > 0
body.frame = 1 if facing_away else 0 body.frame = 1 if facing_away else 0
hands.frame = 1 if facing_away else 0 #hands.frame = 1 if facing_away else 0
body.flip_h = facing_right body.flip_h = facing_right
hands.flip_h = facing_right hands.flip_h = facing_right
if !working and task != null:
working = true
var ok: bool = await task.execute(self)
if !ok:
CitizenManager._instance.add_task(task)
task = null
working = false
if work_status == WorkStatus.CONSTRUCTING_BUILDING:
work_building.build_progress += delta
if work_building.build_state == Building.BuildState.READY:
send_task_update(TaskStatus.DONE)
elif work_building.build_state != Building.BuildState.BUILDING:
send_task_update(TaskStatus.IMPOSSIBLE)
func assign_task(t: Task) -> void:
task = t
func is_idle() -> bool:
return task == null
func put_item_in_building_materials(building: Building) -> bool:
building._add_build_material(held_item)
held_item = null
return true
func put_item_in_building_storage(building: Building) -> bool:
if building.consumer == null:
return false
if !building.consumer.offer_item(held_item):
return false
held_item = null
return true
func build_building(building: Building) -> bool:
work_building = building
work_status = WorkStatus.CONSTRUCTING_BUILDING
var ok: bool = await wait_for_task_update() == TaskStatus.DONE
work_building = null
work_status = WorkStatus.NONE
return ok

View File

@ -4,7 +4,7 @@
[ext_resource type="Script" uid="uid://b7xficxq807qd" path="res://objects/units/citizen.gd" id="2_dv62s"] [ext_resource type="Script" uid="uid://b7xficxq807qd" path="res://objects/units/citizen.gd" id="2_dv62s"]
[ext_resource type="Texture2D" uid="uid://brjswv5ryy8om" path="res://assets/images/lilguy.png" id="3_pedvu"] [ext_resource type="Texture2D" uid="uid://brjswv5ryy8om" path="res://assets/images/lilguy.png" id="3_pedvu"]
[ext_resource type="Texture2D" uid="uid://cpov32m0nxjvh" path="res://assets/images/lilguy_arms.png" id="4_i1unn"] [ext_resource type="Texture2D" uid="uid://cpov32m0nxjvh" path="res://assets/images/lilguy_arms.png" id="4_i1unn"]
[ext_resource type="ArrayMesh" uid="uid://gf0q5hs4wjva" path="res://assets/models/Ore.obj" id="5_dds8f"] [ext_resource type="ArrayMesh" uid="uid://c6yj8uwsgqxv0" path="res://assets/models/Ingot.obj" id="5_dv62s"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_vpcy6"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_vpcy6"]
albedo_texture = ExtResource("3_pedvu") albedo_texture = ExtResource("3_pedvu")
@ -13,13 +13,14 @@ billboard_mode = 2
[sub_resource type="QuadMesh" id="QuadMesh_5xmhx"] [sub_resource type="QuadMesh" id="QuadMesh_5xmhx"]
material = SubResource("StandardMaterial3D_vpcy6") material = SubResource("StandardMaterial3D_vpcy6")
[node name="Citizen" instance=ExtResource("1_6046h")] [node name="Citizen" groups=["Citizens"] instance=ExtResource("1_6046h")]
collision_layer = 8 collision_layer = 8
collision_mask = 5 collision_mask = 5
script = ExtResource("2_dv62s") script = ExtResource("2_dv62s")
stuck_time = 5.0
[node name="NavigationAgent3D" parent="." index="0"] [node name="NavigationAgent3D" parent="." index="0"]
target_position = Vector3(21.7818, 1, 12.7739) target_position = Vector3(18.6013, 1, 14.148)
velocity = Vector3(0.811107, 0, -0.811107) velocity = Vector3(0.811107, 0, -0.811107)
[node name="MeshInstance3D" parent="." index="2"] [node name="MeshInstance3D" parent="." index="2"]
@ -39,7 +40,7 @@ texture = ExtResource("3_pedvu")
hframes = 2 hframes = 2
[node name="Hands" type="Sprite3D" parent="Body" index="0"] [node name="Hands" type="Sprite3D" parent="Body" index="0"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.01) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.0522564)
billboard = 2 billboard = 2
shaded = true shaded = true
alpha_cut = 1 alpha_cut = 1
@ -47,6 +48,7 @@ texture_filter = 0
texture = ExtResource("4_i1unn") texture = ExtResource("4_i1unn")
[node name="Held Item" type="MeshInstance3D" parent="Body/Hands" index="0"] [node name="Held Item" type="MeshInstance3D" parent="Body/Hands" index="0"]
transform = Transform3D(0.17, 0, 0, 0, 0.17, 0, 0, 0, 0.17, 5.96046e-08, -0.0580646, -0.0773078) transform = Transform3D(0.17, 0, 0, 0, 0.17, 0, 0, 0, 0.17, 5.96046e-08, -0.0189633, -0.0170195)
mesh = ExtResource("5_dds8f") visible = false
mesh = ExtResource("5_dv62s")
skeleton = NodePath("../../..") skeleton = NodePath("../../..")

View File

@ -31,7 +31,7 @@ max_speed = 5.0
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."] [node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
path_desired_distance = 0.5 path_desired_distance = 0.5
target_desired_distance = 0.5 target_desired_distance = 2.0
path_max_distance = 1.01 path_max_distance = 1.01
avoidance_enabled = true avoidance_enabled = true
radius = 1.0 radius = 1.0

View File

@ -47,6 +47,7 @@ gdscript/warnings/unsafe_property_access=1
gdscript/warnings/unsafe_method_access=1 gdscript/warnings/unsafe_method_access=1
gdscript/warnings/unsafe_cast=1 gdscript/warnings/unsafe_cast=1
gdscript/warnings/unsafe_call_argument=2 gdscript/warnings/unsafe_call_argument=2
gdscript/warnings/return_value_discarded=1
gdscript/warnings/static_called_on_instance=2 gdscript/warnings/static_called_on_instance=2
gdscript/warnings/missing_tool=2 gdscript/warnings/missing_tool=2
gdscript/warnings/assert_always_false=2 gdscript/warnings/assert_always_false=2
@ -72,6 +73,8 @@ enabled=PackedStringArray("res://addons/GPUTrail/plugin.cfg", "res://addons/Path
[global_group] [global_group]
Enemies="" Enemies=""
Citizens=""
Buildings=""
[input] [input]
@ -121,6 +124,13 @@ building_place={
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
] ]
} }
building_cancel={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":false,"double_click":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
]
}
[layer_names] [layer_names]

57
scripts/CitizenManager.gd Normal file
View File

@ -0,0 +1,57 @@
extends Node
class_name CitizenManager
@export var task_assignment_rate: float = 100
var task_assignment_budget: float = 0
var task_queue: Array[Task] = []
static var _instance: CitizenManager = null
#signal task_queue_updated(task_queue: Array[Task])
func _init() -> void:
if _instance == null:
_instance = self
else:
assert(false, "A second CitizenManager was created you fucking moron")
func _process(delta: float) -> void:
task_assignment_budget += task_assignment_rate * delta
if task_assignment_budget >= task_assignment_rate:
task_assignment_budget = task_assignment_rate
var idle_workers: Array[Citizen] = []
for worker in get_children():
if worker is Citizen:
if worker.is_idle():
idle_workers.append(worker)
#idle_workers.shuffle()
while !idle_workers.is_empty() and !task_queue.is_empty() and task_assignment_budget >= 1:
var task: Task = task_queue.pop_front()
task_assignment_budget -= 1
if task.is_ready_to_start():
# TODO: this currently assigns to the first worker found
var worker: Citizen = idle_workers.pop_front()
worker.assign_task(task)
else:
add_task(task)
func remove_task(task: Task):
var idx: int = task_queue.find(task)
if idx >= 0:
task_queue.remove_at(idx)
func add_task(task: Task):
print("Task added to queue: %s" % task.name)
var idx: int = task_queue.bsearch_custom(task, sort_tasks, false)
task_queue.insert(idx, task)
func sort_tasks(a: Task,b: Task) -> bool:
return a.priority > b.priority
const TASK_PRIORITY_ANYTIME = 0
const TASK_PRIORITY_BY_WAVE_START = 10
const TASK_PRIORITY_ASAP = 20

View File

@ -0,0 +1 @@
uid://ckf7i6ig4twnq

View File

@ -1,6 +1,10 @@
extends Node3D extends Node3D
class_name Building class_name Building
@export_group("Construction", "build")
@export var build_time: float = 5.0 ## Worker-time taken for 1 citizen to finish the building, in citizen-seconds.
@export var build_materials_needed: Dictionary[Item, int] = {}
@export_group("Defence") @export_group("Defence")
@export var max_hp: int = 100 ## Amount of damage this building can sustain before being destroyed. @export var max_hp: int = 100 ## Amount of damage this building can sustain before being destroyed.
@ -31,13 +35,35 @@ const PLACEMENT_PART_ADDED: int = 0b0100
const PLACEMENT_COMPLETED: int = 0b1000 const PLACEMENT_COMPLETED: int = 0b1000
var hp: int = max_hp var hp: int = max_hp
var build_progress: float = 0.0:
set(val):
build_progress = val
if build_progress >= build_time:
build_state = BuildState.READY
var build_percent: float = build_progress / build_time
set_visual_build_progress(build_percent)
var stacked_buildings: Array[Building] = [] var stacked_buildings: Array[Building] = []
var build_state: BuildState = BuildState.READY: var build_state: BuildState = BuildState.READY:
set(state): set(state):
build_state = state if state != build_state:
functional_changed.emit(is_functional()) build_state = state
functional_changed.emit(is_functional())
if build_state == BuildState.BUILDING:
for item in build_materials_needed.keys():
for i in range(build_materials_needed.get(item)):
var task: Task.FetchItemTask = Task.FetchItemTask.new()
task.building = self
task.item = item
CitizenManager._instance.add_task(task)
check_buildable()
set_visual_build_progress(0.0)
elif build_state == BuildState.READY:
set_visual_build_progress(1.0)
var build_materials_used: Dictionary[Item, int] = {}
func _ready() -> void: func _ready() -> void:
if nav_obstacle != null: if nav_obstacle != null:
@ -79,7 +105,7 @@ func _start_placement() -> void:
build_state = BuildState.UNPLACED build_state = BuildState.UNPLACED
func _end_placement() -> void: func _end_placement() -> void:
build_state = BuildState.READY build_state = BuildState.BUILDING
func _placement_select_building(building: Building, confirmed: bool) -> int: func _placement_select_building(building: Building, confirmed: bool) -> int:
while !building.stacked_buildings.is_empty(): while !building.stacked_buildings.is_empty():
@ -110,3 +136,26 @@ func _placement_select_position(pos: Vector3, confirmed: bool) -> int:
ret |= PLACEMENT_COMPLETED ret |= PLACEMENT_COMPLETED
return ret return ret
func check_buildable() -> void:
if can_build():
var task: Task.BuildTask = Task.BuildTask.new()
task.building = self
CitizenManager._instance.add_task(task)
func _add_build_material(item: Item) -> void:
build_materials_used.set(item, build_materials_used.get(item, 0) + 1)
check_buildable()
func can_build() -> bool:
for item in build_materials_needed.keys():
if build_materials_used.get(item,0) < build_materials_needed.get(item,0):
return false
return true
func set_visual_build_progress(ratio: float) -> void:
var geometries: Array[Node] = find_children("", "GeometryInstance3D", true)
for node in geometries:
var geometry: GeometryInstance3D = node as GeometryInstance3D
var opacity: float = 0.5 + (ratio * 0.5)
geometry.transparency = 1.0 - opacity

View File

@ -10,6 +10,18 @@ signal item_added(item: Item)
var storage_total: int = 0 var storage_total: int = 0
var storage: Dictionary[Item, int] = {} var storage: Dictionary[Item, int] = {}
class ItemRequest:
var item: Item
var timeout: float
var requests: Array[ItemRequest] = []
signal requested_item_received(request: ItemRequest)
func _process(delta: float) -> void:
for request in requests:
request.timeout -= delta
if request.timeout <= 0:
cancel_request(request)
func can_accept_item(item: Item) -> bool: func can_accept_item(item: Item) -> bool:
return ((storage_total < storage_size) or void_excess_items) \ return ((storage_total < storage_size) or void_excess_items) \
and (accepted_items.has(item) or accepted_items.is_empty()) \ and (accepted_items.has(item) or accepted_items.is_empty()) \
@ -23,10 +35,19 @@ func offer_item(item: Item) -> bool:
var old_count: int = storage.get(item, 0) var old_count: int = storage.get(item, 0)
storage.set(item, old_count + 1) storage.set(item, old_count + 1)
storage_total += 1 storage_total += 1
#print("Added %s to %s storage; previously had %d, new total %d" % [item.name, get_parent().name, old_count, storage_total]) if !check_requested_items(item):
item_added.emit(item) item_added.emit(item)
return true return true
func check_requested_items(new_item: Item):
for request in requests:
if new_item == request.item:
requested_item_received.emit(request)
break
func cancel_request(request: ItemRequest):
requested_item_received.emit(request)
func check_storage_for_item(item: Item) -> bool: func check_storage_for_item(item: Item) -> bool:
return check_storage_for_items({item:1}) return check_storage_for_items({item:1})
@ -47,6 +68,19 @@ func take_any_item_from_storage() -> Item:
func take_item_from_storage(item: Item) -> bool: func take_item_from_storage(item: Item) -> bool:
return take_items_from_storage({item: 1}) return take_items_from_storage({item: 1})
## Waits for an item to be available at this storage, or a timeout to be reached.
func wait_for_item(item: Item, timeout: float) -> bool:
if check_storage_for_item(item):
return true
var request: ItemRequest = ItemRequest.new()
request.item = item
request.timeout = timeout
requests.append(request)
var finished_request: ItemRequest = null
while finished_request != request:
finished_request = await requested_item_received
return check_storage_for_item(item)
func take_items_from_storage(items: Dictionary[Item, int]) -> bool: func take_items_from_storage(items: Dictionary[Item, int]) -> bool:
if !check_storage_for_items(items): if !check_storage_for_items(items):
return false return false

View File

@ -14,9 +14,13 @@ func start_placement(scene: PackedScene) -> void:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if placing_building == null: if placing_building == null:
return return
if event is InputEventMouseButton and event.is_action("building_place"): if event is InputEventMouseButton:
placement_mouse_input((event as InputEventMouseButton).global_position, true) if event.is_action("building_place"):
get_viewport().set_input_as_handled() placement_mouse_input((event as InputEventMouseButton).global_position, true)
get_viewport().set_input_as_handled()
elif event.is_action("building_cancel"):
placement_cancel()
get_viewport().set_input_as_handled()
elif event is InputEventMouseMotion: elif event is InputEventMouseMotion:
placement_mouse_input((event as InputEventMouseMotion).global_position, false) placement_mouse_input((event as InputEventMouseMotion).global_position, false)
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
@ -41,3 +45,7 @@ func placement_mouse_input(screen_position: Vector2, confirmed: bool) -> void:
if placment_feedback & Building.PLACEMENT_COMPLETED: if placment_feedback & Building.PLACEMENT_COMPLETED:
placing_building._end_placement() placing_building._end_placement()
placing_building = null placing_building = null
func placement_cancel() -> void:
placing_building.queue_free()
placing_building = null

View File

@ -30,6 +30,12 @@ var output_building: Building = null:
var waypoints: Array[Vector3] = [] var waypoints: Array[Vector3] = []
var editing_waypoint: int = 0 var editing_waypoint: int = 0
func _exit_tree() -> void:
if input_building != null:
input_building.producer.consumers.erase(consumer)
if output_building != null:
producer.consumers.erase(output_building.consumer)
func _placement_select_building(building: Building, confirmed: bool) -> int: func _placement_select_building(building: Building, confirmed: bool) -> int:
var ret: int = 0 var ret: int = 0
if input_building == null: if input_building == null:
@ -95,7 +101,7 @@ func _ready() -> void:
path.curve = path.curve.duplicate() path.curve = path.curve.duplicate()
func _process(delta: float) -> void: func _process(delta: float) -> void:
belt_moving = check_items_at_end() belt_moving = is_functional() and check_items_at_end()
consumer.enabled = belt_moving and (closest_item >= spacing) consumer.enabled = belt_moving and (closest_item >= spacing)
var movement: float = (delta * speed) var movement: float = (delta * speed)

View File

@ -87,14 +87,14 @@ func fire_at_target() -> void:
if lead_shots: if lead_shots:
aim_target += current_target.linear_velocity * shot_time aim_target += current_target.linear_velocity * shot_time
target_range = bullet_pos.distance_to(aim_target) target_range = bullet_pos.distance_to(aim_target)
DebugDraw3D.draw_sphere(aim_target, 0.25, Color.RED, shot_time) #DebugDraw3D.draw_sphere(aim_target, 0.25, Color.RED, shot_time)
var fire_direction: Vector3 var fire_direction: Vector3
if compensate_for_gravity: if compensate_for_gravity:
var elevation: float = find_fire_angle(target_range, aim_target.y - bullet_pos.y, use_artillery_firing_solution) var elevation: float = find_fire_angle(target_range, (aim_target.y - bullet_pos.y), use_artillery_firing_solution)
var azimuth: float = Vector3.FORWARD.signed_angle_to(aim_target-global_position, Vector3.UP) var azimuth: float = Vector3.FORWARD.signed_angle_to((aim_target-bullet_pos).slide(Vector3.UP), Vector3.UP)
print("Shot params: θ=%d φ=%d" % [rad_to_deg(elevation), rad_to_deg(azimuth)]) print("Shot params: θ=%d φ=%d" % [rad_to_deg(elevation), rad_to_deg(azimuth)])
if elevation < -PI or elevation > PI: if elevation < -PI or elevation > PI or is_nan(elevation):
print("wtf?? θ=%d" % rad_to_deg(elevation)) print("wtf?? θ=%d" % rad_to_deg(elevation))
return return
fire_direction = Vector3.FORWARD fire_direction = Vector3.FORWARD
@ -103,12 +103,12 @@ func fire_at_target() -> void:
else: else:
fire_direction = (aim_target - bullet_pos).normalized() fire_direction = (aim_target - bullet_pos).normalized()
DebugDraw3D.draw_arrow_ray(bullet_pos, fire_direction, target_range, Color.RED, 0.5, true, shot_time) #DebugDraw3D.draw_arrow_ray(bullet_pos, fire_direction, target_range, Color.RED, 0.5, true, shot_time)
bullet.look_at(bullet_pos+fire_direction) bullet.look_at(bullet_pos+fire_direction)
bullet.linear_velocity = (-bullet.global_basis.z) * shot_velocity bullet.linear_velocity = (-bullet.global_basis.z * shot_velocity)
#bullet.apply_impulse((aim_target - bullet.global_position).normalized()*shot_impulse) #bullet.apply_impulse((aim_target - bullet.global_position).normalized()*shot_impulse)
bullet.angular_velocity = (bullet.global_basis * Vector3(0,0,-10)) bullet.angular_velocity = (bullet.global_basis * Vector3(0,0,deg_to_rad(1080)))
current_target = null current_target = null
ammo -= 1 ammo -= 1
if ammo <= 0: if ammo <= 0:

View File

@ -2,3 +2,6 @@ extends Unit
class_name Enemy class_name Enemy
var sighted: bool = true var sighted: bool = true
func _ready() -> void:
go_to_destination(Vector3(17,1,15))

View File

@ -2,6 +2,7 @@ extends Node3D
@export var spawn_scene: PackedScene = null @export var spawn_scene: PackedScene = null
@export var spawn_time: float = 5.0 @export var spawn_time: float = 5.0
@export var enabled: bool = true
var spawn_timer: float = 0.0 var spawn_timer: float = 0.0

83
scripts/task.gd Normal file
View File

@ -0,0 +1,83 @@
extends RefCounted
class_name Task
var name: String
#var source: TaskManager
var solicitation_time: float = 1.0
var priority: int = TASK_PRIORITY_ANYTIME
var min_worker_count: int = 1
var max_worker_count: int = 1
var potential_workers: Dictionary[Citizen, float] = {}
var workers: Array[Citizen] = []
const FLOAT64_MAX: float = 2.0**1023 * (2 - 2.0**-52)
const TASK_PRIORITY_ANYTIME = 0
const TASK_PRIORITY_BY_WAVE_START = 10
const TASK_PRIORITY_ASAP = 20
func is_ready_to_start() -> bool:
return true
func get_location() -> Vector3:
return Vector3()
func run() -> void:
for worker in workers:
worker.assign_task(self)
func execute(worker: Citizen) -> bool:
return false
func interrupt(worker: Citizen) -> void:
workers.erase(worker)
if workers.size() < min_worker_count:
cancel()
func cancel() -> void:
pass
static func sort_tasks(a: Task, b: Task) -> bool:
return a.priority > b.priority
class BuildTask extends Task:
var building: Building
func _init() -> void:
priority = 1
func get_task_name() -> String:
return "Build " + building.name
func is_ready_to_start() -> bool:
return super() and building.can_build()
func get_location() -> Vector3:
return building.global_position
func execute(worker: Citizen) -> bool:
if !await worker.go_to_destination(building.position): return false
if !await worker.build_building(building): return false
return true
class FetchItemTask extends Task:
var building: Building
var item: Item
func get_task_name() -> String:
return "Transfer " + item.name + " to " + building.name
func execute(worker: Citizen) -> bool:
# find the item from nearby buildings
# TODO: also look for items on the floor?
var storage_building: Building = null
var closest: float = FLOAT64_MAX
for building2 in worker.get_tree().get_nodes_in_group("Buildings"):
if building2 is not Building:
continue
if building2.consumer == null:
continue
if building2.consumer.check_storage_for_item(item):
var distance_sqr: float = building2.global_position.distance_squared_to(worker.position)
if distance_sqr < closest:
storage_building = building2
closest = distance_sqr
if storage_building == null: return false
# go to the storage
if !await worker.go_to_destination(storage_building.global_position): return false
# pick up the item
if !await worker.take_item_from_building(item, storage_building): return false
# carry the item to the construction site
if !await worker.go_to_destination(building.global_position): return false
# store the item in the construction site
if !await worker.put_item_in_building_materials(building): return false
return true

1
scripts/task.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://cc3hwuckhboi1

View File

@ -19,12 +19,35 @@ var avoidance_velocity: Vector3 = Vector3()
var avoidance_timeout: float = 0.0 var avoidance_timeout: float = 0.0
var last_distance_to_target: float = 0.0 var last_distance_to_target: float = 0.0
var stuck_timer: float = 0.0 var stuck_timer: float = 0.0
var move_target: Vector3 = Vector3(16, 1, 13) var moving: bool = false
var move_target: Vector3 = Vector3(16, 1, 13):
set(target):
move_target = target
nav_agent_3d.target_position = move_target
var move_radius: float = 5.0 var move_radius: float = 5.0
var held_item: Item = null:
set(val):
held_item = val
if held_item_meshinstance != null:
if held_item != null:
held_item_meshinstance.mesh = held_item.model
held_item_meshinstance.visible = (held_item != null)
@onready var shapecast_3d: ShapeCast3D = $ShapeCast3D @onready var shapecast_3d: ShapeCast3D = $ShapeCast3D
@onready var nav_agent_3d: NavigationAgent3D = $NavigationAgent3D @onready var nav_agent_3d: NavigationAgent3D = $NavigationAgent3D
@onready var label_3d: Label3D = $Label3D @onready var label_3d: Label3D = $Label3D
@onready var held_item_meshinstance: MeshInstance3D = null
var action_timeout: float = 0.0
enum TaskStatus {
INTERRUPTED,
TIMED_OUT,
IMPOSSIBLE,
DONE,
}
signal task_updated(task_status: TaskStatus)
func _ready() -> void: func _ready() -> void:
nav_agent_3d.connect("velocity_computed", avoidance_velocity_computed) nav_agent_3d.connect("velocity_computed", avoidance_velocity_computed)
@ -38,37 +61,45 @@ func avoidance_velocity_computed(velocity: Vector3) -> void:
func _process(delta: float) -> void: func _process(delta: float) -> void:
label_3d.text = "HP: %d" % hp label_3d.text = "HP: %d" % hp
if nav_agent_3d.is_target_reached() \ if action_timeout > 0:
or nav_agent_3d.target_position.is_zero_approx() \ action_timeout -= delta
or !nav_agent_3d.is_target_reachable(): if action_timeout <= 0:
#target_velocity = Vector3() task_updated.emit(TaskStatus.TIMED_OUT)
nav_agent_3d.target_position = move_target + Vector3(randfn(0, move_radius), 0, randfn(0, move_radius))
#nav_agent_3d.target_position = NavigationServer3D.map_get_random_point(NavigationServer3D.get_maps()[0], 1, true) if moving:
last_distance_to_target = nav_agent_3d.distance_to_target() if nav_agent_3d.is_target_reached() \
else: or nav_agent_3d.target_position.is_zero_approx() \
if shapecast_3d.is_colliding(): or !nav_agent_3d.is_target_reachable():
var distance_to_target: float = nav_agent_3d.distance_to_target() moving = false
var progress_rate: float = (last_distance_to_target - distance_to_target) / delta task_updated.emit(TaskStatus.DONE)
last_distance_to_target = distance_to_target target_velocity = Vector3()
if progress_rate < minimum_progress_rate: #nav_agent_3d.target_position = move_target + Vector3(randfn(0, move_radius), 0, randfn(0, move_radius))
stuck_timer += delta #nav_agent_3d.target_position = NavigationServer3D.map_get_random_point(NavigationServer3D.get_maps()[0], 1, true)
if stuck_timer >= stuck_time: last_distance_to_target = nav_agent_3d.distance_to_target()
unstuck() else:
else: var next_point: Vector3 = nav_agent_3d.get_next_path_position()
label_3d.modulate = Color.WHITE if shapecast_3d.is_colliding():
stuck_timer = 0 var distance_to_target: float = global_position.distance_to(next_point)
if global_position.y <= -10: var progress_rate: float = (last_distance_to_target - distance_to_target) / delta
unstuck() last_distance_to_target = distance_to_target
#DebugDraw3D.draw_sphere(nav_agent_3d.target_position, 0.5, Color.RED) if progress_rate < minimum_progress_rate:
var next_point: Vector3 = nav_agent_3d.get_next_path_position() stuck_timer += delta
#DebugDraw3D.draw_sphere(next_point, 0.1, Color.YELLOW) if stuck_timer >= stuck_time:
var direction: Vector3 = (next_point - global_position).normalized() unstuck()
#basis = Basis.looking_at(direction) else:
#DebugDraw3D.draw_line(global_position, global_position + linear_velocity, Color.BLUE) label_3d.modulate = Color.WHITE
target_velocity = direction * max_speed stuck_timer = 0
nav_agent_3d.velocity = target_velocity if global_position.y <= -10:
#DebugDraw3D.draw_line(global_position, global_positiaon + target_velocity, Color.MAGENTA) unstuck()
#DebugDraw3D.draw_text(global_position + Vector3(0,1,0), "%f" % nav_agent_3d.distance_to_target()) DebugDraw3D.draw_sphere(nav_agent_3d.target_position, 0.5, Color.RED)
#DebugDraw3D.draw_sphere(next_point, 0.1, Color.YELLOW)
var direction: Vector3 = (next_point - global_position).normalized()
#basis = Basis.looking_at(direction)
#DebugDraw3D.draw_line(global_position, global_position + linear_velocity, Color.BLUE)
target_velocity = direction * max_speed
nav_agent_3d.velocity = target_velocity
#DebugDraw3D.draw_line(global_position, global_positiaon + target_velocity, Color.MAGENTA)
#DebugDraw3D.draw_text(global_position + Vector3(0,1,0), "%f" % nav_agent_3d.distance_to_target())
func unstuck() -> void: func unstuck() -> void:
# teleport to next path point # teleport to next path point
@ -101,3 +132,29 @@ func _physics_process(delta: float) -> void:
apply_torque(Vector3(0,force_direction.normalized().signed_angle_to(-global_basis.z, Vector3.DOWN) * rotation_torque,0)) apply_torque(Vector3(0,force_direction.normalized().signed_angle_to(-global_basis.z, Vector3.DOWN) * rotation_torque,0))
#DebugDraw3D.draw_line(global_position, global_position - global_basis.z, Color.BLUE) #DebugDraw3D.draw_line(global_position, global_position - global_basis.z, Color.BLUE)
#DebugDraw3D.draw_line(global_position, global_position + force_direction, Color.RED) #DebugDraw3D.draw_line(global_position, global_position + force_direction, Color.RED)
func send_task_update(task_status: TaskStatus) -> void:
task_updated.emit(task_status)
func wait_for_task_update() -> TaskStatus:
var status_received: TaskStatus = await task_updated;
return status_received
func go_to_destination(destination: Vector3) -> bool:
move_target = destination
moving = true
return (await wait_for_task_update()) == TaskStatus.DONE
func take_item_from_building(item: Item, building: Building) -> bool:
if held_item != null:
return false
if building.consumer == null:
return false
if await building.consumer.wait_for_item(item, 3.0):
if !building.consumer.take_item_from_storage(item):
return false
held_item = item
return true
func drop_item() -> bool:
return false