Compare commits
27 Commits
e9b070ff35
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
7438ba407a | ||
|
1c6c03cd41 | ||
|
32f96d488d | ||
|
630dbf0800 | ||
|
ac1d8cfab9 | ||
|
60b6d6f989 | ||
|
483773f042 | ||
|
56cd4c2db2 | ||
|
7720e74a3d | ||
|
885d2c0075 | ||
|
8073ed23c0 | ||
|
862f11d445 | ||
|
9408651ea8 | ||
|
75cd807187 | ||
|
fac5e5a597 | ||
|
850628ca72 | ||
|
0d3c84491b | ||
|
af31914ada | ||
|
19f7243101 | ||
|
6d91d4efb8 | ||
|
4ab6271e16 | ||
|
359e1532d2 | ||
|
3fd655a3e5 | ||
|
d3b19aa104 | ||
|
e54215423d | ||
|
84b908ef51 | ||
|
c078204e60 |
BIN
Assets/Items/OreIronItem.png
Normal file
After Width: | Height: | Size: 887 KiB |
34
Assets/Items/OreIronItem.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://caoyy241xsoer"
|
||||
path="res://.godot/imported/OreIronItem.png-9b5b0a6a8d0e3616bac40fde1dc14f49.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Items/OreIronItem.png"
|
||||
dest_files=["res://.godot/imported/OreIronItem.png-9b5b0a6a8d0e3616bac40fde1dc14f49.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=false
|
||||
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
|
BIN
Assets/Items/StoneItem.png
Normal file
After Width: | Height: | Size: 699 KiB |
34
Assets/Items/StoneItem.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d3mn15csjooch"
|
||||
path="res://.godot/imported/StoneItem.png-cc89310dd113aeb437107818aa1ce10c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Items/StoneItem.png"
|
||||
dest_files=["res://.godot/imported/StoneItem.png-cc89310dd113aeb437107818aa1ce10c.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=false
|
||||
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
|
@@ -5,15 +5,65 @@
|
||||
"stone": 5
|
||||
},
|
||||
"durability": 100,
|
||||
"buildTime": 1.5
|
||||
"buildTime": 1.5,
|
||||
"allowedRotations": [0],
|
||||
"layer": 1
|
||||
},
|
||||
"miner": {
|
||||
"scene": "res://Scenes/Tiles/MinerTile.tscn",
|
||||
"cost": {
|
||||
"wood": 10,
|
||||
"stone": 3
|
||||
"stone": 10,
|
||||
"ore_iron": 3
|
||||
},
|
||||
"durability": 200,
|
||||
"buildTime": 3.0
|
||||
"buildTime": 3.0,
|
||||
"allowedRotations": [0],
|
||||
"layer": 1,
|
||||
"size": [1, 1]
|
||||
},
|
||||
"turret": {
|
||||
"scene": "res://Scenes/Tiles/TurretTile.tscn",
|
||||
"cost": {
|
||||
"stone": 10,
|
||||
"ore_iron": 5
|
||||
},
|
||||
"durability": 200,
|
||||
"buildTime": 5.0,
|
||||
"allowedRotations": [0],
|
||||
"layer": 1,
|
||||
"size": [1, 1]
|
||||
},
|
||||
"enemy_nest": {
|
||||
"scene": "res://Scenes/Tiles/EnemyNest.tscn",
|
||||
"cost": {},
|
||||
"durability": 200,
|
||||
"buildTime": 0.0,
|
||||
"allowedRotations": [0],
|
||||
"layer": 1,
|
||||
"size": [1, 1]
|
||||
},
|
||||
"ground": {
|
||||
"scene": "res://Scenes/Tiles/GroundTile.tscn",
|
||||
"cost": {},
|
||||
"durability": 200,
|
||||
"buildTime": 0.0,
|
||||
"allowedRotations": [0],
|
||||
"layer": 0
|
||||
},
|
||||
"stone": {
|
||||
"scene": "res://Scenes/Tiles/StoneTile.tscn",
|
||||
"cost": {},
|
||||
"durability": 200,
|
||||
"buildTime": 0.0,
|
||||
"allowedRotations": [0],
|
||||
"layer": 0
|
||||
},
|
||||
"ore_iron": {
|
||||
"scene": "res://Scenes/Tiles/OreIronTile.tscn",
|
||||
"cost": {},
|
||||
"durability": 200,
|
||||
"buildTime": 0.0,
|
||||
"allowedRotations": [0],
|
||||
"layer": 0
|
||||
}
|
||||
}
|
||||
|
4
Data/ItemTextures.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"stone": "res://Assets/Items/StoneItem.png",
|
||||
"ore_iron": "res://Assets/Items/OreIronItem.png"
|
||||
}
|
BIN
Scenes/Entities/Bullet.png
Normal file
After Width: | Height: | Size: 659 KiB |
34
Scenes/Entities/Bullet.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b5mb8tu15rc2p"
|
||||
path="res://.godot/imported/Bullet.png-db4c50cb16094f39ca6a1b9de30a2fe2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Scenes/Entities/Bullet.png"
|
||||
dest_files=["res://.godot/imported/Bullet.png-db4c50cb16094f39ca6a1b9de30a2fe2.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=false
|
||||
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
|
18
Scenes/Entities/Bullet.tscn
Normal file
@@ -0,0 +1,18 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://erqawdsydh6a"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://b5mb8tu15rc2p" path="res://Scenes/Entities/Bullet.png" id="1_fi8au"]
|
||||
[ext_resource type="Script" uid="uid://vgx2a8gm7l8b" path="res://Scripts/Entities/Bullet.cs" id="1_k5b1m"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_fi8au"]
|
||||
size = Vector2(44, 12)
|
||||
|
||||
[node name="Bullet" type="Area2D"]
|
||||
collision_mask = 2
|
||||
script = ExtResource("1_k5b1m")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
scale = Vector2(0.03, 0.03)
|
||||
texture = ExtResource("1_fi8au")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_fi8au")
|
34
Scenes/Entities/Enemy.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dlhpiyxtmp707"
|
||||
path="res://.godot/imported/Enemy.jpg-862b79047d7834ee48aa6bbd7e126824.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Scenes/Entities/Enemy.jpg"
|
||||
dest_files=["res://.godot/imported/Enemy.jpg-862b79047d7834ee48aa6bbd7e126824.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=false
|
||||
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
|
19
Scenes/Entities/Enemy.tscn
Normal file
@@ -0,0 +1,19 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://b3ffcucytwmk"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://dlhpiyxtmp707" path="res://Scenes/Entities/Enemy.jpg" id="1_8q37v"]
|
||||
[ext_resource type="Script" uid="uid://cvsmy820b8dwl" path="res://Scripts/Entities/Enemy.cs" id="1_jajit"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_jajit"]
|
||||
size = Vector2(60, 74)
|
||||
|
||||
[node name="Enemy" type="CharacterBody2D"]
|
||||
collision_layer = 2
|
||||
collision_mask = 3
|
||||
script = ExtResource("1_jajit")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_jajit")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
scale = Vector2(0.1, 0.1)
|
||||
texture = ExtResource("1_8q37v")
|
@@ -4,7 +4,10 @@
|
||||
[ext_resource type="Texture2D" uid="uid://jye6c2ehuxtg" path="res://Scenes/Entities/Player.png" id="1_ucweq"]
|
||||
|
||||
[node name="Player" type="CharacterBody2D"]
|
||||
collision_mask = 3
|
||||
script = ExtResource("1_08t41")
|
||||
MinZoom = 0.1
|
||||
MaxZoom = 5.0
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
rotation = 1.5708
|
||||
|
@@ -1,23 +1,45 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://c22aprj452aha"]
|
||||
[gd_scene load_steps=8 format=3 uid="uid://c22aprj452aha"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cudpc3w17mbsw" path="res://Scripts/System/GridManager.cs" id="1_knkkn"]
|
||||
[ext_resource type="Script" uid="uid://dfi2snip78eq6" path="res://Scripts/System/ResourceManager.cs" id="1_pl8e4"]
|
||||
[ext_resource type="Script" uid="uid://cfbj72nm0eovg" path="res://Scripts/System/BuildingRegistry.cs" id="1_sxhdm"]
|
||||
[ext_resource type="Script" uid="uid://cugfbvw70clgd" path="res://Scripts/System/NaturalResourceGenerator.cs" id="2_oss8w"]
|
||||
[ext_resource type="Script" uid="uid://bx1wj7gn6vrqe" path="res://Scripts/System/PlacementManager.cs" id="2_sxhdm"]
|
||||
[ext_resource type="PackedScene" uid="uid://doxy60afddg1m" path="res://Scenes/Entities/Player.tscn" id="3_oss8w"]
|
||||
[ext_resource type="PackedScene" uid="uid://xwkplaxmye3v" path="res://Scenes/System/ItemPickup.tscn" id="7_is6ib"]
|
||||
|
||||
[node name="Root" type="Node2D"]
|
||||
|
||||
[node name="ResourceSystem" type="Node" parent="."]
|
||||
script = ExtResource("1_pl8e4")
|
||||
|
||||
[node name="BuildingRegistry" type="Node" parent="."]
|
||||
script = ExtResource("1_sxhdm")
|
||||
|
||||
[node name="GridSystem" type="Node2D" parent="."]
|
||||
script = ExtResource("1_knkkn")
|
||||
|
||||
[node name="PlacementSystem" type="Node2D" parent="." node_paths=PackedStringArray("Grid", "Registry")]
|
||||
script = ExtResource("2_sxhdm")
|
||||
[node name="NaturalResourceGenerator" type="Node2D" parent="." node_paths=PackedStringArray("Grid", "Registry")]
|
||||
script = ExtResource("2_oss8w")
|
||||
Grid = NodePath("../GridSystem")
|
||||
Registry = NodePath("../BuildingRegistry")
|
||||
|
||||
[node name="Player" parent="." instance=ExtResource("3_oss8w")]
|
||||
position = Vector2(602, 324)
|
||||
[node name="GridSystem" type="Node2D" parent="."]
|
||||
script = ExtResource("1_knkkn")
|
||||
|
||||
[node name="PlacementSystem" type="Node2D" parent="." node_paths=PackedStringArray("Grid", "Inventory", "Registry")]
|
||||
script = ExtResource("2_sxhdm")
|
||||
Grid = NodePath("../GridSystem")
|
||||
Inventory = NodePath("../ResourceSystem")
|
||||
Registry = NodePath("../BuildingRegistry")
|
||||
|
||||
[node name="Player" parent="." node_paths=PackedStringArray("Inventory") instance=ExtResource("3_oss8w")]
|
||||
scale = Vector2(0.35, 0.35)
|
||||
Inventory = NodePath("../ResourceSystem")
|
||||
|
||||
[node name="ItemPickup" parent="." instance=ExtResource("7_is6ib")]
|
||||
position = Vector2(-496, -245)
|
||||
ItemId = "stone"
|
||||
Quantity = 64
|
||||
|
||||
[node name="ItemPickup2" parent="." instance=ExtResource("7_is6ib")]
|
||||
position = Vector2(-495, 5)
|
||||
ItemId = "ore_iron"
|
||||
Quantity = 16
|
||||
|
26
Scenes/System/ItemPickup.tscn
Normal file
@@ -0,0 +1,26 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://xwkplaxmye3v"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://d3mn15csjooch" path="res://Assets/Items/StoneItem.png" id="1_4weev"]
|
||||
[ext_resource type="Script" uid="uid://qgcue2doj2lf" path="res://Scripts/System/ItemPickup.cs" id="1_ps3kh"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_4weev"]
|
||||
size = Vector2(50, 50)
|
||||
|
||||
[node name="ItemPickup" type="Area2D"]
|
||||
script = ExtResource("1_ps3kh")
|
||||
MagnetRange = 96.0
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
scale = Vector2(0.05, 0.05)
|
||||
texture = ExtResource("1_4weev")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_4weev")
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
offset_left = -20.0
|
||||
offset_right = 20.0
|
||||
offset_bottom = 23.0
|
||||
theme_override_font_sizes/font_size = 0
|
||||
text = "x1"
|
||||
horizontal_alignment = 1
|
34
Scenes/Tiles/EnemyNest.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://vwfs68ftvjr4"
|
||||
path="res://.godot/imported/EnemyNest.jpg-9a1f582f2843b75fdeff38422d3798b9.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Scenes/Tiles/EnemyNest.jpg"
|
||||
dest_files=["res://.godot/imported/EnemyNest.jpg-9a1f582f2843b75fdeff38422d3798b9.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=false
|
||||
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
|
20
Scenes/Tiles/EnemyNest.tscn
Normal file
@@ -0,0 +1,20 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://dup2su0s3ybcy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://26hl5mk4mqur" path="res://Scripts/Tiles/EnemyNestTile.cs" id="1_4g0ff"]
|
||||
[ext_resource type="Texture2D" uid="uid://vwfs68ftvjr4" path="res://Scenes/Tiles/EnemyNest.jpg" id="1_id484"]
|
||||
[ext_resource type="PackedScene" uid="uid://b3ffcucytwmk" path="res://Scenes/Entities/Enemy.tscn" id="2_pka71"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_id484"]
|
||||
size = Vector2(54, 54)
|
||||
|
||||
[node name="EnemyNest" type="StaticBody2D"]
|
||||
script = ExtResource("1_4g0ff")
|
||||
EnemyScene = ExtResource("2_pka71")
|
||||
TileId = "enemy_nest"
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
scale = Vector2(0.056, 0.056)
|
||||
texture = ExtResource("1_id484")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_id484")
|
BIN
Scenes/Tiles/GroundTile.png
Normal file
After Width: | Height: | Size: 367 KiB |
34
Scenes/Tiles/GroundTile.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cww1edmqcmb8b"
|
||||
path="res://.godot/imported/GroundTile.png-b193d222fbf869e7606f01318fb918f7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Scenes/Tiles/GroundTile.png"
|
||||
dest_files=["res://.godot/imported/GroundTile.png-b193d222fbf869e7606f01318fb918f7.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=false
|
||||
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
|
27
Scenes/Tiles/GroundTile.tscn
Normal file
@@ -0,0 +1,27 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://7735luf8htft"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dh0jdeplrigxu" path="res://Scripts/Tiles/GroundTile.cs" id="1_mqsaf"]
|
||||
[ext_resource type="Texture2D" uid="uid://cww1edmqcmb8b" path="res://Scenes/Tiles/GroundTile.png" id="2_tow7r"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_8o613"]
|
||||
size = Vector2(54, 54)
|
||||
|
||||
[node name="GroundTile" type="StaticBody2D"]
|
||||
collision_layer = 0
|
||||
collision_mask = 0
|
||||
script = ExtResource("1_mqsaf")
|
||||
TileId = "ground"
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
scale = Vector2(0.1, 0.1)
|
||||
texture = ExtResource("2_tow7r")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_8o613")
|
||||
|
||||
[node name="ProgressOverlay" type="ColorRect" parent="."]
|
||||
offset_left = -27.0
|
||||
offset_top = -27.0
|
||||
offset_right = 27.0
|
||||
offset_bottom = 27.0
|
||||
metadata/_edit_use_anchors_ = true
|
Before Width: | Height: | Size: 438 KiB After Width: | Height: | Size: 506 KiB |
@@ -2,7 +2,7 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bn80thu20eaia"
|
||||
uid="uid://bt6xmcgrbb078"
|
||||
path="res://.godot/imported/MinerTile.png-a6c5eba73cdda5685afda8ced0234fec.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
|
@@ -1,13 +1,16 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cbu81slklwq3u"]
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cbu81slklwq3u"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dyubkyqtpcg3a" path="res://Scripts/Tiles/MinerTile.cs" id="1_mecoy"]
|
||||
[ext_resource type="Texture2D" uid="uid://bn80thu20eaia" path="res://Scenes/Tiles/MinerTile.png" id="2_mecoy"]
|
||||
[ext_resource type="Texture2D" uid="uid://bt6xmcgrbb078" path="res://Scenes/Tiles/MinerTile.png" id="2_mecoy"]
|
||||
[ext_resource type="PackedScene" uid="uid://xwkplaxmye3v" path="res://Scenes/System/ItemPickup.tscn" id="2_xhk0k"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_8o613"]
|
||||
size = Vector2(54, 54)
|
||||
|
||||
[node name="MinerTile" type="StaticBody2D"]
|
||||
script = ExtResource("1_mecoy")
|
||||
ItemPickup = ExtResource("2_xhk0k")
|
||||
TileId = "miner"
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
scale = Vector2(0.1, 0.1)
|
||||
|
BIN
Scenes/Tiles/OreIronTile.png
Normal file
After Width: | Height: | Size: 436 KiB |
34
Scenes/Tiles/OreIronTile.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c3qdue55e6blv"
|
||||
path="res://.godot/imported/OreIronTile.png-68726be1be21a3f84cf7d705e1a39b0d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Scenes/Tiles/OreIronTile.png"
|
||||
dest_files=["res://.godot/imported/OreIronTile.png-68726be1be21a3f84cf7d705e1a39b0d.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=false
|
||||
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
|
28
Scenes/Tiles/OreIronTile.tscn
Normal file
@@ -0,0 +1,28 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://dnkcl5ucmip40"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dh0jdeplrigxu" path="res://Scripts/Tiles/GroundTile.cs" id="1_exnim"]
|
||||
[ext_resource type="Texture2D" uid="uid://c3qdue55e6blv" path="res://Scenes/Tiles/OreIronTile.png" id="2_7v0w1"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_8o613"]
|
||||
size = Vector2(54, 54)
|
||||
|
||||
[node name="OreIronTile" type="StaticBody2D"]
|
||||
collision_layer = 0
|
||||
collision_mask = 0
|
||||
script = ExtResource("1_exnim")
|
||||
TileId = "ore_iron"
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
position = Vector2(1.49012e-08, -9.53674e-07)
|
||||
scale = Vector2(0.0983607, 0.0981818)
|
||||
texture = ExtResource("2_7v0w1")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_8o613")
|
||||
|
||||
[node name="ProgressOverlay" type="ColorRect" parent="."]
|
||||
visible = false
|
||||
offset_left = -27.0
|
||||
offset_top = -27.0
|
||||
offset_right = 27.0
|
||||
offset_bottom = 27.0
|
BIN
Scenes/Tiles/StoneTile.png
Normal file
After Width: | Height: | Size: 558 KiB |
34
Scenes/Tiles/StoneTile.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ca0qwr6hngho2"
|
||||
path="res://.godot/imported/StoneTile.png-20e063912018fbf7fd000e734fc2097f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Scenes/Tiles/StoneTile.png"
|
||||
dest_files=["res://.godot/imported/StoneTile.png-20e063912018fbf7fd000e734fc2097f.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=false
|
||||
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
|
28
Scenes/Tiles/StoneTile.tscn
Normal file
@@ -0,0 +1,28 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://bafvcatjix3mx"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dh0jdeplrigxu" path="res://Scripts/Tiles/GroundTile.cs" id="1_rndy8"]
|
||||
[ext_resource type="Texture2D" uid="uid://ca0qwr6hngho2" path="res://Scenes/Tiles/StoneTile.png" id="2_rndy8"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_8o613"]
|
||||
size = Vector2(54, 54)
|
||||
|
||||
[node name="StoneTile" type="StaticBody2D"]
|
||||
collision_layer = 0
|
||||
collision_mask = 0
|
||||
script = ExtResource("1_rndy8")
|
||||
TileId = "stone"
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
position = Vector2(1.01328e-06, 1.01328e-06)
|
||||
scale = Vector2(0.0976562, 0.0976562)
|
||||
texture = ExtResource("2_rndy8")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_8o613")
|
||||
|
||||
[node name="ProgressOverlay" type="ColorRect" parent="."]
|
||||
visible = false
|
||||
offset_left = -27.0
|
||||
offset_top = -27.0
|
||||
offset_right = 27.0
|
||||
offset_bottom = 27.0
|
BIN
Scenes/Tiles/TurretTile.png
Normal file
After Width: | Height: | Size: 448 KiB |
34
Scenes/Tiles/TurretTile.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ckssi7soymu7g"
|
||||
path="res://.godot/imported/TurretTile.png-d55543f854deaa0fedf248ba979d1cb4.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Scenes/Tiles/TurretTile.png"
|
||||
dest_files=["res://.godot/imported/TurretTile.png-d55543f854deaa0fedf248ba979d1cb4.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=false
|
||||
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
|
39
Scenes/Tiles/TurretTile.tscn
Normal file
@@ -0,0 +1,39 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://dbup2pvjl8het"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://n5g6i0uovxfk" path="res://Scripts/Tiles/TurretTile.cs" id="1_j3157"]
|
||||
[ext_resource type="Texture2D" uid="uid://ckssi7soymu7g" path="res://Scenes/Tiles/TurretTile.png" id="2_7ljeh"]
|
||||
[ext_resource type="PackedScene" uid="uid://erqawdsydh6a" path="res://Scenes/Entities/Bullet.tscn" id="2_gfad6"]
|
||||
[ext_resource type="Texture2D" uid="uid://dmbbkwgff7dej" path="res://Scenes/Tiles/TurretTileBarrel.png" id="3_gfad6"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_pndcb"]
|
||||
size = Vector2(54, 54)
|
||||
|
||||
[node name="Turret" type="StaticBody2D"]
|
||||
script = ExtResource("1_j3157")
|
||||
BulletScene = ExtResource("2_gfad6")
|
||||
BarrelTipPath = NodePath("Barrel/Marker2D")
|
||||
TileId = "turret"
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
scale = Vector2(0.1, 0.1)
|
||||
texture = ExtResource("2_7ljeh")
|
||||
|
||||
[node name="Barrel" type="Sprite2D" parent="."]
|
||||
position = Vector2(0, -3)
|
||||
scale = Vector2(0.08, 0.08)
|
||||
texture = ExtResource("3_gfad6")
|
||||
offset = Vector2(0, -54)
|
||||
|
||||
[node name="Marker2D" type="Marker2D" parent="Barrel"]
|
||||
position = Vector2(0, 37.5)
|
||||
scale = Vector2(12.5, 12.5)
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_pndcb")
|
||||
|
||||
[node name="ProgressOverlay" type="ColorRect" parent="."]
|
||||
visible = false
|
||||
offset_left = -27.0
|
||||
offset_top = -27.0
|
||||
offset_right = 27.0
|
||||
offset_bottom = 27.0
|
BIN
Scenes/Tiles/TurretTileBarrel.png
Normal file
After Width: | Height: | Size: 185 KiB |
34
Scenes/Tiles/TurretTileBarrel.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dmbbkwgff7dej"
|
||||
path="res://.godot/imported/TurretTileBarrel.png-a9f6a29579c44d5d6ed4cc8f6c09fd72.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Scenes/Tiles/TurretTileBarrel.png"
|
||||
dest_files=["res://.godot/imported/TurretTileBarrel.png-a9f6a29579c44d5d6ed4cc8f6c09fd72.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=false
|
||||
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
|
@@ -8,6 +8,7 @@ size = Vector2(54, 54)
|
||||
|
||||
[node name="WallTile" type="StaticBody2D"]
|
||||
script = ExtResource("1_ph7y3")
|
||||
TileId = "wall"
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
scale = Vector2(0.05, 0.05)
|
||||
|
97
Scripts/Entities/Bullet.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Godot;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.Entities;
|
||||
|
||||
public partial class Bullet : Area2D
|
||||
{
|
||||
[Export] public float Speed = 400.0f;
|
||||
[Export] public int Damage = 10;
|
||||
[Export] public float MaxDistance = 1000.0f;
|
||||
|
||||
private Vector2 _direction = Vector2.Right;
|
||||
private Vector2 _startPosition;
|
||||
private float _distanceTraveled = 0f;
|
||||
private bool _hasHit = false;
|
||||
private uint _ignoreCollisionLayer = 0; // Layer to ignore (will be set by turret)
|
||||
|
||||
public void Initialize(Vector2 direction, Vector2 position, float rotation, uint ignoreLayer = 0)
|
||||
{
|
||||
_direction = direction.Normalized();
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
_startPosition = position;
|
||||
_ignoreCollisionLayer = ignoreLayer;
|
||||
|
||||
// Connect the area entered signal
|
||||
BodyEntered += OnBodyEntered;
|
||||
AreaEntered += OnAreaEntered;
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
if (_hasHit) return;
|
||||
|
||||
// Move the bullet
|
||||
var movement = _direction * Speed * (float)delta;
|
||||
Position += movement;
|
||||
_distanceTraveled += movement.Length();
|
||||
|
||||
// Check if bullet has traveled max distance
|
||||
if (_distanceTraveled >= MaxDistance)
|
||||
{
|
||||
QueueFree();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBodyEntered(Node2D body)
|
||||
{
|
||||
HandleCollision(body);
|
||||
}
|
||||
|
||||
private void OnAreaEntered(Area2D area)
|
||||
{
|
||||
HandleCollision(area);
|
||||
}
|
||||
|
||||
private void HandleCollision(Node2D node)
|
||||
{
|
||||
if (_hasHit) return;
|
||||
|
||||
// Skip collision if it's on the ignore layer
|
||||
if (node is PhysicsBody2D physicsBody &&
|
||||
(physicsBody.CollisionLayer & _ignoreCollisionLayer) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_hasHit = true;
|
||||
|
||||
// If we hit an enemy, deal damage
|
||||
if (node is Enemy enemy)
|
||||
{
|
||||
// Get the global position where the bullet hit
|
||||
var hitPosition = GlobalPosition;
|
||||
enemy.TakeDamage(Damage, hitPosition);
|
||||
}
|
||||
|
||||
// Optional: Add hit effect here
|
||||
// CreateHitEffect();
|
||||
|
||||
// Remove the bullet
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
private void CreateHitEffect()
|
||||
{
|
||||
// You can add a hit effect here if desired
|
||||
// For example, a small explosion or impact sprite
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
// Clean up signal connections
|
||||
BodyEntered -= OnBodyEntered;
|
||||
AreaEntered -= OnAreaEntered;
|
||||
}
|
||||
}
|
1
Scripts/Entities/Bullet.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://vgx2a8gm7l8b
|
193
Scripts/Entities/Enemy.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using Godot;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.Entities;
|
||||
|
||||
public partial class Enemy : CharacterBody2D
|
||||
{
|
||||
public const string EnemyGroupName = "Enemy";
|
||||
|
||||
[Export] public float MoveSpeed = 150.0f;
|
||||
[Export] public float DetectionRadius = 300.0f;
|
||||
[Export] public float AttackRange = 50.0f;
|
||||
[Export] public int Damage = 10;
|
||||
[Export] public float AttackCooldown = 1.0f;
|
||||
[Export] public int MaxHealth = 100;
|
||||
[Export] public bool ShowDamageNumbers = true;
|
||||
|
||||
private Player _player;
|
||||
private float _attackTimer = 0;
|
||||
private bool _isPlayerInRange = false;
|
||||
private Area2D _detectionArea;
|
||||
private int _currentHealth;
|
||||
private ProgressBar _healthBar;
|
||||
|
||||
public int CurrentHealth
|
||||
{
|
||||
get => _currentHealth;
|
||||
private set
|
||||
{
|
||||
_currentHealth = Mathf.Clamp(value, 0, MaxHealth);
|
||||
UpdateHealthBar();
|
||||
|
||||
if (_currentHealth <= 0)
|
||||
{
|
||||
Die();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDead => _currentHealth <= 0;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_currentHealth = MaxHealth;
|
||||
|
||||
// Create health bar
|
||||
_healthBar = new ProgressBar
|
||||
{
|
||||
MaxValue = MaxHealth,
|
||||
Value = _currentHealth,
|
||||
Size = new Vector2(40, 4),
|
||||
ShowPercentage = false,
|
||||
Visible = false
|
||||
};
|
||||
|
||||
var healthBarContainer = new Control();
|
||||
healthBarContainer.AddChild(_healthBar);
|
||||
AddChild(healthBarContainer);
|
||||
healthBarContainer.Position = new Vector2(-20, -20); // Adjust position as needed
|
||||
|
||||
// Create detection area
|
||||
_detectionArea = new Area2D();
|
||||
var collisionShape = new CollisionShape2D();
|
||||
var shape = new CircleShape2D();
|
||||
shape.Radius = DetectionRadius;
|
||||
collisionShape.Shape = shape;
|
||||
_detectionArea.AddChild(collisionShape);
|
||||
AddChild(_detectionArea);
|
||||
|
||||
// Connect signals
|
||||
_detectionArea.BodyEntered += OnBodyEnteredDetection;
|
||||
_detectionArea.BodyExited += OnBodyExitedDetection;
|
||||
|
||||
AddToGroup(EnemyGroupName);
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (IsDead) return;
|
||||
|
||||
if (_player != null && _isPlayerInRange)
|
||||
{
|
||||
// Face the player
|
||||
LookAt(_player.GlobalPosition);
|
||||
|
||||
// Move towards player if not in attack range
|
||||
var direction = GlobalPosition.DirectionTo(_player.GlobalPosition);
|
||||
var distance = GlobalPosition.DistanceTo(_player.GlobalPosition);
|
||||
|
||||
if (distance > AttackRange)
|
||||
{
|
||||
Velocity = direction * MoveSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
Velocity = Vector2.Zero;
|
||||
TryAttackPlayer(delta);
|
||||
}
|
||||
|
||||
MoveAndSlide();
|
||||
}
|
||||
}
|
||||
|
||||
public void TakeDamage(int damage, Vector2? hitPosition = null)
|
||||
{
|
||||
if (IsDead) return;
|
||||
|
||||
CurrentHealth -= damage;
|
||||
|
||||
// Show damage number (optional)
|
||||
if (ShowDamageNumbers)
|
||||
{
|
||||
var damageLabel = new Label
|
||||
{
|
||||
Text = $"-{damage}",
|
||||
Position = hitPosition ?? GlobalPosition,
|
||||
ZIndex = 1000
|
||||
};
|
||||
|
||||
GetTree().CurrentScene.AddChild(damageLabel);
|
||||
|
||||
// Animate and remove damage number
|
||||
var tween = CreateTween();
|
||||
tween.TweenProperty(damageLabel, "position:y", damageLabel.Position.Y - 30, 0.5f);
|
||||
tween.TweenCallback(Callable.From(() => damageLabel.QueueFree())).SetDelay(0.5f);
|
||||
}
|
||||
|
||||
// Visual feedback
|
||||
var originalModulate = Modulate;
|
||||
Modulate = new Color(1, 0.5f, 0.5f); // Flash red
|
||||
|
||||
var tweenFlash = CreateTween();
|
||||
tweenFlash.TweenProperty(this, "modulate", originalModulate, 0.2f);
|
||||
}
|
||||
|
||||
private void UpdateHealthBar()
|
||||
{
|
||||
if (_healthBar != null)
|
||||
{
|
||||
_healthBar.Value = _currentHealth;
|
||||
_healthBar.Visible = _currentHealth < MaxHealth; // Only show when damaged
|
||||
}
|
||||
}
|
||||
|
||||
private void Die()
|
||||
{
|
||||
// Play death animation/sound
|
||||
// You can add a death animation here
|
||||
|
||||
// Disable collisions and hide
|
||||
SetProcess(false);
|
||||
SetPhysicsProcess(false);
|
||||
Hide();
|
||||
|
||||
// Queue free after a delay (for any death animation/sound to play)
|
||||
var timer = new Timer();
|
||||
AddChild(timer);
|
||||
timer.Timeout += () => QueueFree();
|
||||
timer.Start(0.5f);
|
||||
}
|
||||
|
||||
private void TryAttackPlayer(double delta)
|
||||
{
|
||||
if (IsDead) return;
|
||||
|
||||
_attackTimer += (float)delta;
|
||||
|
||||
if (_attackTimer >= AttackCooldown)
|
||||
{
|
||||
_attackTimer = 0;
|
||||
// Here you can implement the attack logic
|
||||
// For example: _player.TakeDamage(Damage);
|
||||
GD.Print("Enemy attacks player!");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBodyEnteredDetection(Node2D body)
|
||||
{
|
||||
if (body is Player player)
|
||||
{
|
||||
_player = player;
|
||||
_isPlayerInRange = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBodyExitedDetection(Node2D body)
|
||||
{
|
||||
if (body == _player)
|
||||
{
|
||||
_isPlayerInRange = false;
|
||||
Velocity = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
1
Scripts/Entities/Enemy.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cvsmy820b8dwl
|
@@ -1,12 +1,84 @@
|
||||
using System;
|
||||
using AceFieldNewHorizon.Scripts.System;
|
||||
using Godot;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.Entities;
|
||||
|
||||
public partial class Player : CharacterBody2D
|
||||
{
|
||||
[Export] public float Speed = 400.0f;
|
||||
[Export] public float MaxSpeed = 400.0f;
|
||||
[Export] public float SprintMultiplier = 1.8f; // 80% faster when sprinting
|
||||
[Export] public float Acceleration = 1500.0f;
|
||||
[Export] public float SprintAcceleration = 1800.0f; // Slightly faster acceleration when sprinting
|
||||
[Export] public float Deceleration = 1200.0f;
|
||||
[Export] public float RotationSpeed = 3.0f;
|
||||
[Export] public float MinZoom = 0.5f;
|
||||
[Export] public float MaxZoom = 2.0f;
|
||||
[Export] public float BaseZoomSpeed = 0.1f;
|
||||
[Export] public float MaxZoomSpeed = 0.5f;
|
||||
[Export] public float ZoomAcceleration = 0.05f;
|
||||
[Export] public float ZoomDecay = 0.9f;
|
||||
[Export] public float ZoomSmoothing = 10.0f;
|
||||
|
||||
[Export] public ResourceManager Inventory;
|
||||
|
||||
private Camera2D _camera;
|
||||
private Vector2 _cameraTargetZoom = Vector2.One;
|
||||
private float _currentZoomSpeed;
|
||||
private int _lastZoomDirection;
|
||||
private double _lastZoomTime;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_camera = GetNode<Camera2D>("Camera2D");
|
||||
_cameraTargetZoom = _camera.Zoom;
|
||||
|
||||
AddToGroup(ItemPickup.PickupGroupName);
|
||||
AddToGroup(NaturalResourceGenerator.ChunkTrackerGroupName);
|
||||
}
|
||||
|
||||
public override void _Input(InputEvent @event)
|
||||
{
|
||||
// Handle mouse wheel zoom
|
||||
if (@event is InputEventMouseButton mouseEvent)
|
||||
{
|
||||
switch (mouseEvent.ButtonIndex)
|
||||
{
|
||||
case MouseButton.WheelDown when mouseEvent.Pressed:
|
||||
HandleZoomInput(1);
|
||||
break;
|
||||
case MouseButton.WheelUp when mouseEvent.Pressed:
|
||||
HandleZoomInput(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleZoomInput(int direction)
|
||||
{
|
||||
var currentTime = Time.GetTicksMsec();
|
||||
|
||||
// If same direction as last time, accelerate
|
||||
if (direction == _lastZoomDirection && (currentTime - _lastZoomTime) < 300)
|
||||
{
|
||||
_currentZoomSpeed = Mathf.Min(_currentZoomSpeed + ZoomAcceleration, MaxZoomSpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentZoomSpeed = BaseZoomSpeed;
|
||||
}
|
||||
|
||||
_lastZoomDirection = direction;
|
||||
_lastZoomTime = currentTime;
|
||||
|
||||
// Apply zoom with current speed
|
||||
var zoomFactor = 1.0f + (_currentZoomSpeed * -direction);
|
||||
_cameraTargetZoom = _camera.Zoom * zoomFactor;
|
||||
|
||||
// Clamp target zoom
|
||||
_cameraTargetZoom.X = Mathf.Clamp(_cameraTargetZoom.X, MinZoom, MaxZoom);
|
||||
_cameraTargetZoom.Y = Mathf.Clamp(_cameraTargetZoom.Y, MinZoom, MaxZoom);
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
@@ -14,16 +86,65 @@ public partial class Player : CharacterBody2D
|
||||
var mousePos = GetGlobalMousePosition();
|
||||
var direction = GlobalPosition.DirectionTo(mousePos);
|
||||
Rotation = direction.Angle();
|
||||
|
||||
// Smoothly interpolate to target zoom
|
||||
var deltaF = (float)delta;
|
||||
_camera.Zoom = _camera.Zoom.Lerp(_cameraTargetZoom, ZoomSmoothing * deltaF);
|
||||
|
||||
// Decay zoom speed when not zooming
|
||||
if ((Time.GetTicksMsec() - _lastZoomTime) > 300)
|
||||
{
|
||||
_currentZoomSpeed = Mathf.Max(_currentZoomSpeed * ZoomDecay, BaseZoomSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// Get movement input
|
||||
var moveForward = Input.GetActionStrength("move_up") - Input.GetActionStrength("move_down");
|
||||
var moveHorizontal = Input.GetActionStrength("move_right") - Input.GetActionStrength("move_left");
|
||||
var isSprinting = Input.IsActionPressed("move_sprint");
|
||||
|
||||
// Calculate movement direction based on rotation
|
||||
Velocity = Vector2.Right.Rotated(Rotation) * moveForward * Speed;
|
||||
// Calculate movement parameters based on sprint state
|
||||
var currentMaxSpeed = isSprinting ? MaxSpeed * SprintMultiplier : MaxSpeed;
|
||||
var currentAcceleration = isSprinting ? SprintAcceleration : Acceleration;
|
||||
|
||||
// Calculate desired movement direction
|
||||
var forwardVector = Vector2.Right.Rotated(Rotation);
|
||||
var rightVector = new Vector2(-forwardVector.Y, forwardVector.X);
|
||||
|
||||
// Calculate target velocity based on input
|
||||
var targetVelocity = (forwardVector * moveForward + rightVector * moveHorizontal).Normalized() * currentMaxSpeed;
|
||||
|
||||
// Apply acceleration or deceleration
|
||||
var currentSpeed = Velocity.Length();
|
||||
var isAccelerating = targetVelocity != Vector2.Zero;
|
||||
|
||||
if (isAccelerating)
|
||||
{
|
||||
// Accelerate towards target velocity
|
||||
Velocity = Velocity.MoveToward(targetVelocity, (float)(currentAcceleration * delta));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply deceleration when no input
|
||||
if (currentSpeed > 0)
|
||||
{
|
||||
var decelAmount = (float)(Deceleration * delta);
|
||||
if (currentSpeed <= decelAmount)
|
||||
Velocity = Vector2.Zero;
|
||||
else
|
||||
Velocity = Velocity.Normalized() * (currentSpeed - decelAmount);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the movement
|
||||
MoveAndSlide();
|
||||
}
|
||||
|
||||
public void AddItem(string itemId, int quantity)
|
||||
{
|
||||
Inventory.AddResource(itemId, quantity);
|
||||
GD.Print($"[Player] Picked up {quantity} x {itemId}. Total: {Inventory.GetResourceAmount(itemId)}");
|
||||
}
|
||||
}
|
@@ -1,17 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Godot;
|
||||
using static System.Int32;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.System;
|
||||
|
||||
public partial class BuildingRegistry : Node
|
||||
public enum RotationDirection
|
||||
{
|
||||
public record BuildingData(
|
||||
Up, // 0 degrees
|
||||
Right, // 90 degrees
|
||||
Down, // 180 degrees
|
||||
Left // 270 degrees
|
||||
}
|
||||
|
||||
public record BuildingData(
|
||||
PackedScene Scene,
|
||||
Dictionary<string, int> Cost,
|
||||
int Durability,
|
||||
float BuildTime
|
||||
);
|
||||
GridLayer Layer,
|
||||
float BuildTime,
|
||||
Vector2I Size = default,
|
||||
RotationDirection[] AllowedRotations = null
|
||||
)
|
||||
{
|
||||
public Vector2I Size { get; } = Size == default ? Vector2I.One : Size;
|
||||
public RotationDirection[] AllowedRotations { get; } =
|
||||
AllowedRotations ?? [RotationDirection.Up, RotationDirection.Right, RotationDirection.Down, RotationDirection.Left
|
||||
];
|
||||
|
||||
public bool IsRotationAllowed(float degrees)
|
||||
{
|
||||
var direction = (RotationDirection)((Mathf.RoundToInt(degrees / 90f) % 4 + 4) % 4);
|
||||
return AllowedRotations.Contains(direction);
|
||||
}
|
||||
|
||||
public Vector2I GetRotatedSize(float degrees)
|
||||
{
|
||||
return (Mathf.RoundToInt(degrees / 90f) % 2) == 0 ? Size : new Vector2I(Size.Y, Size.X);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class BuildingRegistry : Node
|
||||
{
|
||||
private Dictionary<string, BuildingData> _registry = new();
|
||||
|
||||
[Export] public string JsonPath { get; set; } = "res://Data/Buildings.json";
|
||||
@@ -41,11 +71,11 @@ public partial class BuildingRegistry : Node
|
||||
return;
|
||||
}
|
||||
|
||||
var dict = (Godot.Collections.Dictionary)json.Data;
|
||||
var dict = json.Data.AsGodotDictionary();
|
||||
|
||||
foreach (string key in dict.Keys)
|
||||
{
|
||||
// Each entry is a Dictionary with keys: "scene", "cost", "durability", "buildTime"
|
||||
// Each entry is a Dictionary with keys: "scene", "cost", "durability", "buildTime", "size", "allowedRotations"
|
||||
var buildingDict = dict[key].AsGodotDictionary();
|
||||
|
||||
// Parse scene
|
||||
@@ -72,12 +102,22 @@ public partial class BuildingRegistry : Node
|
||||
{
|
||||
int val;
|
||||
var obj = costDict[mat];
|
||||
if (obj.VariantType == Variant.Type.PackedInt64Array)
|
||||
switch (obj.VariantType)
|
||||
{
|
||||
case Variant.Type.PackedInt64Array:
|
||||
val = (int)obj.AsInt64();
|
||||
else if (obj.VariantType == Variant.Type.PackedInt32Array)
|
||||
break;
|
||||
case Variant.Type.PackedInt32Array:
|
||||
val = obj.AsInt32();
|
||||
else
|
||||
int.TryParse(obj.ToString(), out val);
|
||||
break;
|
||||
case Variant.Type.Float:
|
||||
val = (int)obj.AsDouble();
|
||||
break;
|
||||
default:
|
||||
if (!TryParse(obj.ToString(), out val))
|
||||
GD.PrintErr($"[BuildingRegistry] Failed to parse cost for '{key}': {obj.ToString()}");
|
||||
break;
|
||||
}
|
||||
cost[mat] = val;
|
||||
}
|
||||
}
|
||||
@@ -91,7 +131,7 @@ public partial class BuildingRegistry : Node
|
||||
else if (dObj.VariantType == Variant.Type.PackedInt32Array)
|
||||
durability = dObj.AsInt32();
|
||||
else
|
||||
int.TryParse(dObj.ToString(), out durability);
|
||||
TryParse(dObj.ToString(), out durability);
|
||||
}
|
||||
|
||||
// Parse buildTime
|
||||
@@ -115,7 +155,36 @@ public partial class BuildingRegistry : Node
|
||||
}
|
||||
}
|
||||
|
||||
var buildingData = new BuildingData(scene, cost, durability, buildTime);
|
||||
// Parse size
|
||||
var size = Vector2I.One;
|
||||
if (buildingDict.TryGetValue("size", out var sObj))
|
||||
{
|
||||
var sizeArray = sObj.AsGodotArray();
|
||||
if (sizeArray.Count == 2)
|
||||
{
|
||||
size = new Vector2I((int)sizeArray[0].AsInt64(), (int)sizeArray[1].AsInt64());
|
||||
}
|
||||
}
|
||||
|
||||
// Parse allowedRotations
|
||||
RotationDirection[] allowedRotations = null;
|
||||
if (buildingDict.TryGetValue("allowedRotations", out var arObj))
|
||||
{
|
||||
var arArray = arObj.AsGodotArray();
|
||||
allowedRotations = new RotationDirection[arArray.Count];
|
||||
for (var i = 0; i < arArray.Count; i++)
|
||||
{
|
||||
allowedRotations[i] = (RotationDirection)arArray[i].AsInt32();
|
||||
}
|
||||
}
|
||||
|
||||
var layer = GridLayer.Building;
|
||||
if (buildingDict.TryGetValue("layer", out var lObj))
|
||||
{
|
||||
layer = (GridLayer)lObj.AsInt32();
|
||||
}
|
||||
|
||||
var buildingData = new BuildingData(scene, cost, durability, layer, buildTime, size, allowedRotations);
|
||||
_registry[key] = buildingData;
|
||||
GD.Print($"[BuildingRegistry] Loaded building '{key}' from {scenePath}");
|
||||
}
|
||||
|
@@ -1,31 +1,107 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Godot;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.System;
|
||||
|
||||
public enum GridLayer
|
||||
{
|
||||
Ground, // Base layer (e.g., terrain, floors)
|
||||
Building, // Main building layer
|
||||
Decoration // Additional layer for decorations, effects, etc.
|
||||
}
|
||||
|
||||
public partial class GridManager : Node
|
||||
{
|
||||
private Dictionary<Vector2I, Node2D> _grid = new();
|
||||
private Dictionary<GridLayer, Dictionary<Vector2I, (Node2D Building, Vector2I Size, float Rotation)>> _layers =
|
||||
new();
|
||||
|
||||
public bool IsCellFree(Vector2I cell)
|
||||
public GridManager()
|
||||
{
|
||||
return !_grid.ContainsKey(cell);
|
||||
// Initialize all layers
|
||||
foreach (GridLayer layer in Enum.GetValues(typeof(GridLayer)))
|
||||
_layers[layer] = new Dictionary<Vector2I, (Node2D, Vector2I, float)>();
|
||||
}
|
||||
|
||||
public void OccupyCell(Vector2I cell, Node2D building)
|
||||
public bool IsAreaFree(Vector2I topLeft, Vector2I size, float rotation, GridLayer layer = GridLayer.Building)
|
||||
{
|
||||
_grid[cell] = building;
|
||||
var occupiedCells = GridUtils.GetOccupiedCells(topLeft, size, rotation);
|
||||
return occupiedCells.All(cell => !_layers[layer].ContainsKey(cell));
|
||||
}
|
||||
|
||||
public void FreeCell(Vector2I cell)
|
||||
public void OccupyArea(Vector2I topLeft, Node2D building, Vector2I size, float rotation,
|
||||
GridLayer layer = GridLayer.Building)
|
||||
{
|
||||
_grid.Remove(cell);
|
||||
var occupiedCells = GridUtils.GetOccupiedCells(topLeft, size, rotation);
|
||||
foreach (var cell in occupiedCells)
|
||||
{
|
||||
_layers[layer][cell] = (building, size, rotation);
|
||||
}
|
||||
}
|
||||
|
||||
public Node2D? GetBuildingAtCell(Vector2I cell)
|
||||
public void FreeArea(Vector2I topLeft, Vector2I size, float rotation, GridLayer layer = GridLayer.Building)
|
||||
{
|
||||
return _grid.GetValueOrDefault(cell);
|
||||
var occupiedCells = GridUtils.GetOccupiedCells(topLeft, size, rotation);
|
||||
foreach (var cell in occupiedCells)
|
||||
{
|
||||
if (_layers[layer].ContainsKey(cell))
|
||||
_layers[layer].Remove(cell);
|
||||
}
|
||||
}
|
||||
|
||||
public Node2D? GetTileAtCell(Vector2I cell, GridLayer layer = GridLayer.Building)
|
||||
{
|
||||
return _layers[layer].TryGetValue(cell, out var data) ? data.Building : null;
|
||||
}
|
||||
|
||||
public bool IsAnyCellOccupied(Vector2I cell, params GridLayer[] layers)
|
||||
{
|
||||
if (layers.Length == 0)
|
||||
{
|
||||
// Check all layers if none specified
|
||||
foreach (var layer in _layers.Values)
|
||||
{
|
||||
if (layer.ContainsKey(cell)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
if (_layers[layer].ContainsKey(cell)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsAreaOccupied(Vector2I topLeft, Vector2I size, float rotation, params GridLayer[] layers)
|
||||
{
|
||||
var occupiedCells = GridUtils.GetOccupiedCells(topLeft, size, rotation);
|
||||
|
||||
if (layers.Length == 0)
|
||||
{
|
||||
// Check all layers if none specified
|
||||
foreach (var cell in occupiedCells)
|
||||
{
|
||||
foreach (var layer in _layers.Values)
|
||||
if (layer.ContainsKey(cell))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var cell in occupiedCells)
|
||||
{
|
||||
foreach (var layer in layers)
|
||||
if (_layers[layer].ContainsKey(cell))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,4 +124,26 @@ public static class GridUtils
|
||||
cell.Y * TileSize
|
||||
);
|
||||
}
|
||||
|
||||
public static Vector2 GetCenterOffset(Vector2I size, float rotation)
|
||||
{
|
||||
var rotatedSize = (Mathf.RoundToInt(rotation / 90f) % 2) == 0 ? size : new Vector2I(size.Y, size.X);
|
||||
return new Vector2(rotatedSize.X * TileSize / 2f, rotatedSize.Y * TileSize / 2f);
|
||||
}
|
||||
|
||||
public static List<Vector2I> GetOccupiedCells(Vector2I topLeft, Vector2I size, float rotation)
|
||||
{
|
||||
var occupiedCells = new List<Vector2I>();
|
||||
var rotatedSize = (Mathf.RoundToInt(rotation / 90f) % 2) == 0 ? size : new Vector2I(size.Y, size.X);
|
||||
|
||||
for (int x = 0; x < rotatedSize.X; x++)
|
||||
{
|
||||
for (int y = 0; y < rotatedSize.Y; y++)
|
||||
{
|
||||
occupiedCells.Add(new Vector2I(topLeft.X + x, topLeft.Y + y));
|
||||
}
|
||||
}
|
||||
|
||||
return occupiedCells;
|
||||
}
|
||||
}
|
187
Scripts/System/ItemPickup.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System.Collections;
|
||||
using AceFieldNewHorizon.Scripts.Entities;
|
||||
using Godot;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.System;
|
||||
|
||||
public partial class ItemPickup : Area2D
|
||||
{
|
||||
[Signal]
|
||||
public delegate void StackMergedEventHandler(string itemId, int totalQuantity);
|
||||
|
||||
public const string PickupGroupName = "ItemPickupTarget";
|
||||
|
||||
[Export] public string ItemId { get; set; } = "";
|
||||
[Export] public int Quantity { get; set; } = 1;
|
||||
[Export] public bool Infinite { get; set; } = false;
|
||||
[Export] public float MagnetRange { get; set; } = 64f;
|
||||
|
||||
public int GetItemQuantity(string itemId)
|
||||
{
|
||||
if (itemId == ItemId)
|
||||
return Quantity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void SetItemQuantity(string itemId, int newQuantity)
|
||||
{
|
||||
if (itemId == ItemId)
|
||||
{
|
||||
Quantity = newQuantity;
|
||||
// Update the quantity label if it exists
|
||||
if (_quantityLabel != null)
|
||||
{
|
||||
if (Quantity > 1)
|
||||
_quantityLabel.Text = Quantity.ToString();
|
||||
else
|
||||
_quantityLabel.Text = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasItem(string itemId)
|
||||
{
|
||||
return itemId == ItemId;
|
||||
}
|
||||
|
||||
public void AddItem(string itemId, int amount)
|
||||
{
|
||||
if (itemId == ItemId)
|
||||
{
|
||||
Quantity += amount;
|
||||
// Update the quantity label if it exists
|
||||
if (_quantityLabel != null)
|
||||
{
|
||||
if (Quantity > 1)
|
||||
_quantityLabel.Text = Quantity.ToString();
|
||||
else
|
||||
_quantityLabel.Text = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite2D _sprite;
|
||||
private Label _quantityLabel;
|
||||
private Sprite2D _shadowSprite;
|
||||
private Node2D _playerTarget;
|
||||
|
||||
// Called when the node enters the scene tree
|
||||
public override void _Ready()
|
||||
{
|
||||
BodyEntered += OnEntered;
|
||||
AreaEntered += OnEntered;
|
||||
|
||||
_sprite = GetNode<Sprite2D>("Sprite2D");
|
||||
UpdateTexture();
|
||||
|
||||
// Get the Label node for quantity
|
||||
if (HasNode("Label"))
|
||||
{
|
||||
_quantityLabel = GetNode<Label>("Label");
|
||||
if (Quantity > 1)
|
||||
_quantityLabel.Text = Quantity.ToString();
|
||||
else
|
||||
_quantityLabel.Text = string.Empty;
|
||||
}
|
||||
|
||||
// Add or update shadow sprite
|
||||
if (HasNode("ShadowSprite"))
|
||||
{
|
||||
_shadowSprite = GetNode<Sprite2D>("ShadowSprite");
|
||||
_shadowSprite.Texture = _sprite.Texture;
|
||||
_shadowSprite.Modulate = new Color(0, 0, 0, 0.5f);
|
||||
_shadowSprite.Position = _sprite.Position + new Vector2(0, 6);
|
||||
_shadowSprite.ZIndex = _sprite.ZIndex - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_shadowSprite = new Sprite2D();
|
||||
_shadowSprite.Scale = _sprite.Scale;
|
||||
_shadowSprite.Name = "ShadowSprite";
|
||||
_shadowSprite.Texture = _sprite.Texture;
|
||||
_shadowSprite.Modulate = new Color(0, 0, 0, 0.5f);
|
||||
_shadowSprite.Position = _sprite.Position + new Vector2(0, 6);
|
||||
_shadowSprite.ZIndex = _sprite.ZIndex - 1;
|
||||
AddChild(_shadowSprite);
|
||||
}
|
||||
|
||||
_playerTarget = GetTree().GetFirstNodeInGroup(PickupGroupName) as Node2D;
|
||||
if (_playerTarget == null)
|
||||
_playerTarget = GetTree().GetFirstNodeInGroup("Player") as Node2D;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (_playerTarget != null)
|
||||
{
|
||||
var distance = Position.DistanceTo(_playerTarget.Position);
|
||||
const float speed = 10f;
|
||||
if (distance <= MagnetRange)
|
||||
Position = Position.Lerp(_playerTarget.Position, (float)delta * speed);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTexture()
|
||||
{
|
||||
var file = FileAccess.Open("res://Data/ItemTextures.json", FileAccess.ModeFlags.Read);
|
||||
if (file == null)
|
||||
{
|
||||
GD.PrintErr("Failed to open ItemTextures.json");
|
||||
return;
|
||||
}
|
||||
|
||||
var text = file.GetAsText();
|
||||
file.Close();
|
||||
|
||||
var json = new Json();
|
||||
var err = json.Parse(text);
|
||||
if (err != Error.Ok)
|
||||
{
|
||||
GD.PrintErr(json.GetErrorMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
var dict = json.Data.AsGodotDictionary();
|
||||
if (!dict.TryGetValue(ItemId, out var value))
|
||||
return;
|
||||
|
||||
var texturePath = value.AsString();
|
||||
if (string.IsNullOrEmpty(texturePath))
|
||||
return;
|
||||
|
||||
var texture = GD.Load<Texture2D>(texturePath);
|
||||
if (texture == null)
|
||||
return;
|
||||
|
||||
_sprite.Texture = texture;
|
||||
}
|
||||
|
||||
private void OnEntered(Node body)
|
||||
{
|
||||
if (body is ItemPickup itemStack)
|
||||
{
|
||||
// Only process the merge for the item with the lower instance ID to prevent double merging
|
||||
if (itemStack.ItemId != ItemId || GetInstanceId() > body.GetInstanceId())
|
||||
return;
|
||||
|
||||
// Get current quantity and add to it
|
||||
var currentQuantity = itemStack.GetItemQuantity(ItemId);
|
||||
var newQuantity = currentQuantity + Quantity;
|
||||
itemStack.SetItemQuantity(ItemId, newQuantity);
|
||||
|
||||
// Emit signal for stack merge
|
||||
EmitSignal(nameof(StackMerged), ItemId, newQuantity);
|
||||
QueueFree();
|
||||
}
|
||||
else if (body.IsInGroup(PickupGroupName))
|
||||
{
|
||||
if (body is Player player)
|
||||
{
|
||||
player.AddItem(ItemId, Quantity);
|
||||
}
|
||||
|
||||
if (!Infinite)
|
||||
QueueFree();
|
||||
}
|
||||
}
|
||||
}
|
1
Scripts/System/ItemPickup.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://qgcue2doj2lf
|
493
Scripts/System/NaturalResourceGenerator.cs
Normal file
@@ -0,0 +1,493 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using Godot;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AceFieldNewHorizon.Scripts.Entities;
|
||||
using AceFieldNewHorizon.Scripts.Tiles;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.System;
|
||||
|
||||
public partial class NaturalResourceGenerator : Node2D
|
||||
{
|
||||
public const string ChunkTrackerGroupName = "NrgTrackingTarget";
|
||||
|
||||
[Export] public GridManager Grid { get; set; }
|
||||
[Export] public BuildingRegistry Registry { get; set; }
|
||||
|
||||
[Export] public int ChunkSize = 16;
|
||||
[Export] public int LoadDistance = 2; // Number of chunks to load in each direction
|
||||
[Export] public float StoneDensity = 0.1f;
|
||||
[Export] public float IronDensity = 0.03f;
|
||||
[Export] public int MinStoneVeinSize = 1;
|
||||
[Export] public int MaxStoneVeinSize = 5;
|
||||
[Export] public int MinIronVeinSize = 1;
|
||||
[Export] public int MaxIronVeinSize = 3;
|
||||
[Export] public int Seed;
|
||||
|
||||
[Export] public bool SpawnEnemyNest = true;
|
||||
[Export] public int MinDistanceFromOrigin = 20; // Minimum distance from world origin (0,0)
|
||||
[Export] public int MaxDistanceFromOrigin = 50; // Maximum distance from world origin
|
||||
|
||||
private const string LogPrefix = "[NaturalGeneration]";
|
||||
|
||||
private RandomNumberGenerator _rng;
|
||||
private readonly Dictionary<Vector2I, ChunkData> _loadedChunks = new();
|
||||
private Vector2I _lastPlayerChunk = new(-1000, -1000);
|
||||
|
||||
private Player _player;
|
||||
|
||||
private readonly ConcurrentQueue<Action> _mainThreadActions = new();
|
||||
private Task _generationTask;
|
||||
private bool _isRunning = true;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_rng = new RandomNumberGenerator();
|
||||
_rng.Seed = (ulong)(Seed != 0 ? Seed : (int)GD.Randi());
|
||||
|
||||
// Test if building registry is assigned
|
||||
if (Registry == null)
|
||||
{
|
||||
GD.PrintErr($"{LogPrefix} BuildingRegistry is not assigned!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Test if enemy_nest is in the registry
|
||||
var testBuilding = Registry.GetBuilding("enemy_nest");
|
||||
if (testBuilding == null)
|
||||
{
|
||||
GD.PrintErr($"{LogPrefix} 'enemy_nest' is not found in BuildingRegistry!");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print($"{LogPrefix} Found enemy_nest in registry!");
|
||||
}
|
||||
|
||||
GD.Print($"{LogPrefix} NaturalResourceGenerator ready, SpawnEnemyNest = {SpawnEnemyNest}");
|
||||
|
||||
if (SpawnEnemyNest)
|
||||
{
|
||||
GD.Print($"{LogPrefix} Attempting to spawn enemy nest...");
|
||||
SpawnRandomEnemyNest();
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
// Process any pending main thread actions
|
||||
while (_mainThreadActions.TryDequeue(out var action))
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
|
||||
// Rest of your existing _Process code
|
||||
_player = GetTree().GetFirstNodeInGroup(ChunkTrackerGroupName) as Player;
|
||||
if (_player != null)
|
||||
{
|
||||
UpdatePlayerPosition(_player.GlobalPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr($"{LogPrefix} Player not found in group: {ChunkTrackerGroupName}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePlayerPosition(Vector2 playerPosition)
|
||||
{
|
||||
var playerChunk = WorldToChunkCoords(playerPosition);
|
||||
|
||||
if (playerChunk == _lastPlayerChunk) return;
|
||||
_lastPlayerChunk = playerChunk;
|
||||
|
||||
// Start generation in background task if not already running
|
||||
if (_generationTask == null || _generationTask.IsCompleted)
|
||||
{
|
||||
_generationTask = Task.Run(() => GenerateChunksAroundPlayer(playerChunk));
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateChunksAroundPlayer(Vector2I playerChunk)
|
||||
{
|
||||
var chunksToLoad = new List<Vector2I>();
|
||||
|
||||
// Determine which chunks to load/unload
|
||||
for (int x = -LoadDistance; x <= LoadDistance; x++)
|
||||
{
|
||||
for (int y = -LoadDistance; y <= LoadDistance; y++)
|
||||
{
|
||||
var chunkPos = new Vector2I(playerChunk.X + x, playerChunk.Y + y);
|
||||
if (!_loadedChunks.ContainsKey(chunkPos))
|
||||
{
|
||||
chunksToLoad.Add(chunkPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate chunks in background
|
||||
foreach (var chunkPos in chunksToLoad)
|
||||
{
|
||||
if (!_isRunning) return;
|
||||
GenerateChunkInBackground(chunkPos);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateChunkInBackground(Vector2I chunkPos)
|
||||
{
|
||||
// Skip if chunk was loaded while we were waiting
|
||||
if (_loadedChunks.ContainsKey(chunkPos)) return;
|
||||
|
||||
var chunkData = new ChunkData();
|
||||
var chunkWorldPos = ChunkToWorldCoords(chunkPos);
|
||||
|
||||
// Generate ground tiles
|
||||
for (int x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (int y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
|
||||
_mainThreadActions.Enqueue(() => PlaceTile("ground", cell));
|
||||
}
|
||||
}
|
||||
|
||||
// Generate stone veins
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
|
||||
if (_rng.Randf() < StoneDensity)
|
||||
{
|
||||
var veinSize = _rng.RandiRange(MinStoneVeinSize, MaxStoneVeinSize);
|
||||
PlaceVeinInBackground(cell, "stone", veinSize, chunkData.StoneTiles);
|
||||
}
|
||||
else if (_rng.Randf() < IronDensity)
|
||||
{
|
||||
var veinSize = _rng.RandiRange(MinIronVeinSize, MaxIronVeinSize);
|
||||
PlaceVeinInBackground(cell, "ore_iron", veinSize, chunkData.IronTiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add chunk data to dictionary on main thread
|
||||
_mainThreadActions.Enqueue(() => { _loadedChunks[chunkPos] = chunkData; });
|
||||
}
|
||||
|
||||
private void PlaceVeinInBackground(Vector2I startCell, string tileType, int maxSize, List<Vector2I> tileList)
|
||||
{
|
||||
var placed = new HashSet<Vector2I>();
|
||||
var queue = new Queue<Vector2I>();
|
||||
queue.Enqueue(startCell);
|
||||
|
||||
int placedCount = 0;
|
||||
|
||||
while (queue.Count > 0 && placedCount < maxSize && _isRunning)
|
||||
{
|
||||
var cell = queue.Dequeue();
|
||||
if (placed.Contains(cell)) continue;
|
||||
|
||||
// Schedule tile placement on main thread
|
||||
_mainThreadActions.Enqueue(() =>
|
||||
{
|
||||
if (PlaceTile(tileType, cell))
|
||||
{
|
||||
tileList.Add(cell);
|
||||
}
|
||||
});
|
||||
|
||||
placed.Add(cell);
|
||||
placedCount++;
|
||||
|
||||
// Add adjacent cells
|
||||
var directions = new[] { Vector2I.Up, Vector2I.Right, Vector2I.Down, Vector2I.Left };
|
||||
foreach (var dir in directions)
|
||||
{
|
||||
var newCell = cell + dir;
|
||||
if (!placed.Contains(newCell))
|
||||
{
|
||||
queue.Enqueue(newCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
// Clean up
|
||||
_isRunning = false;
|
||||
_generationTask?.Wait(); // Wait for current generation to finish
|
||||
base._ExitTree();
|
||||
}
|
||||
|
||||
private void GenerateChunk(Vector2I chunkPos)
|
||||
{
|
||||
GD.Print($"{LogPrefix} Generating chunk at {chunkPos}");
|
||||
var chunkData = new ChunkData();
|
||||
var chunkWorldPos = ChunkToWorldCoords(chunkPos);
|
||||
|
||||
// First, place ground tiles
|
||||
for (int x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (int y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
|
||||
if (!PlaceTile("ground", cell))
|
||||
{
|
||||
GD.PrintErr($"{LogPrefix} Failed to place ground at {cell}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then generate stone veins
|
||||
var stoneVeins = 0;
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
|
||||
if (_rng.Randf() < StoneDensity)
|
||||
{
|
||||
var veinSize = _rng.RandiRange(MinStoneVeinSize, MaxStoneVeinSize);
|
||||
GD.Print($"{LogPrefix} Attempting to place stone vein at {cell} with size {veinSize}");
|
||||
PlaceVein(cell, "stone", veinSize, chunkData.StoneTiles);
|
||||
stoneVeins++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GD.Print($"{LogPrefix} Placed {stoneVeins} stone veins in chunk {chunkPos}");
|
||||
|
||||
// Generate iron veins within stone
|
||||
int ironVeins = 0;
|
||||
foreach (var stoneCell in new List<Vector2I>(chunkData.StoneTiles))
|
||||
{
|
||||
if (_rng.Randf() < IronDensity)
|
||||
{
|
||||
var veinSize = _rng.RandiRange(MinIronVeinSize, MaxIronVeinSize);
|
||||
GD.Print($"{LogPrefix} Attempting to place iron vein at {stoneCell} with size {veinSize}");
|
||||
PlaceVein(stoneCell, "ore_iron", veinSize, chunkData.IronTiles);
|
||||
ironVeins++;
|
||||
}
|
||||
}
|
||||
|
||||
GD.Print($"{LogPrefix} Placed {ironVeins} iron veins in chunk {chunkPos}");
|
||||
|
||||
_loadedChunks[chunkPos] = chunkData;
|
||||
}
|
||||
|
||||
private void UnloadChunk(Vector2I chunkPos)
|
||||
{
|
||||
GD.Print($"{LogPrefix} Unloading chunk at {chunkPos}");
|
||||
if (!_loadedChunks.TryGetValue(chunkPos, out var chunkData)) return;
|
||||
|
||||
// Remove all tiles in this chunk
|
||||
var chunkWorldPos = ChunkToWorldCoords(chunkPos);
|
||||
for (int x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (int y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
|
||||
// Free a 1x1 area for each cell
|
||||
Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground);
|
||||
}
|
||||
}
|
||||
|
||||
_loadedChunks.Remove(chunkPos);
|
||||
}
|
||||
|
||||
private Vector2I WorldToChunkCoords(Vector2 worldPos)
|
||||
{
|
||||
var cell = GridUtils.WorldToGrid(worldPos);
|
||||
return new Vector2I(
|
||||
(int)Mathf.Floor((float)cell.X / ChunkSize),
|
||||
(int)Mathf.Floor((float)cell.Y / ChunkSize)
|
||||
);
|
||||
}
|
||||
|
||||
private Vector2I ChunkToWorldCoords(Vector2I chunkPos)
|
||||
{
|
||||
return new Vector2I(
|
||||
chunkPos.X * ChunkSize,
|
||||
chunkPos.Y * ChunkSize
|
||||
);
|
||||
}
|
||||
|
||||
private bool IsInLoadedChunk(Vector2I cell)
|
||||
{
|
||||
// Check the chunk where the cell is located
|
||||
var chunkPos = new Vector2I(
|
||||
(int)Mathf.Floor((float)cell.X / ChunkSize),
|
||||
(int)Mathf.Floor((float)cell.Y / ChunkSize)
|
||||
);
|
||||
|
||||
// Also check adjacent chunks since veins can cross chunk boundaries
|
||||
for (var dx = -1; dx <= 1; dx++)
|
||||
{
|
||||
for (var dy = -1; dy <= 1; dy++)
|
||||
{
|
||||
var checkPos = new Vector2I(chunkPos.X + dx, chunkPos.Y + dy);
|
||||
if (_loadedChunks.ContainsKey(checkPos))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void PlaceVein(Vector2I startCell, string tileType, int maxSize, List<Vector2I> tileList)
|
||||
{
|
||||
GD.Print($"{LogPrefix} Starting to place vein of type {tileType} at {startCell} with max size {maxSize}");
|
||||
|
||||
var placed = new HashSet<Vector2I>();
|
||||
var queue = new Queue<Vector2I>();
|
||||
queue.Enqueue(startCell);
|
||||
|
||||
int placedCount = 0;
|
||||
|
||||
while (queue.Count > 0 && placedCount < maxSize)
|
||||
{
|
||||
var cell = queue.Dequeue();
|
||||
|
||||
// Skip if already placed or out of bounds
|
||||
if (placed.Contains(cell))
|
||||
{
|
||||
GD.Print($"{LogPrefix} Skipping cell {cell} - already placed");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsInLoadedChunk(cell))
|
||||
{
|
||||
GD.Print($"{LogPrefix} Skipping cell {cell} - out of bounds");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to place the tile
|
||||
if (PlaceTile(tileType, cell))
|
||||
{
|
||||
tileList.Add(cell);
|
||||
placed.Add(cell);
|
||||
placedCount++;
|
||||
// GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell} ({placedCount}/{maxSize})");
|
||||
|
||||
// Add adjacent cells to queue
|
||||
var directions = new[] { Vector2I.Up, Vector2I.Right, Vector2I.Down, Vector2I.Left };
|
||||
foreach (var dir in directions)
|
||||
{
|
||||
var newCell = cell + dir;
|
||||
if (!placed.Contains(newCell))
|
||||
{
|
||||
queue.Enqueue(newCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print($"{LogPrefix} Failed to place {tileType} at {cell}");
|
||||
}
|
||||
}
|
||||
|
||||
GD.Print($"{LogPrefix} Finished placing vein - placed {placedCount}/{maxSize} {tileType} tiles");
|
||||
}
|
||||
|
||||
private void SpawnRandomEnemyNest()
|
||||
{
|
||||
// Generate a random position within the specified distance from origin
|
||||
var angle = _rng.Randf() * Mathf.Pi * 2;
|
||||
var distance = _rng.RandfRange(MinDistanceFromOrigin, MaxDistanceFromOrigin);
|
||||
var offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * distance;
|
||||
var nestPosition = new Vector2I((int)offset.X, (int)offset.Y);
|
||||
|
||||
// Try to find a valid position for the nest
|
||||
int attempts = 0;
|
||||
const int maxAttempts = 10;
|
||||
|
||||
while (attempts < maxAttempts)
|
||||
{
|
||||
if (PlaceTile("enemy_nest", nestPosition))
|
||||
{
|
||||
GD.Print($"{LogPrefix} Placed enemy nest at {nestPosition}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Try a different position if placement failed
|
||||
angle = _rng.Randf() * Mathf.Pi * 2;
|
||||
distance = _rng.RandfRange(MinDistanceFromOrigin, MaxDistanceFromOrigin);
|
||||
offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * distance;
|
||||
nestPosition = new Vector2I((int)offset.X, (int)offset.Y);
|
||||
attempts++;
|
||||
}
|
||||
|
||||
GD.PrintErr($"{LogPrefix} Failed to place enemy nest after {maxAttempts} attempts");
|
||||
}
|
||||
|
||||
private bool PlaceTile(string tileType, Vector2I cell)
|
||||
{
|
||||
try
|
||||
{
|
||||
// For enemy nest, we want to place it on top of existing ground
|
||||
if (tileType != "enemy_nest")
|
||||
{
|
||||
// Original behavior for other tile types
|
||||
Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For enemy nest, check if there's ground below (skip for now)
|
||||
GD.Print($"{LogPrefix} Attempting placing nest at {cell}.");
|
||||
}
|
||||
|
||||
var building = Registry.GetBuilding(tileType);
|
||||
if (building == null)
|
||||
{
|
||||
GD.PrintErr($"{LogPrefix} Building type not found in registry: {tileType}");
|
||||
return false;
|
||||
}
|
||||
// GD.Print($"{LogPrefix} Found building in registry: {tileType}");
|
||||
|
||||
var scene = building.Scene;
|
||||
if (scene == null)
|
||||
{
|
||||
GD.PrintErr($"{LogPrefix} Scene is null for building type: {tileType}");
|
||||
return false;
|
||||
}
|
||||
// GD.Print($"{LogPrefix} Scene loaded for {tileType}");
|
||||
|
||||
if (scene.Instantiate() is not BaseTile instance)
|
||||
{
|
||||
GD.PrintErr($"{LogPrefix} Failed to instantiate scene for: {tileType}");
|
||||
return false;
|
||||
}
|
||||
// GD.Print($"{LogPrefix} Successfully instantiated {tileType}");
|
||||
|
||||
// Use the same positioning logic as PlacementManager
|
||||
var rotatedSize = building.GetRotatedSize(0f);
|
||||
var offset = GridUtils.GetCenterOffset(rotatedSize, 0f);
|
||||
|
||||
instance.ZIndex = (int)building.Layer;
|
||||
instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset;
|
||||
instance.Grid = Grid;
|
||||
AddChild(instance);
|
||||
|
||||
// For enemy nest, use Building layer
|
||||
var layer = tileType == "enemy_nest" ? GridLayer.Building : building.Layer;
|
||||
Grid.OccupyArea(cell, instance, building.Size, 0f, layer);
|
||||
|
||||
// GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell} on layer {layer}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
GD.PrintErr($"{LogPrefix} Error placing {tileType} at {cell}: {e.Message}");
|
||||
GD.Print($"{LogPrefix} Stack trace: {e.StackTrace}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ChunkData
|
||||
{
|
||||
public List<Vector2I> StoneTiles { get; } = [];
|
||||
public List<Vector2I> IronTiles { get; } = [];
|
||||
}
|
1
Scripts/System/NaturalResourceGenerator.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cugfbvw70clgd
|
@@ -1,89 +1,460 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AceFieldNewHorizon.Scripts.Tiles;
|
||||
using Godot;
|
||||
using System.Linq;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.System;
|
||||
|
||||
public partial class PlacementManager : Node2D
|
||||
{
|
||||
[Export] public GridManager Grid { get; set; }
|
||||
[Export] public ResourceManager Inventory { get; set; }
|
||||
[Export] public BuildingRegistry Registry { get; set; }
|
||||
|
||||
private string _currentBuildingId = "miner";
|
||||
[Export] public int MaxConcurrentBuilds { get; set; } = 6; // Make it adjustable in editor
|
||||
[Export] public bool Enabled { get; set; } = true;
|
||||
[Export] public StringName ToggleBuildAction { get; set; } = "toggle_build";
|
||||
|
||||
private static readonly List<string> BuildableTiles = ["wall", "miner", "turret"];
|
||||
private readonly Dictionary<Node2D, BuildTask> _buildTasks = new();
|
||||
private AudioStreamPlayer _completionSound;
|
||||
private AudioStreamPlayer _buildingSound;
|
||||
private AudioStreamPlayer _canceledSound;
|
||||
private AudioStreamPlayer _cannotDeploySound;
|
||||
private AudioStreamPlayer _notReadySound;
|
||||
private AudioStreamPlayer _insufficientFundsSound;
|
||||
private Node2D _currentGhost; // Keep track of the current ghost building
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
|
||||
// Setup completion sound
|
||||
_completionSound = CreateAudioPlayer("res://Sounds/Events/ConstructionComplete.wav");
|
||||
_buildingSound = CreateAudioPlayer("res://Sounds/Events/Building.wav");
|
||||
_canceledSound = CreateAudioPlayer("res://Sounds/Events/Canceled.wav");
|
||||
_cannotDeploySound = CreateAudioPlayer("res://Sounds/Events/CannotDeployHere.wav");
|
||||
_notReadySound = CreateAudioPlayer("res://Sounds/Events/NotReady.wav");
|
||||
_insufficientFundsSound = CreateAudioPlayer("res://Sounds/Events/InsufficientFunds.wav");
|
||||
}
|
||||
|
||||
private AudioStreamPlayer CreateAudioPlayer(string path)
|
||||
{
|
||||
var player = new AudioStreamPlayer();
|
||||
AddChild(player);
|
||||
var sound = GD.Load<AudioStream>(path);
|
||||
if (sound != null)
|
||||
{
|
||||
player.Stream = sound;
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
private void OnBuildCompleted()
|
||||
{
|
||||
// Remove all completed builds
|
||||
var completed = _buildTasks.Where(kvp => kvp.Value.IsCompleted)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
foreach (var key in completed)
|
||||
{
|
||||
_buildTasks.Remove(key);
|
||||
}
|
||||
|
||||
// If no builds left, play the completion sound
|
||||
if (_buildTasks.Count == 0)
|
||||
{
|
||||
_completionSound.Play();
|
||||
}
|
||||
}
|
||||
|
||||
// Call this when starting a new build
|
||||
private bool CanStartNewBuild()
|
||||
{
|
||||
// Remove completed builds
|
||||
var completed = _buildTasks.Where(kvp => kvp.Value.IsCompleted)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
foreach (var key in completed)
|
||||
{
|
||||
_buildTasks.Remove(key);
|
||||
}
|
||||
|
||||
return _buildTasks.Count < MaxConcurrentBuilds;
|
||||
}
|
||||
|
||||
private class BuildTask(Action onCompleted) : IDisposable
|
||||
{
|
||||
public bool IsCompleted { get; private set; }
|
||||
public bool WasCancelled { get; private set; }
|
||||
|
||||
public void Complete(bool wasCancelled = false)
|
||||
{
|
||||
if (IsCompleted) return;
|
||||
|
||||
IsCompleted = true;
|
||||
WasCancelled = wasCancelled;
|
||||
onCompleted?.Invoke();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Complete(true); // Mark as cancelled if disposed before completion
|
||||
}
|
||||
}
|
||||
|
||||
private string _currentBuildingId = "wall";
|
||||
private Vector2I _hoveredCell;
|
||||
private BaseTile _ghostBuilding;
|
||||
private float _currentRotation;
|
||||
private Vector2I _currentBuildingSize = Vector2I.One;
|
||||
|
||||
public void SetCurrentBuilding(string buildingId)
|
||||
{
|
||||
_currentBuildingId = buildingId;
|
||||
var buildingData = Registry.GetBuilding(buildingId);
|
||||
if (buildingData != null)
|
||||
{
|
||||
_currentBuildingSize = buildingData.Size;
|
||||
// Reset rotation to nearest allowed rotation
|
||||
if (!buildingData.IsRotationAllowed(_currentRotation))
|
||||
{
|
||||
_currentRotation = (int)buildingData.AllowedRotations[0] * 90f;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace ghost immediately
|
||||
if (_ghostBuilding == null) return;
|
||||
if (_ghostBuilding != null)
|
||||
{
|
||||
_ghostBuilding.QueueFree();
|
||||
_ghostBuilding = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void RotateGhost(bool reverse = false)
|
||||
{
|
||||
if (_ghostBuilding == null) return;
|
||||
|
||||
var buildingData = Registry.GetBuilding(_currentBuildingId);
|
||||
if (buildingData == null) return;
|
||||
|
||||
// Calculate next rotation
|
||||
var currentDirection = (RotationDirection)((Mathf.RoundToInt(_currentRotation / 90f) % 4 + 4) % 4);
|
||||
var currentIndex = Array.IndexOf(buildingData.AllowedRotations.ToArray(), currentDirection);
|
||||
|
||||
if (reverse)
|
||||
currentIndex = (currentIndex - 1 + buildingData.AllowedRotations.Length) %
|
||||
buildingData.AllowedRotations.Length;
|
||||
else
|
||||
currentIndex = (currentIndex + 1) % buildingData.AllowedRotations.Length;
|
||||
|
||||
_currentRotation = (int)buildingData.AllowedRotations[currentIndex] * 90f;
|
||||
_ghostBuilding.RotationDegrees = _currentRotation;
|
||||
|
||||
// Update ghost position to keep the same cell under cursor
|
||||
UpdateGhostPosition();
|
||||
}
|
||||
|
||||
private void UpdateGhostPosition()
|
||||
{
|
||||
if (_ghostBuilding == null) return;
|
||||
|
||||
var buildingData = Registry.GetBuilding(_currentBuildingId);
|
||||
if (buildingData == null) return;
|
||||
|
||||
var rotatedSize = buildingData.GetRotatedSize(_currentRotation);
|
||||
var offset = GridUtils.GetCenterOffset(rotatedSize, _currentRotation);
|
||||
_ghostBuilding.Position = GridUtils.GridToWorld(_hoveredCell) + offset;
|
||||
|
||||
// Update occupied cells
|
||||
GridUtils.GetOccupiedCells(_hoveredCell, rotatedSize, _currentRotation);
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (!Enabled) return;
|
||||
|
||||
// Snap mouse to grid
|
||||
var mousePos = GetGlobalMousePosition();
|
||||
_hoveredCell = GridUtils.WorldToGrid(mousePos);
|
||||
var newHoveredCell = GridUtils.WorldToGrid(mousePos);
|
||||
|
||||
// Only update if cell changed
|
||||
if (newHoveredCell != _hoveredCell)
|
||||
{
|
||||
_hoveredCell = newHoveredCell;
|
||||
UpdateGhostPosition();
|
||||
}
|
||||
|
||||
if (Input.IsActionJustPressed("rotate_tile_reverse"))
|
||||
RotateGhost(reverse: true);
|
||||
else if (Input.IsActionJustPressed("rotate_tile"))
|
||||
RotateGhost();
|
||||
|
||||
if (_ghostBuilding == null)
|
||||
{
|
||||
var scene = Registry.GetBuilding(_currentBuildingId)?.Scene;
|
||||
if (scene == null) return;
|
||||
var building = Registry.GetBuilding(_currentBuildingId);
|
||||
if (building == null) return;
|
||||
var scene = building.Scene;
|
||||
|
||||
_ghostBuilding = (BaseTile)scene.Instantiate();
|
||||
_ghostBuilding.Grid = Grid;
|
||||
_ghostBuilding.SetGhostMode(true);
|
||||
_ghostBuilding.RotationDegrees = _currentRotation;
|
||||
_ghostBuilding.ZAsRelative = false;
|
||||
_ghostBuilding.ZIndex = (int)building.Layer;
|
||||
UpdateGhostPosition();
|
||||
AddChild(_ghostBuilding);
|
||||
}
|
||||
|
||||
var placementPos = GridUtils.GridToWorld(_hoveredCell);
|
||||
var spaceState = GetWorld2D().DirectSpaceState;
|
||||
var centerPos = placementPos + new Vector2(GridUtils.TileSize / 2f, GridUtils.TileSize / 2f);
|
||||
|
||||
var query = new PhysicsPointQueryParameters2D
|
||||
{
|
||||
Position = centerPos,
|
||||
CollideWithBodies = true,
|
||||
CollideWithAreas = true,
|
||||
CollisionMask = uint.MaxValue // check against all layers/masks
|
||||
// Exclude = new Godot.Collections.Array<Rid>() // optional, see below
|
||||
};
|
||||
|
||||
var collision = spaceState.IntersectPoint(query, 8);
|
||||
var canPlace = Grid.IsCellFree(_hoveredCell) && collision.Count == 0;
|
||||
|
||||
_ghostBuilding.Position = placementPos;
|
||||
var canPlace = CanPlaceBuilding();
|
||||
_ghostBuilding.SetGhostMode(canPlace);
|
||||
|
||||
// Left click to place
|
||||
if (Input.IsActionPressed("build_tile") && canPlace)
|
||||
if (Input.IsActionPressed("build_tile"))
|
||||
{
|
||||
var scene = Registry.GetBuilding(_currentBuildingId)?.Scene;
|
||||
if (scene == null) return;
|
||||
|
||||
_ghostBuilding.FinalizePlacement();
|
||||
|
||||
var buildingData = Registry.GetBuilding(_currentBuildingId);
|
||||
Grid.OccupyCell(_hoveredCell, _ghostBuilding);
|
||||
|
||||
if (buildingData is { BuildTime: > 0f })
|
||||
_ghostBuilding.StartConstruction(buildingData.BuildTime);
|
||||
|
||||
_ghostBuilding = (BaseTile)scene.Instantiate();
|
||||
_ghostBuilding.SetGhostMode(true);
|
||||
AddChild(_ghostBuilding);
|
||||
}
|
||||
|
||||
if (Input.IsActionPressed("destroy_tile") && !Grid.IsCellFree(_hoveredCell))
|
||||
{
|
||||
// Right click to destroy
|
||||
var building = Grid.GetBuildingAtCell(_hoveredCell);
|
||||
var building = Registry.GetBuilding(_currentBuildingId);
|
||||
if (building == null) return;
|
||||
building.QueueFree();
|
||||
Grid.FreeCell(_hoveredCell);
|
||||
|
||||
if (!CanStartNewBuild())
|
||||
{
|
||||
_notReadySound.Play();
|
||||
return;
|
||||
}
|
||||
|
||||
// First check if area is free
|
||||
if (!IsAreaFree(_hoveredCell, building.Size, _currentRotation, building.Layer))
|
||||
{
|
||||
// Check if the area is occupied by under-construction tiles
|
||||
var occupiedCells = GridUtils.GetOccupiedCells(_hoveredCell, building.Size, _currentRotation);
|
||||
var isUnderConstruction = occupiedCells.Any(cell =>
|
||||
Grid.GetTileAtCell(cell, building.Layer) is BaseTile { IsConstructing: true });
|
||||
|
||||
if (!isUnderConstruction)
|
||||
_cannotDeploySound.Play();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume resources first
|
||||
if (!ConsumeBuildingResources(_currentBuildingId))
|
||||
{
|
||||
_insufficientFundsSound.Play();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the building instance
|
||||
var scene = building.Scene;
|
||||
var buildingInstance = (BaseTile)scene.Instantiate();
|
||||
buildingInstance.Grid = Grid;
|
||||
buildingInstance.RotationDegrees = _currentRotation;
|
||||
buildingInstance.ZIndex = (int)building.Layer;
|
||||
buildingInstance.Position = _ghostBuilding.Position;
|
||||
AddChild(buildingInstance);
|
||||
|
||||
// If we get here, area is free, so we can safely occupy it
|
||||
Grid.OccupyArea(_hoveredCell, buildingInstance, building.Size, _currentRotation, building.Layer);
|
||||
|
||||
if (building.BuildTime > 0f)
|
||||
{
|
||||
var wasQueueEmpty = _buildTasks.Count == 0;
|
||||
var buildTask = new BuildTask(OnBuildCompleted);
|
||||
_buildTasks[buildingInstance] = buildTask;
|
||||
|
||||
// Play building sound only when adding to an empty queue
|
||||
if (wasQueueEmpty)
|
||||
_buildingSound.Play();
|
||||
|
||||
buildingInstance.StartConstruction(building.BuildTime, () =>
|
||||
{
|
||||
// On construction complete
|
||||
if (_buildTasks.TryGetValue(buildingInstance, out var task))
|
||||
{
|
||||
if (task.WasCancelled)
|
||||
{
|
||||
RefundBuildingResources(_currentBuildingId);
|
||||
Grid.FreeArea(_hoveredCell, building.Size, _currentRotation, building.Layer);
|
||||
buildingInstance.QueueFree();
|
||||
}
|
||||
|
||||
task.Complete();
|
||||
_buildTasks.Remove(buildingInstance);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Input.IsActionPressed("destroy_tile") &&
|
||||
!Grid.IsAreaFree(_hoveredCell, Vector2I.One, 0f))
|
||||
{
|
||||
// Right click to destroy from current layer
|
||||
var building = Grid.GetTileAtCell(_hoveredCell);
|
||||
if (building == null) return;
|
||||
|
||||
// Find all cells occupied by this building
|
||||
var buildingInfo = Grid.GetBuildingInfoAtCell(_hoveredCell, GridLayer.Building);
|
||||
if (buildingInfo == null) return;
|
||||
|
||||
// Check if this building is in the build tasks (under construction)
|
||||
if (_buildTasks.TryGetValue(building, out var buildTask))
|
||||
{
|
||||
var buildingTile = building as BaseTile;
|
||||
// Cancel the build task
|
||||
buildTask.Complete(true); // Mark as cancelled
|
||||
_buildTasks.Remove(building);
|
||||
_canceledSound.Play();
|
||||
if (buildingTile == null) return;
|
||||
|
||||
// Refund resources for canceled build
|
||||
var buildingData = Registry.GetBuilding(buildingTile.TileId);
|
||||
if (buildingData != null)
|
||||
RefundBuildingResources(buildingTile.TileId);
|
||||
}
|
||||
|
||||
// Clean up the building and grid
|
||||
building.QueueFree();
|
||||
Grid.FreeArea(buildingInfo.Value.Position, buildingInfo.Value.Size, buildingInfo.Value.Rotation);
|
||||
}
|
||||
|
||||
if (Input.IsActionJustPressed("switch_tile"))
|
||||
{
|
||||
var currentIdx = BuildableTiles.IndexOf(_currentBuildingId);
|
||||
var nextIdx = (currentIdx + 1) % BuildableTiles.Count;
|
||||
SetCurrentBuilding(BuildableTiles[nextIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Input(InputEvent @event)
|
||||
{
|
||||
if (@event.IsActionPressed(ToggleBuildAction))
|
||||
{
|
||||
Enabled = !Enabled;
|
||||
|
||||
// Hide ghost building when disabling
|
||||
if (!Enabled && _ghostBuilding != null && _ghostBuilding.IsInsideTree())
|
||||
{
|
||||
_ghostBuilding.QueueFree();
|
||||
_ghostBuilding = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
base._ExitTree();
|
||||
_buildTasks.Clear();
|
||||
}
|
||||
|
||||
private bool CanAffordBuilding(string buildingId)
|
||||
{
|
||||
if (Inventory == null) return false;
|
||||
|
||||
var buildingData = Registry.GetBuilding(buildingId);
|
||||
if (buildingData == null) return false;
|
||||
|
||||
// Check if we have enough of each required resource
|
||||
foreach (var (resourceId, amount) in buildingData.Cost)
|
||||
if (!Inventory.HasResource(resourceId, amount))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ConsumeBuildingResources(string buildingId)
|
||||
{
|
||||
if (Inventory == null) return false;
|
||||
|
||||
var buildingData = Registry.GetBuilding(buildingId);
|
||||
if (buildingData == null) return false;
|
||||
|
||||
// First verify we can afford it
|
||||
if (!CanAffordBuilding(buildingId)) return false;
|
||||
|
||||
// Then consume each resource
|
||||
foreach (var (resourceId, amount) in buildingData.Cost)
|
||||
Inventory.RemoveResource(resourceId, amount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RefundBuildingResources(string buildingId)
|
||||
{
|
||||
if (Inventory == null) return;
|
||||
|
||||
var buildingData = Registry.GetBuilding(buildingId);
|
||||
if (buildingData == null) return;
|
||||
|
||||
// Refund each resource
|
||||
foreach (var (resourceId, amount) in buildingData.Cost)
|
||||
{
|
||||
Inventory.AddResource(resourceId, amount);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanPlaceBuilding()
|
||||
{
|
||||
var buildingData = Registry.GetBuilding(_currentBuildingId);
|
||||
if (buildingData == null) return false;
|
||||
|
||||
// Check if we can afford the building
|
||||
if (!CanAffordBuilding(_currentBuildingId)) return false;
|
||||
|
||||
// Check if area is free
|
||||
var rotatedSize = buildingData.GetRotatedSize(_currentRotation);
|
||||
return !Grid.IsAreaOccupied(_hoveredCell, rotatedSize, _currentRotation, GetBlockingLayers(buildingData.Layer));
|
||||
}
|
||||
|
||||
private GridLayer[] GetBlockingLayers(GridLayer layer)
|
||||
{
|
||||
return layer switch
|
||||
{
|
||||
GridLayer.Ground => [GridLayer.Ground],
|
||||
GridLayer.Building => [GridLayer.Building],
|
||||
GridLayer.Decoration => [GridLayer.Decoration],
|
||||
_ => []
|
||||
};
|
||||
}
|
||||
|
||||
private bool IsAreaFree(Vector2I topLeft, Vector2I size, float rotation, GridLayer layer)
|
||||
{
|
||||
return !Grid.IsAreaOccupied(topLeft, size, rotation, GetBlockingLayers(layer));
|
||||
}
|
||||
}
|
||||
|
||||
public static class GridManagerExtensions
|
||||
{
|
||||
public static (Vector2I Position, Vector2I Size, float Rotation)? GetBuildingInfoAtCell(this GridManager grid,
|
||||
Vector2I cell, GridLayer layer)
|
||||
{
|
||||
if (grid.GetTileAtCell(cell, layer) is { } building)
|
||||
{
|
||||
// Find the top-left position of the building
|
||||
for (int x = 0; x < 100; x++) // Arbitrary max size
|
||||
{
|
||||
for (int y = 0; y < 100; y++)
|
||||
{
|
||||
var checkCell = new Vector2I(cell.X - x, cell.Y - y);
|
||||
if (grid.GetTileAtCell(checkCell, layer) == building)
|
||||
{
|
||||
// Found the top-left corner, now find the size
|
||||
var size = Vector2I.One;
|
||||
// Search right
|
||||
while (grid.GetTileAtCell(new Vector2I(checkCell.X + size.X, checkCell.Y), layer) ==
|
||||
building)
|
||||
size.X++;
|
||||
// Search down
|
||||
while (grid.GetTileAtCell(new Vector2I(checkCell.X, checkCell.Y + size.Y), layer) ==
|
||||
building)
|
||||
size.Y++;
|
||||
|
||||
// Get rotation from the first cell
|
||||
var rotation = 0f; // You'll need to store rotation in GridManager to make this work
|
||||
return (checkCell, size, rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
145
Scripts/System/ResourceManager.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using Godot;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.System;
|
||||
|
||||
public partial class ResourceManager : Node
|
||||
{
|
||||
// Dictionary to store resources with their IDs and quantities
|
||||
private readonly Dictionary<string, int> _resources = new();
|
||||
private static readonly StringName LoggerName = "ResourceManager";
|
||||
|
||||
// Event for when resource amounts change
|
||||
[Signal]
|
||||
public delegate void OnResourceChangedEventHandler(string resourceId, int newAmount);
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
GD.Print($"[{LoggerName}] ResourceManager initialized");
|
||||
}
|
||||
|
||||
// Add resources of a specific type
|
||||
public void AddResource(string resourceId, int amount = 1)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
GD.PushWarning($"[{LoggerName}] Attempted to add non-positive amount ({amount}) for resource: {resourceId}");
|
||||
return;
|
||||
}
|
||||
|
||||
bool isNew = !_resources.ContainsKey(resourceId);
|
||||
|
||||
if (!_resources.TryAdd(resourceId, amount))
|
||||
{
|
||||
var oldAmount = _resources[resourceId];
|
||||
_resources[resourceId] += amount;
|
||||
GD.Print($"[{LoggerName}] Added {amount} {resourceId}. New total: {_resources[resourceId]} (was {oldAmount})");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print($"[{LoggerName}] Added new resource: {resourceId} with amount: {amount}");
|
||||
}
|
||||
|
||||
EmitSignal(nameof(OnResourceChanged), resourceId, _resources[resourceId]);
|
||||
}
|
||||
|
||||
// Remove resources of a specific type
|
||||
public bool RemoveResource(string resourceId, int amount = 1)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
GD.PushWarning($"[{LoggerName}] Attempted to remove non-positive amount ({amount}) for resource: {resourceId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_resources.ContainsKey(resourceId))
|
||||
{
|
||||
GD.Print($"[{LoggerName}] Failed to remove {amount} {resourceId}: Resource not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_resources[resourceId] < amount)
|
||||
{
|
||||
GD.Print($"[{LoggerName}] Insufficient {resourceId}. Requested: {amount}, Available: {_resources[resourceId]}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var oldAmount = _resources[resourceId];
|
||||
_resources[resourceId] -= amount;
|
||||
|
||||
GD.Print($"[{LoggerName}] Removed {amount} {resourceId}. New total: {_resources[resourceId]} (was {oldAmount})");
|
||||
|
||||
// Remove the entry if quantity reaches zero
|
||||
if (_resources[resourceId] <= 0)
|
||||
{
|
||||
_resources.Remove(resourceId);
|
||||
GD.Print($"[{LoggerName}] Removed resource entry for {resourceId} (reached zero)");
|
||||
EmitSignal(nameof(OnResourceChanged), resourceId, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitSignal(nameof(OnResourceChanged), resourceId, _resources[resourceId]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the current amount of a specific resource
|
||||
public int GetResourceAmount(string resourceId)
|
||||
{
|
||||
// Silently return 0 for non-existent resources
|
||||
return _resources.GetValueOrDefault(resourceId, 0);
|
||||
}
|
||||
|
||||
// Check if there are at least 'amount' of a specific resource
|
||||
public bool HasResource(string resourceId, int amount = 1)
|
||||
{
|
||||
if (amount <= 0) return true; // Always true for zero or negative amounts
|
||||
|
||||
var exists = _resources.TryGetValue(resourceId, out var currentAmount);
|
||||
var hasEnough = exists && currentAmount >= amount;
|
||||
|
||||
return hasEnough;
|
||||
}
|
||||
|
||||
// Get all resources as a read-only dictionary
|
||||
public IReadOnlyDictionary<string, int> GetAllResources()
|
||||
{
|
||||
GD.Print($"[{LoggerName}] Getting all resources. Total types: {_resources.Count}");
|
||||
return new Dictionary<string, int>(_resources);
|
||||
}
|
||||
|
||||
// Clear all resources
|
||||
public void Clear()
|
||||
{
|
||||
GD.Print($"[{LoggerName}] Clearing all resources. Total types before clear: {_resources.Count}");
|
||||
|
||||
var keys = new List<string>(_resources.Keys);
|
||||
foreach (var resourceId in keys)
|
||||
{
|
||||
_resources[resourceId] = 0;
|
||||
EmitSignal(nameof(OnResourceChanged), resourceId, 0);
|
||||
}
|
||||
_resources.Clear();
|
||||
|
||||
GD.Print($"[{LoggerName}] All resources cleared");
|
||||
}
|
||||
|
||||
// Helper method to log current resource state
|
||||
public void LogResourceState()
|
||||
{
|
||||
if (_resources.Count == 0)
|
||||
{
|
||||
GD.Print($"[{LoggerName}] No resources currently stored");
|
||||
return;
|
||||
}
|
||||
|
||||
GD.Print($"[{LoggerName}] Current resource state:");
|
||||
foreach (var (id, amount) in _resources.OrderBy(x => x.Key))
|
||||
{
|
||||
GD.Print($" - {id}: {amount}");
|
||||
}
|
||||
}
|
||||
}
|
1
Scripts/System/ResourceManager.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dfi2snip78eq6
|
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using AceFieldNewHorizon.Scripts.System;
|
||||
using Godot;
|
||||
using Vector2 = Godot.Vector2;
|
||||
@@ -7,9 +8,16 @@ namespace AceFieldNewHorizon.Scripts.Tiles;
|
||||
|
||||
public partial class BaseTile : Node2D
|
||||
{
|
||||
[Export] public string TileId { get; set; }
|
||||
[Export] public GridManager Grid { get; set; }
|
||||
|
||||
private CollisionShape2D _collisionShape;
|
||||
private Sprite2D _sprite;
|
||||
private ColorRect _progressOverlay;
|
||||
private Action _onConstructionComplete;
|
||||
|
||||
public bool IsConstructing;
|
||||
public bool IsConstructed;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
@@ -20,8 +28,11 @@ public partial class BaseTile : Node2D
|
||||
_progressOverlay.Visible = false;
|
||||
}
|
||||
|
||||
public void SetGhostMode(bool canPlace)
|
||||
public virtual void SetGhostMode(bool canPlace)
|
||||
{
|
||||
// Don't modify collision for constructing buildings
|
||||
if (IsConstructing) return;
|
||||
|
||||
if (_collisionShape != null)
|
||||
_collisionShape.Disabled = true;
|
||||
|
||||
@@ -31,7 +42,7 @@ public partial class BaseTile : Node2D
|
||||
: new Color(1, 0, 0, 0.5f);
|
||||
}
|
||||
|
||||
public void FinalizePlacement()
|
||||
public virtual void FinalizePlacement()
|
||||
{
|
||||
if (_collisionShape != null)
|
||||
_collisionShape.Disabled = false;
|
||||
@@ -40,13 +51,28 @@ public partial class BaseTile : Node2D
|
||||
}
|
||||
|
||||
// Building progress visualization
|
||||
public void StartConstruction(float buildTime)
|
||||
public void StartConstruction(float buildTime, Action onComplete = null)
|
||||
{
|
||||
if (_progressOverlay == null || _sprite?.Texture == null) return;
|
||||
IsConstructing = true;
|
||||
if (_collisionShape != null)
|
||||
_collisionShape.Disabled = true;
|
||||
|
||||
if (_progressOverlay == null || _sprite?.Texture == null)
|
||||
{
|
||||
IsConstructing = false;
|
||||
onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
_onConstructionComplete = onComplete;
|
||||
var texSize = new Vector2(GridUtils.TileSize, GridUtils.TileSize);
|
||||
|
||||
// Set initial transparency for construction
|
||||
if (_sprite != null)
|
||||
_sprite.Modulate = new Color(1, 1, 1, 0.8f);
|
||||
|
||||
_progressOverlay.Visible = true;
|
||||
_progressOverlay.Modulate = Colors.White;
|
||||
_progressOverlay.Color = new Color(0, 0, 1, 0.4f); // semi-transparent blue
|
||||
|
||||
async void RunProgress()
|
||||
@@ -67,9 +93,38 @@ public partial class BaseTile : Node2D
|
||||
);
|
||||
}
|
||||
|
||||
_progressOverlay.Visible = false;
|
||||
// Fade out the overlay
|
||||
await FadeOutOverlay(0.5f);
|
||||
|
||||
// Construction complete - restore full opacity and enable collision
|
||||
if (_sprite != null)
|
||||
_sprite.Modulate = Colors.White;
|
||||
|
||||
IsConstructing = false;
|
||||
if (_collisionShape != null)
|
||||
_collisionShape.Disabled = false;
|
||||
|
||||
_onConstructionComplete?.Invoke();
|
||||
IsConstructed = true;
|
||||
}
|
||||
|
||||
RunProgress();
|
||||
}
|
||||
|
||||
private async Task FadeOutOverlay(float duration)
|
||||
{
|
||||
var elapsed = 0f;
|
||||
var startAlpha = _progressOverlay.Modulate.A;
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
|
||||
elapsed += (float)GetProcessDeltaTime();
|
||||
var alpha = Mathf.Lerp(startAlpha, 0f, elapsed / duration);
|
||||
_progressOverlay.Modulate = new Color(1, 1, 1, alpha);
|
||||
}
|
||||
|
||||
_progressOverlay.Visible = false;
|
||||
_progressOverlay.Modulate = Colors.White; // Reset alpha for next use
|
||||
}
|
||||
}
|
74
Scripts/Tiles/EnemyNestTile.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using AceFieldNewHorizon.Scripts.Entities;
|
||||
using Godot;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.Tiles;
|
||||
|
||||
public partial class EnemyNestTile : BaseTile
|
||||
{
|
||||
[Export] public PackedScene EnemyScene;
|
||||
[Export] public int MaxEnemies = 5;
|
||||
[Export] public float SpawnDelay = 5.0f; // Time between spawn attempts
|
||||
[Export] public float SpawnRadius = 50.0f; // Radius around the nest where enemies can spawn
|
||||
[Export] public bool Active = true;
|
||||
|
||||
private Timer _spawnTimer;
|
||||
private int _currentEnemyCount = 0;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
|
||||
// Create and configure the timer
|
||||
_spawnTimer = new Timer
|
||||
{
|
||||
Autostart = true,
|
||||
WaitTime = SpawnDelay
|
||||
};
|
||||
AddChild(_spawnTimer);
|
||||
_spawnTimer.Timeout += OnSpawnTimerTimeout;
|
||||
}
|
||||
|
||||
private void OnSpawnTimerTimeout()
|
||||
{
|
||||
if (!Active || EnemyScene == null || _currentEnemyCount >= MaxEnemies)
|
||||
return;
|
||||
|
||||
// Check if we can spawn more enemies
|
||||
var enemies = GetTree().GetNodesInGroup("Enemy");
|
||||
_currentEnemyCount = enemies.Count;
|
||||
|
||||
if (_currentEnemyCount >= MaxEnemies)
|
||||
return;
|
||||
|
||||
// Spawn a new enemy
|
||||
var enemy = EnemyScene.Instantiate<Enemy>();
|
||||
if (enemy != null)
|
||||
{
|
||||
GetParent().AddChild(enemy);
|
||||
|
||||
// Calculate a random position within the spawn radius
|
||||
var randomAngle = GD.Randf() * Mathf.Pi * 2;
|
||||
var randomOffset = new Vector2(
|
||||
Mathf.Cos(randomAngle) * SpawnRadius,
|
||||
Mathf.Sin(randomAngle) * SpawnRadius
|
||||
);
|
||||
|
||||
enemy.GlobalPosition = GlobalPosition + randomOffset;
|
||||
_currentEnemyCount++;
|
||||
|
||||
// Connect to the enemy's death signal if available
|
||||
enemy.TreeExiting += () => OnEnemyDied();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnemyDied()
|
||||
{
|
||||
_currentEnemyCount = Mathf.Max(0, _currentEnemyCount - 1);
|
||||
}
|
||||
|
||||
public void SetActive(bool active)
|
||||
{
|
||||
Active = active;
|
||||
_spawnTimer.Paused = !active;
|
||||
}
|
||||
}
|
1
Scripts/Tiles/EnemyNestTile.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://26hl5mk4mqur
|
14
Scripts/Tiles/GroundTile.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Godot;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.Tiles;
|
||||
|
||||
public partial class GroundTile : BaseTile
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
var sprite = GetNode<Sprite2D>("Sprite2D");
|
||||
sprite.Modulate = new Color(0.75f, 0.75f, 0.75f); // Makes the sprite 25% darker
|
||||
|
||||
base._Ready();
|
||||
}
|
||||
}
|
1
Scripts/Tiles/GroundTile.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dh0jdeplrigxu
|
@@ -1,8 +1,83 @@
|
||||
using AceFieldNewHorizon.Scripts.System;
|
||||
using Godot;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.Tiles;
|
||||
|
||||
public partial class MinerTile : BaseTile
|
||||
{
|
||||
[Export] public PackedScene ItemPickup { get; set; }
|
||||
[Export] public string ItemToMine { get; set; }
|
||||
[Export] public int MiningRate = 1; // Items per second
|
||||
|
||||
private Vector2I _gridPosition;
|
||||
private float _timeSinceLastMine;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
_gridPosition = GridUtils.WorldToGrid(Position);
|
||||
|
||||
var ground = Grid.GetTileAtCell(_gridPosition, GridLayer.Ground) as BaseTile;
|
||||
if (ground == null)
|
||||
{
|
||||
GD.Print($"[Miner] Miner {GetInstanceId()} not found available resource...");
|
||||
return;
|
||||
}
|
||||
|
||||
ItemToMine = (ground.TileId) switch
|
||||
{
|
||||
"stone" => "stone",
|
||||
"ore_iron" => "ore_iron",
|
||||
_ => null
|
||||
};
|
||||
if (ItemToMine == null)
|
||||
GD.Print($"[Miner] Miner {GetInstanceId()} not found available resource...");
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (ItemToMine == null)
|
||||
return;
|
||||
|
||||
// Don't mine if building is not completed
|
||||
if (!IsConstructed || ItemPickup == null)
|
||||
return;
|
||||
|
||||
_timeSinceLastMine += (float)delta;
|
||||
|
||||
if (!(_timeSinceLastMine >= 1f / MiningRate)) return;
|
||||
_timeSinceLastMine = 0f;
|
||||
SpawnItem();
|
||||
}
|
||||
|
||||
private void SpawnItem()
|
||||
{
|
||||
var itemPickup = ItemPickup?.Instantiate<ItemPickup>();
|
||||
if (itemPickup == null) return;
|
||||
|
||||
itemPickup.ItemId = ItemToMine;
|
||||
itemPickup.Quantity = 1;
|
||||
|
||||
// Initial position (slightly below the spawn point)
|
||||
const int halfTileSize = GridUtils.TileSize / 2;
|
||||
var spawnPosition = GridUtils.GridToWorld(_gridPosition);
|
||||
var targetY = spawnPosition.Y - halfTileSize; // Target Y position
|
||||
var targetX = spawnPosition.X + halfTileSize + (GD.Randf() * 10f - 5f);
|
||||
itemPickup.Position =
|
||||
new Vector2(spawnPosition.X + halfTileSize, spawnPosition.Y + 16); // Start below
|
||||
itemPickup.Scale = Vector2.Zero; // Start invisible
|
||||
|
||||
// Add to the scene
|
||||
GetTree().CurrentScene.AddChild(itemPickup);
|
||||
|
||||
// Create the pop-up animation
|
||||
var tween = CreateTween().SetTrans(Tween.TransitionType.Elastic).SetEase(Tween.EaseType.Out);
|
||||
|
||||
// Animate the pop-up effect
|
||||
tween.TweenProperty(itemPickup, "position:y", targetY, 0.6f);
|
||||
tween.Parallel().TweenProperty(itemPickup, "scale", Vector2.One, 0.6f);
|
||||
|
||||
// Optional: Add a slight horizontal wobble
|
||||
tween.Parallel().TweenProperty(itemPickup, "position:x", targetX, 0.6f);
|
||||
}
|
||||
}
|
127
Scripts/Tiles/TurretTile.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.Linq;
|
||||
using AceFieldNewHorizon.Scripts.Entities;
|
||||
using Godot;
|
||||
|
||||
namespace AceFieldNewHorizon.Scripts.Tiles;
|
||||
|
||||
public partial class TurretTile : BaseTile
|
||||
{
|
||||
[Export] public PackedScene BulletScene;
|
||||
|
||||
[Export] public float RotationSpeed = 2.0f; // Radians per second
|
||||
[Export] public float AttackRange = 300.0f;
|
||||
[Export] public float AttackCooldown = 0.5f;
|
||||
[Export] public int Damage = 10;
|
||||
[Export] public float BulletSpeed = 400.0f;
|
||||
[Export] public NodePath BarrelTipPath;
|
||||
|
||||
private Node2D _spriteBarrel;
|
||||
private Node2D _barrelTip;
|
||||
private float _attackTimer = 0;
|
||||
private bool _hasTarget = false;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
|
||||
_spriteBarrel = GetNodeOrNull<Node2D>("Barrel");
|
||||
_barrelTip = GetNodeOrNull<Node2D>(BarrelTipPath);
|
||||
|
||||
if (_barrelTip == null && _spriteBarrel != null)
|
||||
{
|
||||
// If no barrel tip is specified, use the end of the barrel sprite
|
||||
_barrelTip = _spriteBarrel.GetNodeOrNull<Node2D>("BarrelTip");
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetGhostMode(bool canPlace)
|
||||
{
|
||||
base.SetGhostMode(canPlace);
|
||||
|
||||
if (_spriteBarrel != null)
|
||||
_spriteBarrel.Modulate = canPlace
|
||||
? new Color(0, 1, 0, 0.5f)
|
||||
: new Color(1, 0, 0, 0.5f);
|
||||
}
|
||||
|
||||
public override void FinalizePlacement()
|
||||
{
|
||||
base.FinalizePlacement();
|
||||
|
||||
if (_spriteBarrel != null)
|
||||
_spriteBarrel.Modulate = Colors.White;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (!IsConstructed) return;
|
||||
|
||||
// Update attack cooldown
|
||||
if (_attackTimer > 0)
|
||||
{
|
||||
_attackTimer -= (float)delta;
|
||||
}
|
||||
|
||||
// Find the nearest enemy
|
||||
var enemies = GetTree().GetNodesInGroup(Enemy.EnemyGroupName)
|
||||
.OfType<Enemy>()
|
||||
.Where(e => e.GlobalPosition.DistanceTo(GlobalPosition) <= AttackRange)
|
||||
.OrderBy(e => e.GlobalPosition.DistanceTo(GlobalPosition))
|
||||
.ToList();
|
||||
|
||||
if (enemies.Count > 0)
|
||||
{
|
||||
var nearestEnemy = enemies[0];
|
||||
_hasTarget = true;
|
||||
|
||||
// Calculate target angle
|
||||
var direction = (nearestEnemy.GlobalPosition - _spriteBarrel.GlobalPosition).Normalized();
|
||||
var targetAngle = Mathf.Atan2(direction.Y, direction.X);
|
||||
|
||||
// Smoothly rotate towards target
|
||||
_spriteBarrel.Rotation = Mathf.LerpAngle(_spriteBarrel.Rotation, targetAngle, (float)delta * RotationSpeed);
|
||||
|
||||
// Check if we're facing the target and can attack
|
||||
if (Mathf.Abs(Mathf.Wrap(targetAngle - _spriteBarrel.Rotation, -Mathf.Pi, Mathf.Pi)) < 0.1f)
|
||||
TryAttack(nearestEnemy.GlobalPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryAttack(Vector2 targetPosition)
|
||||
{
|
||||
if (_attackTimer <= 0 && BulletScene != null && _barrelTip != null)
|
||||
{
|
||||
// Create bullet instance
|
||||
var bullet = BulletScene.Instantiate<Bullet>();
|
||||
|
||||
// Calculate direction and rotation
|
||||
var direction = (targetPosition - _barrelTip.GlobalPosition).Normalized();
|
||||
var bulletRotation = direction.Angle();
|
||||
|
||||
// Set bullet position and rotation
|
||||
GetTree().CurrentScene.AddChild(bullet);
|
||||
bullet.GlobalPosition = _barrelTip.GlobalPosition;
|
||||
bullet.Rotation = bulletRotation; // Use the calculated rotation
|
||||
|
||||
// Initialize bullet with direction and damage
|
||||
bullet.Initialize(
|
||||
direction,
|
||||
bullet.GlobalPosition,
|
||||
bulletRotation, // Pass the calculated rotation
|
||||
1 // Pass the turret's collision layer to ignore
|
||||
);
|
||||
bullet.Damage = Damage;
|
||||
bullet.Speed = BulletSpeed;
|
||||
bullet.MaxDistance = AttackRange * 1.5f; // Bullets can travel slightly further than attack range
|
||||
|
||||
// Reset attack cooldown
|
||||
_attackTimer = AttackCooldown;
|
||||
|
||||
GD.Print("Turret attacking enemy!");
|
||||
}
|
||||
}
|
||||
}
|
1
Scripts/Tiles/TurretTile.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://n5g6i0uovxfk
|
BIN
Sounds/Events/Building.wav
Normal file
24
Sounds/Events/Building.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://d1trbqrntmuij"
|
||||
path="res://.godot/imported/Building.wav-b8766581fd25a206c63f47b13bd2e2f5.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Sounds/Events/Building.wav"
|
||||
dest_files=["res://.godot/imported/Building.wav-b8766581fd25a206c63f47b13bd2e2f5.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
BIN
Sounds/Events/Canceled.wav
Normal file
24
Sounds/Events/Canceled.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://chn8ux4two1kd"
|
||||
path="res://.godot/imported/Canceled.wav-6d441d9a898b5cd9be4c0664a1f489ed.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Sounds/Events/Canceled.wav"
|
||||
dest_files=["res://.godot/imported/Canceled.wav-6d441d9a898b5cd9be4c0664a1f489ed.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
BIN
Sounds/Events/CannotDeployHere.wav
Normal file
24
Sounds/Events/CannotDeployHere.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://7u1gw7lt5xd1"
|
||||
path="res://.godot/imported/CannotDeployHere.wav-a9ec27508654f74d03ac7b8c8037059c.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Sounds/Events/CannotDeployHere.wav"
|
||||
dest_files=["res://.godot/imported/CannotDeployHere.wav-a9ec27508654f74d03ac7b8c8037059c.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
BIN
Sounds/Events/ConstructionComplete.wav
Normal file
24
Sounds/Events/ConstructionComplete.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://xmgj505csg5x"
|
||||
path="res://.godot/imported/ConstructionComplete.wav-094dbaa5fa3f80e5fe8fc1583055a77d.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Sounds/Events/ConstructionComplete.wav"
|
||||
dest_files=["res://.godot/imported/ConstructionComplete.wav-094dbaa5fa3f80e5fe8fc1583055a77d.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
BIN
Sounds/Events/InsufficientFunds.wav
Normal file
24
Sounds/Events/InsufficientFunds.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://bemxvqcettqgp"
|
||||
path="res://.godot/imported/InsufficientFunds.wav-7aba215cb1cd04a5285a5e9908999906.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Sounds/Events/InsufficientFunds.wav"
|
||||
dest_files=["res://.godot/imported/InsufficientFunds.wav-7aba215cb1cd04a5285a5e9908999906.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
BIN
Sounds/Events/NotReady.wav
Normal file
24
Sounds/Events/NotReady.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dogbl6tfealwg"
|
||||
path="res://.godot/imported/NotReady.wav-2cfa5f22feed110ca411d6478060fdea.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Sounds/Events/NotReady.wav"
|
||||
dest_files=["res://.godot/imported/NotReady.wav-2cfa5f22feed110ca411d6478060fdea.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
@@ -55,8 +55,32 @@ destroy_tile={
|
||||
"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":2,"position":Vector2(481, 42),"global_position":Vector2(500, 138),"factor":1.0,"button_index":2,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
rotate_tile={
|
||||
"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":79,"key_label":0,"unicode":111,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
rotate_tile_reverse={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":79,"key_label":0,"unicode":79,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_sprint={
|
||||
"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":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
switch_tile={
|
||||
"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":93,"key_label":0,"unicode":93,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
toggle_build={
|
||||
"deadzone": 0.2,
|
||||
"events": []
|
||||
}
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
anti_aliasing/quality/msaa_2d=3
|
||||
anti_aliasing/quality/use_taa=true
|
||||
|