Compare commits
10 Commits
fbe3487b56
...
5e171ae63a
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e171ae63a | |||
| b5e2493c2a | |||
| eef2068747 | |||
| 052b36a4e7 | |||
| 8a85c6fbba | |||
| bdc1f88b6f | |||
| 852d10f7ae | |||
| d185dcacf3 | |||
| 4fb1cfabc1 | |||
| 60c72c558d |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,41 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://f72a3xfidpms"
|
||||||
|
path.s3tc="res://.godot/imported/colormap.png-08a7d61e658f89289085c66cadb1bc2d.s3tc.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/foliage/Textures/colormap.png"
|
||||||
|
dest_files=["res://.godot/imported/colormap.png-08a7d61e658f89289085c66cadb1bc2d.s3tc.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
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/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
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.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://dyu3r0cl0judq"
|
||||||
|
path="res://.godot/imported/tree-high.glb-7b56684fc38d48b551f6a2589ecabd53.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/foliage/tree-high.glb"
|
||||||
|
dest_files=["res://.godot/imported/tree-high.glb-7b56684fc38d48b551f6a2589ecabd53.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
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=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://q0w3en4bfk42"
|
||||||
|
path="res://.godot/imported/tree.glb-5e87a640dbd9ffc2053ff1705fe52b99.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/foliage/tree.glb"
|
||||||
|
dest_files=["res://.godot/imported/tree.glb-5e87a640dbd9ffc2053ff1705fe52b99.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
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=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
[gd_scene load_steps=15 format=3 uid="uid://rs3hv73svbpa"]
|
[gd_scene load_steps=23 format=3 uid="uid://rs3hv73svbpa"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://4mg67crlim53" path="res://player/player.gd" id="1_h2yge"]
|
[ext_resource type="Script" uid="uid://4mg67crlim53" path="res://player/player.gd" id="1_h2yge"]
|
||||||
[ext_resource type="Script" uid="uid://ii8ai801jur2" path="res://player/camera_rig.gd" id="2_1bvp3"]
|
[ext_resource type="Script" uid="uid://ii8ai801jur2" path="res://player/camera_rig.gd" id="2_1bvp3"]
|
||||||
[ext_resource type="Script" uid="uid://dbb25ttlyogqr" path="res://simulation/SimulationManager.gd" id="3_lquwl"]
|
[ext_resource type="Script" uid="uid://dbb25ttlyogqr" path="res://simulation/SimulationManager.gd" id="3_lquwl"]
|
||||||
[ext_resource type="Script" uid="uid://c1f3b26n4yntf" path="res://world/world_view_manager.gd" id="4_1bvp3"]
|
[ext_resource type="Script" uid="uid://c1f3b26n4yntf" path="res://world/world_view_manager.gd" id="4_1bvp3"]
|
||||||
[ext_resource type="PackedScene" uid="uid://dhxxyprqflotq" path="res://player/npc/NpcVisual.tscn" id="5_lquwl"]
|
[ext_resource type="PackedScene" uid="uid://dhxxyprqflotq" path="res://player/npc/NpcVisual.tscn" id="5_lquwl"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://q0w3en4bfk42" path="res://assets/foliage/tree.glb" id="6_5vw27"]
|
||||||
[ext_resource type="Script" uid="uid://cjvbgqx8rao0t" path="res://world/ui/ui.gd" id="6_7mycd"]
|
[ext_resource type="Script" uid="uid://cjvbgqx8rao0t" path="res://world/ui/ui.gd" id="6_7mycd"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dyu3r0cl0judq" path="res://assets/foliage/tree-high.glb" id="7_kek77"]
|
||||||
|
|
||||||
[sub_resource type="NavigationMesh" id="NavigationMesh_lquwl"]
|
[sub_resource type="NavigationMesh" id="NavigationMesh_lquwl"]
|
||||||
vertices = PackedVector3Array(-14.5, 0.5, 0, -1, 0.5, 0, -1, 0.5, -0.75, 0, 0.5, -1, 0, 0.5, -14.5, -14.5, 0.5, -14.5, 0.75, 0.5, -1, 0.75, 0.75, -0.5, 14.5, 0.5, -0.5, 14.5, 0.5, -14.5, 0.75, 0.75, 0, 0, 1, 0, 0, 0.75, 0.75, -0.5, 0.75, 0.75, -0.5, 0.5, 14.5, 14.5, 0.5, 14.5, -1, 0.5, 0.75, -14.5, 0.5, 14.5)
|
vertices = PackedVector3Array(-14.5, 0.5, 0, -1, 0.5, 0, -1, 0.5, -0.75, 0, 0.5, -1, 0, 0.5, -14.5, -14.5, 0.5, -14.5, 0.75, 0.5, -1, 0.75, 0.75, -0.5, 14.5, 0.5, -0.5, 14.5, 0.5, -14.5, 0.75, 0.75, 0, 0, 1, 0, 0, 0.75, 0.75, -0.5, 0.75, 0.75, -0.5, 0.5, 14.5, 14.5, 0.5, 14.5, -1, 0.5, 0.75, -14.5, 0.5, 14.5)
|
||||||
@@ -34,6 +36,23 @@ ambient_light_source = 2
|
|||||||
ambient_light_color = Color(1, 1, 1, 1)
|
ambient_light_color = Color(1, 1, 1, 1)
|
||||||
ambient_light_energy = 0.5
|
ambient_light_energy = 0.5
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_5vw27"]
|
||||||
|
albedo_color = Color(0.5328383, 0.4215183, 0.18649894, 1)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="BoxMesh_5vw27"]
|
||||||
|
material = SubResource("StandardMaterial3D_5vw27")
|
||||||
|
|
||||||
|
[sub_resource type="CylinderMesh" id="CylinderMesh_5vw27"]
|
||||||
|
top_radius = 1.129
|
||||||
|
height = 5.774
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="BoxMesh_kek77"]
|
||||||
|
|
||||||
|
[sub_resource type="PrismMesh" id="PrismMesh_5vw27"]
|
||||||
|
size = Vector3(3, 5, 2)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="BoxMesh_4c57u"]
|
||||||
|
|
||||||
[node name="Main" type="Node3D"]
|
[node name="Main" type="Node3D"]
|
||||||
|
|
||||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||||
@@ -57,10 +76,17 @@ skeleton = NodePath("../../..")
|
|||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.18380833, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.18380833, 0)
|
||||||
shape = SubResource("BoxShape3D_lquwl")
|
shape = SubResource("BoxShape3D_lquwl")
|
||||||
|
|
||||||
[node name="Player" type="CharacterBody3D" parent="." node_paths=PackedStringArray("camera_rig")]
|
[node name="Player" type="CharacterBody3D" parent="." node_paths=PackedStringArray("camera_rig", "simulation_manager", "farm_zone", "forest_zone", "food_zone", "guard_zone", "study_zone")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0)
|
||||||
script = ExtResource("1_h2yge")
|
script = ExtResource("1_h2yge")
|
||||||
camera_rig = NodePath("../CameraRig")
|
camera_rig = NodePath("../CameraRig")
|
||||||
|
simulation_manager = NodePath("../SimulationManager")
|
||||||
|
farm_zone = NodePath("../TaskZone/FarmZone")
|
||||||
|
forest_zone = NodePath("../TaskZone/ForestZone")
|
||||||
|
food_zone = NodePath("../TaskZone/FoodZone")
|
||||||
|
guard_zone = NodePath("../TaskZone/GuardZone")
|
||||||
|
study_zone = NodePath("../TaskZone/StudyZone")
|
||||||
|
stomach_capacity_for_food = 10
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
|
||||||
@@ -113,21 +139,58 @@ environment = SubResource("Environment_o5qli")
|
|||||||
[node name="FarmZone" type="Marker3D" parent="TaskZone"]
|
[node name="FarmZone" type="Marker3D" parent="TaskZone"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8, 0, -6)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8, 0, -6)
|
||||||
|
|
||||||
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="TaskZone/FarmZone"]
|
||||||
|
transform = Transform3D(2.0639582, 0, 0, 0, 0.2, 0, 0, 0, 2.211021, -0.47271156, 0.47702956, -0.02484417)
|
||||||
|
mesh = SubResource("BoxMesh_5vw27")
|
||||||
|
|
||||||
[node name="ForestZone" type="Marker3D" parent="TaskZone"]
|
[node name="ForestZone" type="Marker3D" parent="TaskZone"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0, -6)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0, -6)
|
||||||
|
|
||||||
|
[node name="smallTree" parent="TaskZone/ForestZone" instance=ExtResource("6_5vw27")]
|
||||||
|
transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, 1.891, 0, -0.22)
|
||||||
|
|
||||||
|
[node name="smallTree2" parent="TaskZone/ForestZone" instance=ExtResource("6_5vw27")]
|
||||||
|
transform = Transform3D(2, 0, 0, 0, 2, 0, 0, 0, 2, -0.043, 0, -1.767)
|
||||||
|
|
||||||
|
[node name="smallTree3" parent="TaskZone/ForestZone" instance=ExtResource("6_5vw27")]
|
||||||
|
transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, 0.13, 0, 0.614)
|
||||||
|
|
||||||
|
[node name="highTree" parent="TaskZone/ForestZone" instance=ExtResource("7_kek77")]
|
||||||
|
transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, -1.539, 0, 0.171)
|
||||||
|
|
||||||
[node name="GuardZone" type="Marker3D" parent="TaskZone"]
|
[node name="GuardZone" type="Marker3D" parent="TaskZone"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 8)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 8)
|
||||||
|
|
||||||
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="TaskZone/GuardZone"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.354011, 0)
|
||||||
|
mesh = SubResource("CylinderMesh_5vw27")
|
||||||
|
|
||||||
[node name="StudyZone" type="Marker3D" parent="TaskZone"]
|
[node name="StudyZone" type="Marker3D" parent="TaskZone"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, 6)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, 6)
|
||||||
|
|
||||||
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="TaskZone/StudyZone"]
|
||||||
|
transform = Transform3D(3.279775, 0, 0, 0, 5.511466, 0, 0, 0, 1, 0, 2.8180213, 0)
|
||||||
|
mesh = SubResource("BoxMesh_kek77")
|
||||||
|
metadata/_edit_group_ = true
|
||||||
|
|
||||||
[node name="RestZone" type="Marker3D" parent="TaskZone"]
|
[node name="RestZone" type="Marker3D" parent="TaskZone"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0, 5)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0, 5)
|
||||||
|
|
||||||
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="TaskZone/RestZone"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.4911091, 0)
|
||||||
|
mesh = SubResource("PrismMesh_5vw27")
|
||||||
|
|
||||||
|
[node name="MeshInstance3D2" type="MeshInstance3D" parent="TaskZone/RestZone"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.3897309, 1.399735, 1.0352364)
|
||||||
|
mesh = SubResource("PrismMesh_5vw27")
|
||||||
|
|
||||||
[node name="FoodZone" type="Marker3D" parent="TaskZone"]
|
[node name="FoodZone" type="Marker3D" parent="TaskZone"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -8)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -8)
|
||||||
|
|
||||||
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="TaskZone/FoodZone"]
|
||||||
|
transform = Transform3D(4, 0, 0, 0, 3, 0, 0, 0, 4, 0, 1.5, 0)
|
||||||
|
mesh = SubResource("BoxMesh_4c57u")
|
||||||
|
|
||||||
[node name="UI" type="CanvasLayer" parent="." node_paths=PackedStringArray("simulation_manager")]
|
[node name="UI" type="CanvasLayer" parent="." node_paths=PackedStringArray("simulation_manager")]
|
||||||
script = ExtResource("6_7mycd")
|
script = ExtResource("6_7mycd")
|
||||||
simulation_manager = NodePath("../SimulationManager")
|
simulation_manager = NodePath("../SimulationManager")
|
||||||
@@ -141,9 +204,10 @@ offset_bottom = 140.0
|
|||||||
[node name="MarginContainer" type="MarginContainer" parent="UI/VillagePanel"]
|
[node name="MarginContainer" type="MarginContainer" parent="UI/VillagePanel"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_constants/margin_left = 12
|
theme_override_constants/margin_left = 12
|
||||||
theme_override_constants/margin_top = 12
|
theme_override_constants/margin_top = 4
|
||||||
theme_override_constants/margin_right = 12
|
theme_override_constants/margin_right = 12
|
||||||
theme_override_constants/margin_bottom = 12
|
theme_override_constants/margin_bottom = 4
|
||||||
|
|
||||||
[node name="VillageStatsLabel" type="Label" parent="UI/VillagePanel/MarginContainer"]
|
[node name="VillageStatsLabel" type="Label" parent="UI/VillagePanel/MarginContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
extends Node
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b1ka4yih1ioh
|
||||||
+25
-12
@@ -1,21 +1,30 @@
|
|||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
@export var target: Node3D
|
@export var target: Node3D
|
||||||
|
|
||||||
@export var follow_offset := Vector3(0, 10, 8)
|
@export var follow_offset := Vector3(0, 10, 8)
|
||||||
@export var follow_speed := 12.0
|
@export var follow_speed := 12.0
|
||||||
|
@export var rotation_smooth_speed := 18.0
|
||||||
|
@export var focus_smooth_speed := 14.0
|
||||||
|
|
||||||
@export var mouse_sensitivity := 0.002
|
@export var mouse_sensitivity := 0.002
|
||||||
|
|
||||||
@export var deadzone_right := 3.0
|
@export var deadzone_right := 3.0
|
||||||
@export var deadzone_forward := 3.0
|
@export var deadzone_forward := 3.0
|
||||||
|
|
||||||
var yaw := 0.0
|
var yaw := 0.0
|
||||||
|
var smoothed_yaw := 0.0
|
||||||
|
|
||||||
var focus_position := Vector3.ZERO
|
var focus_position := Vector3.ZERO
|
||||||
|
var desired_focus_position := Vector3.ZERO
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||||
|
|
||||||
if target:
|
if target:
|
||||||
focus_position = target.global_position
|
focus_position = target.global_position
|
||||||
|
desired_focus_position = focus_position
|
||||||
global_position = focus_position + follow_offset
|
global_position = focus_position + follow_offset
|
||||||
|
|
||||||
|
|
||||||
@@ -31,28 +40,32 @@ func _physics_process(delta: float) -> void:
|
|||||||
if target == null:
|
if target == null:
|
||||||
return
|
return
|
||||||
|
|
||||||
rotation = Vector3(deg_to_rad(-55), yaw, 0)
|
var rot_t := 1.0 - exp(-rotation_smooth_speed * delta)
|
||||||
|
var focus_t := 1.0 - exp(-focus_smooth_speed * delta)
|
||||||
|
var follow_t := 1.0 - exp(-follow_speed * delta)
|
||||||
|
|
||||||
|
smoothed_yaw = lerp_angle(smoothed_yaw, yaw, rot_t)
|
||||||
|
|
||||||
|
rotation = Vector3(deg_to_rad(-55), smoothed_yaw, 0)
|
||||||
|
|
||||||
var target_pos := target.global_position
|
var target_pos := target.global_position
|
||||||
var diff := target_pos - focus_position
|
var diff := target_pos - desired_focus_position
|
||||||
|
|
||||||
# Camera-relative horizontal axes
|
var cam_right := Vector3.RIGHT.rotated(Vector3.UP, smoothed_yaw).normalized()
|
||||||
var cam_right := Vector3.RIGHT.rotated(Vector3.UP, yaw).normalized()
|
var cam_forward := Vector3.FORWARD.rotated(Vector3.UP, smoothed_yaw).normalized()
|
||||||
var cam_forward := Vector3.FORWARD.rotated(Vector3.UP, yaw).normalized()
|
|
||||||
|
|
||||||
var diff_right := diff.dot(cam_right)
|
var diff_right := diff.dot(cam_right)
|
||||||
var diff_forward := diff.dot(cam_forward)
|
var diff_forward := diff.dot(cam_forward)
|
||||||
|
|
||||||
if abs(diff_right) > deadzone_right:
|
if abs(diff_right) > deadzone_right:
|
||||||
focus_position += cam_right * (diff_right - sign(diff_right) * deadzone_right)
|
desired_focus_position += cam_right * (diff_right - sign(diff_right) * deadzone_right)
|
||||||
|
|
||||||
if abs(diff_forward) > deadzone_forward:
|
if abs(diff_forward) > deadzone_forward:
|
||||||
focus_position += cam_forward * (diff_forward - sign(diff_forward) * deadzone_forward)
|
desired_focus_position += cam_forward * (diff_forward - sign(diff_forward) * deadzone_forward)
|
||||||
|
|
||||||
var rotated_offset := follow_offset.rotated(Vector3.UP, yaw)
|
focus_position = focus_position.lerp(desired_focus_position, focus_t)
|
||||||
|
|
||||||
|
var rotated_offset := follow_offset.rotated(Vector3.UP, smoothed_yaw)
|
||||||
var desired_position := focus_position + rotated_offset
|
var desired_position := focus_position + rotated_offset
|
||||||
|
|
||||||
global_position = global_position.lerp(
|
global_position = global_position.lerp(desired_position, follow_t)
|
||||||
desired_position,
|
|
||||||
1.0 - exp(-follow_speed * delta)
|
|
||||||
)
|
|
||||||
|
|||||||
+68
-4
@@ -1,6 +1,8 @@
|
|||||||
extends CharacterBody3D
|
extends CharacterBody3D
|
||||||
|
|
||||||
const DEBUG_LOGS := false
|
signal arrived_at_target(sim_id: int)
|
||||||
|
|
||||||
|
const DEBUG_LOGS := true
|
||||||
|
|
||||||
@export var move_speed := 3.0
|
@export var move_speed := 3.0
|
||||||
@export var rotation_speed := 8.0
|
@export var rotation_speed := 8.0
|
||||||
@@ -8,6 +10,7 @@ const DEBUG_LOGS := false
|
|||||||
|
|
||||||
@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D
|
@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D
|
||||||
|
|
||||||
|
var has_reported_arrival := false
|
||||||
var sim_id: int = -1
|
var sim_id: int = -1
|
||||||
var npc_name: String = ""
|
var npc_name: String = ""
|
||||||
var profession: String = ""
|
var profession: String = ""
|
||||||
@@ -15,11 +18,18 @@ var profession: String = ""
|
|||||||
var current_path: PackedVector3Array = []
|
var current_path: PackedVector3Array = []
|
||||||
var path_index := 0
|
var path_index := 0
|
||||||
var current_target := Vector3.INF
|
var current_target := Vector3.INF
|
||||||
|
var arrival_distance := 0.6
|
||||||
|
|
||||||
|
var is_dead_visual := false
|
||||||
|
var dead_material := StandardMaterial3D.new()
|
||||||
|
|
||||||
func debug_log(message: String) -> void:
|
func debug_log(message: String) -> void:
|
||||||
if DEBUG_LOGS:
|
if DEBUG_LOGS:
|
||||||
print("[NPCVisual] ", name, " | ", message)
|
print("[NPCVisual] ", name, " | ", message)
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
dead_material.albedo_color = Color(0.25, 0.25, 0.25, 1.0)
|
||||||
|
|
||||||
func setup_from_sim(npc: SimNPC) -> void:
|
func setup_from_sim(npc: SimNPC) -> void:
|
||||||
sim_id = npc.id
|
sim_id = npc.id
|
||||||
npc_name = npc.npc_name
|
npc_name = npc.npc_name
|
||||||
@@ -27,10 +37,15 @@ func setup_from_sim(npc: SimNPC) -> void:
|
|||||||
name = "NPC_%s_%s" % [sim_id, npc_name]
|
name = "NPC_%s_%s" % [sim_id, npc_name]
|
||||||
|
|
||||||
func set_target_position(pos: Vector3) -> void:
|
func set_target_position(pos: Vector3) -> void:
|
||||||
if current_target.distance_to(pos) < 0.1:
|
current_target = pos
|
||||||
|
has_reported_arrival = false
|
||||||
|
|
||||||
|
if global_position.distance_to(pos) <= arrival_distance:
|
||||||
|
current_path = PackedVector3Array()
|
||||||
|
path_index = 0
|
||||||
|
debug_log("Already close to target, will report arrival")
|
||||||
return
|
return
|
||||||
|
|
||||||
current_target = pos
|
|
||||||
nav_agent.target_position = pos
|
nav_agent.target_position = pos
|
||||||
|
|
||||||
await get_tree().physics_frame
|
await get_tree().physics_frame
|
||||||
@@ -44,9 +59,28 @@ func set_target_position(pos: Vector3) -> void:
|
|||||||
|
|
||||||
path_index = 0
|
path_index = 0
|
||||||
|
|
||||||
debug_log("New target: %s Path points: %s" % [pos, current_path.size()])
|
if current_path.is_empty():
|
||||||
|
debug_log("Path is empty. Will report arrival/failure fallback.")
|
||||||
|
else:
|
||||||
|
debug_log("New target: %s Path points: %s" % [pos, current_path.size()])
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
|
if is_dead_visual:
|
||||||
|
velocity = Vector3.ZERO
|
||||||
|
move_and_slide()
|
||||||
|
return
|
||||||
|
|
||||||
|
if current_target == Vector3.INF:
|
||||||
|
velocity = Vector3.ZERO
|
||||||
|
move_and_slide()
|
||||||
|
return
|
||||||
|
|
||||||
|
if global_position.distance_to(current_target) <= arrival_distance:
|
||||||
|
_report_arrival_once()
|
||||||
|
velocity = Vector3.ZERO
|
||||||
|
move_and_slide()
|
||||||
|
return
|
||||||
|
|
||||||
if current_path.is_empty() or path_index >= current_path.size():
|
if current_path.is_empty() or path_index >= current_path.size():
|
||||||
velocity = Vector3.ZERO
|
velocity = Vector3.ZERO
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
@@ -70,3 +104,33 @@ func _physics_process(delta: float) -> void:
|
|||||||
|
|
||||||
var target_angle := atan2(direction.x, direction.z)
|
var target_angle := atan2(direction.x, direction.z)
|
||||||
rotation.y = lerp_angle(rotation.y, target_angle, rotation_speed * delta)
|
rotation.y = lerp_angle(rotation.y, target_angle, rotation_speed * delta)
|
||||||
|
|
||||||
|
func _report_arrival_once() -> void:
|
||||||
|
if has_reported_arrival:
|
||||||
|
return
|
||||||
|
|
||||||
|
has_reported_arrival = true
|
||||||
|
current_path = PackedVector3Array()
|
||||||
|
path_index = 0
|
||||||
|
|
||||||
|
debug_log("Arrived at target")
|
||||||
|
arrived_at_target.emit(sim_id)
|
||||||
|
|
||||||
|
func apply_dead_visual_state() -> void:
|
||||||
|
is_dead_visual = true
|
||||||
|
velocity = Vector3.ZERO
|
||||||
|
current_path = PackedVector3Array()
|
||||||
|
path_index = 0
|
||||||
|
current_target = Vector3.INF
|
||||||
|
has_reported_arrival = true
|
||||||
|
|
||||||
|
scale = Vector3(0.8, 0.25, 1.2)
|
||||||
|
rotation_degrees.x = 90.0
|
||||||
|
|
||||||
|
for child in get_children():
|
||||||
|
if child is MeshInstance3D:
|
||||||
|
child.material_override = dead_material
|
||||||
|
|
||||||
|
collision_layer = 0
|
||||||
|
collision_mask = 0
|
||||||
|
debug_log("Applied dead visual state")
|
||||||
|
|||||||
@@ -5,6 +5,17 @@ extends CharacterBody3D
|
|||||||
@export var rotation_speed := 12.0
|
@export var rotation_speed := 12.0
|
||||||
@export var camera_rig: Node3D
|
@export var camera_rig: Node3D
|
||||||
|
|
||||||
|
@export var simulation_manager: Node
|
||||||
|
|
||||||
|
@export var farm_zone: Marker3D
|
||||||
|
@export var forest_zone: Marker3D
|
||||||
|
@export var food_zone: Marker3D
|
||||||
|
@export var guard_zone: Marker3D
|
||||||
|
@export var study_zone: Marker3D
|
||||||
|
|
||||||
|
@export var stomach_capacity_for_food := 5
|
||||||
|
@export var interaction_range := 3.0
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
var input := Input.get_vector(
|
var input := Input.get_vector(
|
||||||
"move_left",
|
"move_left",
|
||||||
@@ -39,3 +50,36 @@ func _physics_process(delta: float) -> void:
|
|||||||
if direction.length() > 0.01:
|
if direction.length() > 0.01:
|
||||||
var target_angle := atan2(direction.x, direction.z)
|
var target_angle := atan2(direction.x, direction.z)
|
||||||
rotation.y = lerp_angle(rotation.y, target_angle, rotation_speed * delta)
|
rotation.y = lerp_angle(rotation.y, target_angle, rotation_speed * delta)
|
||||||
|
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
# keep your camera mouse code here too
|
||||||
|
if event.is_action_pressed("interact"):
|
||||||
|
try_interact()
|
||||||
|
|
||||||
|
|
||||||
|
func try_interact() -> void:
|
||||||
|
if simulation_manager == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_near(farm_zone):
|
||||||
|
simulation_manager.add_food(5.0)
|
||||||
|
print("Player gathered food for the village.")
|
||||||
|
elif is_near(forest_zone):
|
||||||
|
simulation_manager.add_wood(5.0)
|
||||||
|
print("Player gathered wood for the village.")
|
||||||
|
elif is_near(guard_zone):
|
||||||
|
simulation_manager.add_safety(3.0)
|
||||||
|
print("Player helped guard the village.")
|
||||||
|
elif is_near(study_zone):
|
||||||
|
simulation_manager.add_knowledge(2.0)
|
||||||
|
print("Player shared knowledge.")
|
||||||
|
elif is_near(food_zone):
|
||||||
|
simulation_manager.eat_food(stomach_capacity_for_food)
|
||||||
|
print("Player eating shit up.")
|
||||||
|
|
||||||
|
func is_near(zone: Marker3D) -> bool:
|
||||||
|
if zone == null:
|
||||||
|
return false
|
||||||
|
|
||||||
|
return global_position.distance_to(zone.global_position) <= interaction_range
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ run/main_scene="uid://rs3hv73svbpa"
|
|||||||
config/features=PackedStringArray("4.5", "Forward Plus")
|
config/features=PackedStringArray("4.5", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
|
[display]
|
||||||
|
|
||||||
|
window/size/viewport_width=1920
|
||||||
|
window/size/viewport_height=1020
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
|
|
||||||
move_forward={
|
move_forward={
|
||||||
@@ -37,3 +42,8 @@ move_right={
|
|||||||
"events": [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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
"events": [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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
interact={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [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":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
+213
-34
@@ -1,6 +1,13 @@
|
|||||||
class_name SimNPC
|
class_name SimNPC
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
|
const DEBUG_LOGS := true
|
||||||
|
|
||||||
|
const TASK_STATE_IDLE := "idle"
|
||||||
|
const TASK_STATE_TRAVELING := "traveling"
|
||||||
|
const TASK_STATE_WORKING := "working"
|
||||||
|
const TASK_STATE_COMPLETE := "complete"
|
||||||
|
|
||||||
var id: int
|
var id: int
|
||||||
var npc_name: String
|
var npc_name: String
|
||||||
var profession: String
|
var profession: String
|
||||||
@@ -9,8 +16,18 @@ var energy: float
|
|||||||
var strength: float
|
var strength: float
|
||||||
var intelligence: float
|
var intelligence: float
|
||||||
var current_task: String = "idle"
|
var current_task: String = "idle"
|
||||||
|
var task_state: String = TASK_STATE_IDLE
|
||||||
var position: Vector3
|
var position: Vector3
|
||||||
|
|
||||||
|
var is_starving := false
|
||||||
|
var starvation_ticks := 0
|
||||||
|
var starvation_death_threshold := 20
|
||||||
|
var is_dead := false
|
||||||
|
|
||||||
|
var task_duration := 0.0
|
||||||
|
var task_progress := 0.0
|
||||||
|
var task_complete := true
|
||||||
|
|
||||||
func _init(
|
func _init(
|
||||||
_id: int,
|
_id: int,
|
||||||
_npc_name: String,
|
_npc_name: String,
|
||||||
@@ -25,53 +42,215 @@ func _init(
|
|||||||
intelligence = _intelligence
|
intelligence = _intelligence
|
||||||
hunger = randf_range(20.0, 80.0)
|
hunger = randf_range(20.0, 80.0)
|
||||||
energy = randf_range(40.0, 100.0)
|
energy = randf_range(40.0, 100.0)
|
||||||
|
|
||||||
position = Vector3(randf_range(-8.0, 8.0), 0.0, randf_range(-8.0, 8.0))
|
position = Vector3(randf_range(-8.0, 8.0), 0.0, randf_range(-8.0, 8.0))
|
||||||
|
|
||||||
func simulate_tick(village: SimVillage) -> void:
|
func simulate_tick(village: SimVillage) -> void:
|
||||||
hunger += 5.0
|
if is_dead:
|
||||||
energy -= 2.0
|
return
|
||||||
|
|
||||||
|
update_needs(village)
|
||||||
|
|
||||||
|
if is_dead:
|
||||||
|
return
|
||||||
|
|
||||||
|
if task_state == TASK_STATE_IDLE or task_state == TASK_STATE_COMPLETE:
|
||||||
|
choose_new_task(village)
|
||||||
|
return
|
||||||
|
|
||||||
|
if task_state == TASK_STATE_TRAVELING:
|
||||||
|
return
|
||||||
|
|
||||||
|
if task_state == TASK_STATE_WORKING:
|
||||||
|
task_progress += 1.0
|
||||||
|
|
||||||
|
if task_progress >= task_duration:
|
||||||
|
task_complete = true
|
||||||
|
task_state = TASK_STATE_COMPLETE
|
||||||
|
|
||||||
|
func update_needs(village: SimVillage) -> void:
|
||||||
|
hunger += 3.0 * village.food_modifier
|
||||||
|
|
||||||
|
match task_state:
|
||||||
|
TASK_STATE_IDLE:
|
||||||
|
energy -= 0.5
|
||||||
|
TASK_STATE_TRAVELING:
|
||||||
|
energy -= 2.0
|
||||||
|
TASK_STATE_WORKING:
|
||||||
|
if current_task != "rest":
|
||||||
|
energy -= 3.0
|
||||||
|
TASK_STATE_COMPLETE:
|
||||||
|
energy -= 0.25
|
||||||
|
|
||||||
hunger = clamp(hunger, 0.0, 100.0)
|
hunger = clamp(hunger, 0.0, 100.0)
|
||||||
energy = clamp(energy, 0.0, 100.0)
|
energy = clamp(energy, 0.0, 100.0)
|
||||||
|
|
||||||
|
is_starving = hunger >= 90.0
|
||||||
|
|
||||||
|
if is_starving:
|
||||||
|
starvation_ticks += 1
|
||||||
|
else:
|
||||||
|
starvation_ticks = 0
|
||||||
|
|
||||||
|
if starvation_ticks >= starvation_death_threshold:
|
||||||
|
die_from_starvation()
|
||||||
|
|
||||||
|
func die_from_starvation() -> void:
|
||||||
|
is_dead = true
|
||||||
|
current_task = "dead"
|
||||||
|
task_state = TASK_STATE_IDLE
|
||||||
|
task_progress = 0.0
|
||||||
|
task_duration = 0.0
|
||||||
|
task_complete = true
|
||||||
|
|
||||||
|
if DEBUG_LOGS:
|
||||||
|
print("[SimNPC] ", npc_name, " died from starvation.")
|
||||||
|
|
||||||
|
func choose_new_task(village: SimVillage) -> void:
|
||||||
|
if is_dead:
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_starving:
|
||||||
|
if village.food > 0:
|
||||||
|
set_task("eat", 1.0)
|
||||||
|
else:
|
||||||
|
set_task("gather_food", 4.0)
|
||||||
|
return
|
||||||
|
|
||||||
if hunger > 75.0:
|
if hunger > 75.0:
|
||||||
if village.food > 0:
|
if village.food > 0:
|
||||||
current_task = "eat"
|
set_task("eat", 2.0)
|
||||||
hunger -= 25.0
|
|
||||||
else:
|
else:
|
||||||
current_task = "gather_food"
|
set_task("gather_food", 5.0)
|
||||||
return
|
return
|
||||||
|
|
||||||
if energy < 30.0:
|
if energy < 25.0:
|
||||||
current_task = "rest"
|
set_task("rest", 4.0)
|
||||||
energy += 20.0
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if village.food < 15.0:
|
var best_task := choose_best_work_task(village)
|
||||||
current_task = "gather_food"
|
|
||||||
return
|
|
||||||
|
|
||||||
if village.wood < 10.0:
|
match best_task:
|
||||||
current_task = "gather_wood"
|
"gather_food":
|
||||||
return
|
set_task("gather_food", 5.0)
|
||||||
|
"gather_wood":
|
||||||
if village.safety < 40.0 and profession == "guard":
|
set_task("gather_wood", 5.0)
|
||||||
current_task = "patrol"
|
"patrol":
|
||||||
return
|
set_task("patrol", 6.0)
|
||||||
|
"study":
|
||||||
if village.knowledge < 10.0 and profession == "scholar":
|
set_task("study", 6.0)
|
||||||
current_task = "study"
|
|
||||||
return
|
|
||||||
|
|
||||||
match profession:
|
|
||||||
"farmer":
|
|
||||||
current_task = "gather_food"
|
|
||||||
"woodcutter":
|
|
||||||
current_task = "gather_wood"
|
|
||||||
"guard":
|
|
||||||
current_task = "patrol"
|
|
||||||
"scholar":
|
|
||||||
current_task = "study"
|
|
||||||
_:
|
_:
|
||||||
current_task = "wander"
|
set_task("wander", 4.0)
|
||||||
|
|
||||||
|
func should_eat_immediately_after_task() -> bool:
|
||||||
|
return current_task == "gather_food" and is_starving and not is_dead
|
||||||
|
|
||||||
|
func choose_best_work_task(village: SimVillage) -> String:
|
||||||
|
var food_score := calculate_task_score(
|
||||||
|
"gather_food",
|
||||||
|
village.food_priority,
|
||||||
|
village.food,
|
||||||
|
15.0,
|
||||||
|
30.0,
|
||||||
|
3.0,
|
||||||
|
1.5,
|
||||||
|
"farmer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var wood_score := calculate_task_score(
|
||||||
|
"gather_wood",
|
||||||
|
village.wood_priority,
|
||||||
|
village.wood,
|
||||||
|
10.0,
|
||||||
|
20.0,
|
||||||
|
2.5,
|
||||||
|
1.0,
|
||||||
|
"woodcutter"
|
||||||
|
)
|
||||||
|
|
||||||
|
var safety_score := calculate_task_score(
|
||||||
|
"patrol",
|
||||||
|
village.safety_priority,
|
||||||
|
village.safety,
|
||||||
|
30.0,
|
||||||
|
50.0,
|
||||||
|
3.0,
|
||||||
|
1.5,
|
||||||
|
"guard"
|
||||||
|
)
|
||||||
|
|
||||||
|
var knowledge_score := calculate_task_score(
|
||||||
|
"study",
|
||||||
|
village.knowledge_priority,
|
||||||
|
village.knowledge,
|
||||||
|
10.0,
|
||||||
|
25.0,
|
||||||
|
1.5,
|
||||||
|
0.75,
|
||||||
|
"scholar"
|
||||||
|
)
|
||||||
|
|
||||||
|
var best_task := "gather_food"
|
||||||
|
var best_score := food_score
|
||||||
|
|
||||||
|
if wood_score > best_score:
|
||||||
|
best_task = "gather_wood"
|
||||||
|
best_score = wood_score
|
||||||
|
|
||||||
|
if safety_score > best_score:
|
||||||
|
best_task = "patrol"
|
||||||
|
best_score = safety_score
|
||||||
|
|
||||||
|
if knowledge_score > best_score:
|
||||||
|
best_task = "study"
|
||||||
|
best_score = knowledge_score
|
||||||
|
|
||||||
|
return best_task
|
||||||
|
|
||||||
|
func calculate_task_score(
|
||||||
|
task_name: String,
|
||||||
|
base_priority: float,
|
||||||
|
resource_amount: float,
|
||||||
|
critical_threshold: float,
|
||||||
|
low_threshold: float,
|
||||||
|
critical_bonus: float,
|
||||||
|
low_bonus: float,
|
||||||
|
preferred_profession: String
|
||||||
|
) -> float:
|
||||||
|
var score := base_priority
|
||||||
|
|
||||||
|
if resource_amount < critical_threshold:
|
||||||
|
score += critical_bonus
|
||||||
|
elif resource_amount < low_threshold:
|
||||||
|
score += low_bonus
|
||||||
|
|
||||||
|
if profession == preferred_profession:
|
||||||
|
score += 2.0
|
||||||
|
|
||||||
|
score += randf_range(0.0, 0.5)
|
||||||
|
|
||||||
|
if DEBUG_LOGS:
|
||||||
|
print(
|
||||||
|
"[SimNPC] ",
|
||||||
|
npc_name,
|
||||||
|
" score ",
|
||||||
|
task_name,
|
||||||
|
" = ",
|
||||||
|
score
|
||||||
|
)
|
||||||
|
|
||||||
|
return score
|
||||||
|
|
||||||
|
func set_task(task: String, duration: float) -> void:
|
||||||
|
current_task = task
|
||||||
|
task_duration = duration
|
||||||
|
task_progress = 0.0
|
||||||
|
task_complete = false
|
||||||
|
task_state = TASK_STATE_TRAVELING
|
||||||
|
|
||||||
|
func start_working() -> void:
|
||||||
|
if task_state != TASK_STATE_TRAVELING:
|
||||||
|
return
|
||||||
|
|
||||||
|
task_state = TASK_STATE_WORKING
|
||||||
|
task_progress = 0.0
|
||||||
|
task_complete = false
|
||||||
|
|||||||
+116
-8
@@ -1,25 +1,112 @@
|
|||||||
class_name SimVillage
|
class_name SimVillage
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
|
const DEBUG_LOGS := true
|
||||||
|
|
||||||
var food := 20.0
|
var food := 20.0
|
||||||
var wood := 10.0
|
var wood := 10.0
|
||||||
var safety := 50.0
|
var safety := 50.0
|
||||||
var knowledge := 0.0
|
var knowledge := 0.0
|
||||||
|
|
||||||
|
var food_modifier := 1.0
|
||||||
|
var wood_modifier := 1.0
|
||||||
|
var safety_modifier := 1.0
|
||||||
|
var knowledge_modifier := 1.0
|
||||||
|
|
||||||
|
var food_priority := 1.0
|
||||||
|
var wood_priority := 1.0
|
||||||
|
var safety_priority := 1.0
|
||||||
|
var knowledge_priority := 1.0
|
||||||
|
|
||||||
|
func update_modifiers() -> void:
|
||||||
|
food_modifier = 1.0
|
||||||
|
wood_modifier = 1.0
|
||||||
|
safety_modifier = 1.0
|
||||||
|
knowledge_modifier = 1.0
|
||||||
|
if food <= 0:
|
||||||
|
food_modifier = 2.0
|
||||||
|
if wood <= 0:
|
||||||
|
wood_modifier = 2.0
|
||||||
|
if safety < 20:
|
||||||
|
safety_modifier = 2.0
|
||||||
|
if knowledge > 25:
|
||||||
|
knowledge_modifier = 0.75
|
||||||
|
|
||||||
|
debug_log("FoodMod=%s SafetyMod=%s KnowledgeMod=%s" %
|
||||||
|
[
|
||||||
|
food_modifier,
|
||||||
|
safety_modifier,
|
||||||
|
knowledge_modifier
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
func update_priorities() -> void:
|
||||||
|
food_priority = 1.0
|
||||||
|
wood_priority = 1.0
|
||||||
|
safety_priority = 1.0
|
||||||
|
knowledge_priority = 1.0
|
||||||
|
|
||||||
|
if food < 10.0:
|
||||||
|
food_priority = 3.0
|
||||||
|
elif food < 20.0:
|
||||||
|
food_priority = 2.0
|
||||||
|
|
||||||
|
if wood < 5.0:
|
||||||
|
wood_priority = 2.5
|
||||||
|
elif wood < 15.0:
|
||||||
|
wood_priority = 1.75
|
||||||
|
|
||||||
|
if safety < 25.0:
|
||||||
|
safety_priority = 3.0
|
||||||
|
elif safety < 50.0:
|
||||||
|
safety_priority = 1.5
|
||||||
|
|
||||||
|
if knowledge < 5.0:
|
||||||
|
knowledge_priority = 1.75
|
||||||
|
elif knowledge < 15.0:
|
||||||
|
knowledge_priority = 1.25
|
||||||
|
|
||||||
|
if DEBUG_LOGS:
|
||||||
|
print(get_priority_summary())
|
||||||
|
|
||||||
func apply_npc_task(npc: SimNPC) -> void:
|
func apply_npc_task(npc: SimNPC) -> void:
|
||||||
|
if npc.is_dead:
|
||||||
|
return
|
||||||
|
|
||||||
|
var productivity := 1.0
|
||||||
|
|
||||||
|
if npc.is_starving:
|
||||||
|
productivity = 0.35
|
||||||
|
elif npc.energy < 20.0:
|
||||||
|
productivity = 0.25
|
||||||
|
elif npc.energy < 40.0:
|
||||||
|
productivity = 0.5
|
||||||
|
|
||||||
match npc.current_task:
|
match npc.current_task:
|
||||||
"gather_food":
|
"gather_food":
|
||||||
food += 2.0
|
food += 2.0 * productivity
|
||||||
"gather_wood":
|
"gather_wood":
|
||||||
wood += 2.0
|
wood += 2.0 * productivity
|
||||||
"patrol":
|
"patrol":
|
||||||
safety += 1.0
|
safety += 1.0 * productivity
|
||||||
"study":
|
"study":
|
||||||
knowledge += 1.0
|
knowledge += 1.0 * productivity
|
||||||
"eat":
|
"eat":
|
||||||
food -= 1.0
|
if food > 0:
|
||||||
|
food -= 1.0
|
||||||
|
npc.hunger = max(npc.hunger - 55.0, 0.0)
|
||||||
|
npc.starvation_ticks = 0
|
||||||
|
npc.is_starving = npc.hunger >= 90.0
|
||||||
|
else:
|
||||||
|
npc.hunger = min(npc.hunger + 5.0, 100.0)
|
||||||
|
npc.is_starving = npc.hunger >= 90.0
|
||||||
"rest":
|
"rest":
|
||||||
pass
|
if npc.is_starving:
|
||||||
|
npc.energy = min(npc.energy + 2.0, 100.0)
|
||||||
|
elif npc.hunger > 75.0:
|
||||||
|
npc.energy = min(npc.energy + 12.0, 100.0)
|
||||||
|
else:
|
||||||
|
npc.energy = min(npc.energy + 35.0, 100.0)
|
||||||
"wander":
|
"wander":
|
||||||
safety -= 0.2
|
safety -= 0.2
|
||||||
|
|
||||||
@@ -28,10 +115,31 @@ func apply_npc_task(npc: SimNPC) -> void:
|
|||||||
safety = clamp(safety, 0.0, 100.0)
|
safety = clamp(safety, 0.0, 100.0)
|
||||||
knowledge = max(knowledge, 0.0)
|
knowledge = max(knowledge, 0.0)
|
||||||
|
|
||||||
|
update_modifiers()
|
||||||
|
update_priorities()
|
||||||
|
|
||||||
func get_summary() -> String:
|
func get_summary() -> String:
|
||||||
return "Village | food: %s | wood: %s | safety: %s | knowledge: %s" % [
|
|
||||||
|
return "Village | food: %s | wood: %s | safety: %s | knowledge: %s | priorities F/W/S/K: %s/%s/%s/%s" % [
|
||||||
round(food),
|
round(food),
|
||||||
round(wood),
|
round(wood),
|
||||||
round(safety),
|
round(safety),
|
||||||
round(knowledge)
|
round(knowledge),
|
||||||
|
food_priority,
|
||||||
|
wood_priority,
|
||||||
|
safety_priority,
|
||||||
|
knowledge_priority
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
func get_priority_summary() -> String:
|
||||||
|
return "Priorities | food: %s | wood: %s | safety: %s | knowledge: %s" % [
|
||||||
|
food_priority,
|
||||||
|
wood_priority,
|
||||||
|
safety_priority,
|
||||||
|
knowledge_priority
|
||||||
|
]
|
||||||
|
|
||||||
|
func debug_log(message: String) -> void:
|
||||||
|
if DEBUG_LOGS:
|
||||||
|
print("[Village] ", message)
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ extends Node
|
|||||||
|
|
||||||
signal npc_task_changed(npc: SimNPC, old_task: String, new_task: String)
|
signal npc_task_changed(npc: SimNPC, old_task: String, new_task: String)
|
||||||
signal village_changed(village: SimVillage)
|
signal village_changed(village: SimVillage)
|
||||||
|
signal npc_died(npc: SimNPC)
|
||||||
|
|
||||||
var village := SimVillage.new()
|
var village := SimVillage.new()
|
||||||
|
|
||||||
const DEBUG_LOGS := true
|
const DEBUG_LOGS := true
|
||||||
|
|
||||||
var npcs: Array[SimNPC] = []
|
var npcs: Array[SimNPC] = []
|
||||||
var tick_interval := 1.0
|
var tick_interval := 1.2
|
||||||
var tick_timer := 0.0
|
var tick_timer := 0.0
|
||||||
var tick_count := 0
|
var tick_count := 0
|
||||||
|
|
||||||
var names := [
|
var names := [
|
||||||
"Rafi", "Mira", "Adem", "Nura", "Sahin",
|
"Amina", "Tarik", "Jasmin"
|
||||||
"Lejla", "Tarik", "Hana", "Idris", "Zejd"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
var professions := [
|
var professions := [
|
||||||
@@ -27,6 +27,8 @@ var professions := [
|
|||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
randomize()
|
randomize()
|
||||||
|
village.update_modifiers()
|
||||||
|
village.update_priorities()
|
||||||
generate_npcs()
|
generate_npcs()
|
||||||
print("--- Simulation started ---")
|
print("--- Simulation started ---")
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ func _process(delta: float) -> void:
|
|||||||
simulate_tick()
|
simulate_tick()
|
||||||
|
|
||||||
func generate_npcs() -> void:
|
func generate_npcs() -> void:
|
||||||
for i in range(10):
|
for i in range(names.size()):
|
||||||
var npc := SimNPC.new(
|
var npc := SimNPC.new(
|
||||||
i,
|
i,
|
||||||
names[i],
|
names[i],
|
||||||
@@ -53,13 +55,44 @@ func simulate_tick() -> void:
|
|||||||
tick_count += 1
|
tick_count += 1
|
||||||
print("--- Tick ", tick_count, " ---")
|
print("--- Tick ", tick_count, " ---")
|
||||||
|
|
||||||
|
var village_was_changed := false
|
||||||
|
|
||||||
for npc in npcs:
|
for npc in npcs:
|
||||||
var old_task := npc.current_task
|
var old_task := npc.current_task
|
||||||
|
var old_state := npc.task_state
|
||||||
|
var was_complete := npc.task_complete
|
||||||
|
var was_dead := npc.is_dead
|
||||||
|
|
||||||
npc.simulate_tick(village)
|
npc.simulate_tick(village)
|
||||||
|
|
||||||
if old_task != npc.current_task:
|
if not was_dead and npc.is_dead:
|
||||||
|
npc_died.emit(npc)
|
||||||
npc_task_changed.emit(npc, old_task, npc.current_task)
|
npc_task_changed.emit(npc, old_task, npc.current_task)
|
||||||
|
|
||||||
|
if DEBUG_LOGS:
|
||||||
|
print("[SimulationManager] NPC died: ", npc.npc_name)
|
||||||
|
|
||||||
|
if old_task != npc.current_task and not npc.is_dead:
|
||||||
|
npc_task_changed.emit(npc, old_task, npc.current_task)
|
||||||
|
|
||||||
|
if not was_complete and npc.task_complete and not npc.is_dead:
|
||||||
|
var completed_task := npc.current_task
|
||||||
|
var needs_immediate_food_after_gather := npc.should_eat_immediately_after_task()
|
||||||
|
|
||||||
|
village.apply_npc_task(npc)
|
||||||
|
village_was_changed = true
|
||||||
|
|
||||||
|
npc.task_complete = true
|
||||||
|
npc.task_state = SimNPC.TASK_STATE_IDLE
|
||||||
|
npc.current_task = "idle"
|
||||||
|
|
||||||
|
if needs_immediate_food_after_gather and village.food > 0:
|
||||||
|
npc.set_task("eat", 1.0)
|
||||||
|
npc_task_changed.emit(npc, completed_task, npc.current_task)
|
||||||
|
|
||||||
|
if DEBUG_LOGS:
|
||||||
|
print("[SimulationManager] ", npc.npc_name, " gathered food while starving and is going to eat immediately.")
|
||||||
|
|
||||||
if DEBUG_LOGS:
|
if DEBUG_LOGS:
|
||||||
print(
|
print(
|
||||||
npc.npc_name,
|
npc.npc_name,
|
||||||
@@ -67,14 +100,71 @@ func simulate_tick() -> void:
|
|||||||
npc.profession,
|
npc.profession,
|
||||||
" | task: ",
|
" | task: ",
|
||||||
npc.current_task,
|
npc.current_task,
|
||||||
|
" | state: ",
|
||||||
|
npc.task_state,
|
||||||
|
" | progress: ",
|
||||||
|
npc.task_progress,
|
||||||
|
"/",
|
||||||
|
npc.task_duration,
|
||||||
|
" | complete: ",
|
||||||
|
npc.task_complete,
|
||||||
" | hunger: ",
|
" | hunger: ",
|
||||||
round(npc.hunger),
|
round(npc.hunger),
|
||||||
" | energy: ",
|
" | energy: ",
|
||||||
round(npc.energy)
|
round(npc.energy),
|
||||||
|
" | starving: ",
|
||||||
|
npc.is_starving,
|
||||||
|
" | starvation_ticks: ",
|
||||||
|
npc.starvation_ticks,
|
||||||
|
" | dead: ",
|
||||||
|
npc.is_dead
|
||||||
)
|
)
|
||||||
|
|
||||||
for npc in npcs:
|
if old_state != npc.task_state:
|
||||||
village.apply_npc_task(npc)
|
print("[SimulationManager] State changed: ", npc.npc_name, " ", old_state, " -> ", npc.task_state)
|
||||||
|
|
||||||
|
if village_was_changed:
|
||||||
village_changed.emit(village)
|
village_changed.emit(village)
|
||||||
|
|
||||||
if DEBUG_LOGS:
|
if DEBUG_LOGS:
|
||||||
print(village.get_summary())
|
print(village.get_summary())
|
||||||
|
|
||||||
|
func notify_npc_arrived(npc_id: int) -> void:
|
||||||
|
for npc in npcs:
|
||||||
|
if npc.id == npc_id:
|
||||||
|
if npc.is_dead:
|
||||||
|
return
|
||||||
|
|
||||||
|
if npc.task_state == SimNPC.TASK_STATE_TRAVELING:
|
||||||
|
npc.start_working()
|
||||||
|
|
||||||
|
if DEBUG_LOGS:
|
||||||
|
print("[SimulationManager] ", npc.npc_name, " arrived and started working on: ", npc.current_task)
|
||||||
|
return
|
||||||
|
|
||||||
|
func eat_food(amount: float) -> void:
|
||||||
|
village.food -= amount
|
||||||
|
village_changed.emit(village)
|
||||||
|
|
||||||
|
func add_food(amount: float) -> void:
|
||||||
|
village.food += amount
|
||||||
|
village_changed.emit(village)
|
||||||
|
|
||||||
|
func add_wood(amount: float) -> void:
|
||||||
|
village.wood += amount
|
||||||
|
village_changed.emit(village)
|
||||||
|
|
||||||
|
func add_safety(amount: float) -> void:
|
||||||
|
village.safety = clamp(village.safety + amount, 0.0, 100.0)
|
||||||
|
village_changed.emit(village)
|
||||||
|
|
||||||
|
func add_knowledge(amount: float) -> void:
|
||||||
|
village.knowledge += amount
|
||||||
|
village_changed.emit(village)
|
||||||
|
|
||||||
|
func get_starving_count() -> int:
|
||||||
|
var count := 0
|
||||||
|
for npc in npcs:
|
||||||
|
if npc.is_starving:
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|||||||
+16
-7
@@ -21,15 +21,24 @@ func _ready() -> void:
|
|||||||
|
|
||||||
func _on_village_changed(village: SimVillage) -> void:
|
func _on_village_changed(village: SimVillage) -> void:
|
||||||
village_stats_label.text = """
|
village_stats_label.text = """
|
||||||
Village
|
Village
|
||||||
|
|
||||||
Food: %s
|
Food: %s
|
||||||
Wood: %s
|
Wood: %s
|
||||||
Safety: %s
|
Safety: %s
|
||||||
Knowledge: %s
|
Knowledge: %s
|
||||||
""" % [
|
Starving: %s
|
||||||
|
|
||||||
|
Food Mod: %.2f
|
||||||
|
Safety Mod: %.2f
|
||||||
|
Knowledge Mod: %.2f
|
||||||
|
""" % [
|
||||||
round(village.food),
|
round(village.food),
|
||||||
round(village.wood),
|
round(village.wood),
|
||||||
round(village.safety),
|
round(village.safety),
|
||||||
round(village.knowledge)
|
round(village.knowledge),
|
||||||
|
simulation_manager.get_starving_count(),
|
||||||
|
village.food_modifier,
|
||||||
|
village.safety_modifier,
|
||||||
|
village.knowledge_modifier
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ func initialize_world_view() -> void:
|
|||||||
else:
|
else:
|
||||||
push_error("WorldViewManager: SimulationManager has no npc_task_changed signal")
|
push_error("WorldViewManager: SimulationManager has no npc_task_changed signal")
|
||||||
|
|
||||||
|
if simulation_manager.has_signal("npc_died"):
|
||||||
|
simulation_manager.npc_died.connect(_on_npc_died)
|
||||||
|
else:
|
||||||
|
push_error("WorldViewManager: SimulationManager has no npc_died signal")
|
||||||
|
|
||||||
update_npc_targets()
|
update_npc_targets()
|
||||||
|
|
||||||
func spawn_initial_npcs() -> void:
|
func spawn_initial_npcs() -> void:
|
||||||
@@ -61,8 +66,12 @@ func spawn_npc_visual(npc: SimNPC) -> void:
|
|||||||
visual.global_position = npc.position
|
visual.global_position = npc.position
|
||||||
visual.setup_from_sim(npc)
|
visual.setup_from_sim(npc)
|
||||||
|
|
||||||
active_npc_visuals[npc.id] = visual
|
if visual.has_signal("arrived_at_target"):
|
||||||
|
visual.arrived_at_target.connect(_on_npc_visual_arrived)
|
||||||
|
else:
|
||||||
|
push_error("WorldViewManager: NPCVisual has no arrived_at_target signal")
|
||||||
|
|
||||||
|
active_npc_visuals[npc.id] = visual
|
||||||
debug_log("Spawned visual for %s at %s" % [npc.npc_name, npc.position])
|
debug_log("Spawned visual for %s at %s" % [npc.npc_name, npc.position])
|
||||||
|
|
||||||
func update_npc_targets() -> void:
|
func update_npc_targets() -> void:
|
||||||
@@ -111,3 +120,27 @@ func _on_npc_task_changed(npc: SimNPC, old_task: String, new_task: String) -> vo
|
|||||||
|
|
||||||
if target != null:
|
if target != null:
|
||||||
visual.set_target_position(target.global_position)
|
visual.set_target_position(target.global_position)
|
||||||
|
|
||||||
|
func _on_npc_visual_arrived(sim_id: int) -> void:
|
||||||
|
debug_log("NPC visual arrived. sim_id=%s" % sim_id)
|
||||||
|
|
||||||
|
if simulation_manager == null:
|
||||||
|
push_error("WorldViewManager: simulation_manager missing during arrival report")
|
||||||
|
return
|
||||||
|
|
||||||
|
if simulation_manager.has_method("notify_npc_arrived"):
|
||||||
|
simulation_manager.notify_npc_arrived(sim_id)
|
||||||
|
else:
|
||||||
|
push_error("WorldViewManager: SimulationManager has no notify_npc_arrived method")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_npc_died(npc: SimNPC) -> void:
|
||||||
|
if not active_npc_visuals.has(npc.id):
|
||||||
|
return
|
||||||
|
|
||||||
|
var visual = active_npc_visuals[npc.id]
|
||||||
|
|
||||||
|
if visual.has_method("apply_dead_visual_state"):
|
||||||
|
visual.apply_dead_visual_state()
|
||||||
|
else:
|
||||||
|
push_error("WorldViewManager: NPCVisual has no apply_dead_visual_state method")
|
||||||
|
|||||||
Reference in New Issue
Block a user