Compare commits

..

21 Commits

Author SHA1 Message Date
2c5e0459ad 💄 Optimize stuff 2025-08-31 19:30:16 +08:00
f2c243ecf6 🍱 Retexture the enemies 2025-08-31 18:54:30 +08:00
b424aafeab ♻️ Optimizations of the various system
🍱 Retexture of the enemy portal
2025-08-31 18:26:45 +08:00
09511b37c9 Reactor, and enemies attacking the tiles 2025-08-31 14:30:18 +08:00
c72353716f ♻️ Rebuild with DI 2025-08-31 01:44:18 +08:00
1cc941d893 Hud basis 2025-08-30 23:41:49 +08:00
88647b1c41 🐛 Bug fixes 2025-08-30 23:26:19 +08:00
7438ba407a Turret shooting and damaing 2025-08-30 13:05:50 +08:00
1c6c03cd41 Turret tile basis 2025-08-30 12:03:26 +08:00
32f96d488d Enemy and nest 2025-08-30 02:06:58 +08:00
630dbf0800 :bugS: Fix natural resource generate missing iron 2025-08-29 18:11:00 +08:00
ac1d8cfab9 ♻️ Better miner tile 2025-08-29 18:05:30 +08:00
60b6d6f989 💄 Optimize the unable to place sound plays 2025-08-29 17:54:31 +08:00
483773f042 Merge stack
💫 Better miner tile vfx
2025-08-29 17:44:49 +08:00
56cd4c2db2 Miner tile 2025-08-29 16:59:59 +08:00
7720e74a3d New sfx and vfx and features in building 2025-08-29 14:36:05 +08:00
885d2c0075 Toggle build
🐛 Fix world gen tiles didn't align with placed ones
2025-08-29 13:46:18 +08:00
8073ed23c0 Multi threading world gen 2025-08-29 13:38:46 +08:00
862f11d445 Infinite world gen 2025-08-29 13:35:03 +08:00
9408651ea8 Item magnet 2025-08-29 12:41:08 +08:00
75cd807187 💄 Optimize vfx of item pickup 2025-08-29 12:39:23 +08:00
68 changed files with 2342 additions and 258 deletions

View File

@@ -4,4 +4,7 @@
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>AceFieldNewHorizon</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SimpleInjector" Version="5.5.0" />
</ItemGroup>
</Project>

View File

@@ -19,8 +19,38 @@
"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]
},
"reactor": {
"scene": "res://Scenes/Tiles/ReactorTile.tscn",
"cost": {},
"durability": 1000,
"buildTime": 5.0,
"allowedRotations": [0],
"layer": 1,
"size": [3, 3]
},
"enemy_portal": {
"scene": "res://Scenes/Tiles/EnemyPortalTile.tscn",
"cost": {},
"durability": 200,
"buildTime": 0.0,
"allowedRotations": [0],
"layer": 1,
"size": [1, 2]
},
"ground": {
"scene": "res://Scenes/Tiles/GroundTile.tscn",
"cost": {},

BIN
Scenes/Entities/Bullet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 KiB

View 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

View 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")

BIN
Scenes/Entities/Enemy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://x4u6oatvsm8y"
path="res://.godot/imported/Enemy.png-7a121c0bc2e7a40a7fe012e488d00452.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Scenes/Entities/Enemy.png"
dest_files=["res://.godot/imported/Enemy.png-7a121c0bc2e7a40a7fe012e488d00452.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

View File

@@ -0,0 +1,21 @@
[gd_scene load_steps=3 format=3 uid="uid://b3ffcucytwmk"]
[ext_resource type="Script" uid="uid://cvsmy820b8dwl" path="res://Scripts/Entities/Enemy.cs" id="1_jajit"]
[ext_resource type="Texture2D" uid="uid://x4u6oatvsm8y" path="res://Scenes/Entities/Enemy.png" id="2_jajit"]
[node name="Enemy" type="CharacterBody2D"]
collision_layer = 2
collision_mask = 3
script = ExtResource("1_jajit")
[node name="CollisionShape2D" type="CollisionPolygon2D" parent="."]
polygon = PackedVector2Array(-2, -21, 3, -21, 24, 6, 24, 10, 20, 14, -21, 14, -24, 11, -24, 7)
[node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(0.05, 0.05)
texture = ExtResource("2_jajit")
[node name="AttackArea" type="Area2D" parent="."]
[node name="CollisionShape2D" type="CollisionPolygon2D" parent="AttackArea"]
polygon = PackedVector2Array(-3, -24, 4, -24, 27, 5, 27, 14, 20, 19, -21, 19, -27, 14, -27, 5)

View File

@@ -4,7 +4,9 @@
[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="."]

View File

@@ -1,45 +1,30 @@
[gd_scene load_steps=8 format=3 uid="uid://c22aprj452aha"]
[gd_scene load_steps=6 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"]
[ext_resource type="PackedScene" uid="uid://byv2vu0k2drdd" path="res://Scenes/System/HUD.tscn" id="8_hud_scene"]
[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="NaturalResourceGenerator" type="Node2D" parent="." node_paths=PackedStringArray("Grid", "Registry")]
[node name="NaturalResourceGenerator" type="Node2D" parent="."]
script = ExtResource("2_oss8w")
Grid = NodePath("../GridSystem")
Registry = NodePath("../BuildingRegistry")
[node name="GridSystem" type="Node2D" parent="."]
script = ExtResource("1_knkkn")
[node name="PlacementSystem" type="Node2D" parent="." node_paths=PackedStringArray("Grid", "Inventory", "Registry")]
[node name="PlacementSystem" type="Node2D" parent="."]
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")]
[node name="Player" parent="." instance=ExtResource("3_oss8w")]
scale = Vector2(0.35, 0.35)
Inventory = NodePath("../ResourceSystem")
[node name="HUD" parent="." instance=ExtResource("8_hud_scene")]
[node name="ItemPickup" parent="." instance=ExtResource("7_is6ib")]
position = Vector2(-496, -245)
ItemId = "stone"
Infinite = true
Quantity = 64
[node name="ItemPickup2" parent="." instance=ExtResource("7_is6ib")]
position = Vector2(-495, 5)
ItemId = "ore_iron"
Infinite = true
Quantity = 16

13
Scenes/System/HUD.tscn Normal file
View File

@@ -0,0 +1,13 @@
[gd_scene load_steps=2 format=3 uid="uid://byv2vu0k2drdd"]
[ext_resource type="Script" uid="uid://ddoqqcg77f60v" path="res://Scripts/System/Hud.cs" id="1_yw4ou"]
[node name="HUD" type="CanvasLayer"]
script = ExtResource("1_yw4ou")
[node name="ResourceDisplay" type="VBoxContainer" parent="."]
layout_mode = 0
offset_left = 10.0
offset_top = 10.0
offset_right = 100.0
offset_bottom = 100.0

View File

@@ -4,16 +4,23 @@
[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(160, 160)
size = Vector2(50, 50)
[node name="ItemPickup" type="Node2D"]
[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="Area2D" type="Area2D" parent="."]
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
[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

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dv2xwfyshxdtp"
path="res://.godot/imported/EnemyPortalTile.png-3904776a211e67c58254b1bdc9aba071.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Scenes/Tiles/EnemyPortalTile.png"
dest_files=["res://.godot/imported/EnemyPortalTile.png-3904776a211e67c58254b1bdc9aba071.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

View File

@@ -0,0 +1,23 @@
[gd_scene load_steps=5 format=3 uid="uid://dup2su0s3ybcy"]
[ext_resource type="Script" uid="uid://26hl5mk4mqur" path="res://Scripts/Tiles/EnemyPortalTile.cs" id="1_o543x"]
[ext_resource type="PackedScene" uid="uid://b3ffcucytwmk" path="res://Scenes/Entities/Enemy.tscn" id="2_nh7ff"]
[ext_resource type="Texture2D" uid="uid://dv2xwfyshxdtp" path="res://Scenes/Tiles/EnemyPortalTile.png" id="3_i4us4"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_id484"]
size = Vector2(54, 79)
[node name="EnemyPortal" type="StaticBody2D"]
collision_layer = 0
collision_mask = 0
script = ExtResource("1_o543x")
EnemyScene = ExtResource("2_nh7ff")
TileId = "enemy_portal"
[node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(0.1, 0.1)
texture = ExtResource("3_i4us4")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, -0.5)
shape = SubResource("RectangleShape2D_id484")

View File

@@ -8,6 +8,7 @@ size = Vector2(54, 54)
[node name="GroundTile" type="StaticBody2D"]
collision_layer = 0
collision_mask = 0
script = ExtResource("1_mqsaf")
TileId = "ground"

View File

@@ -1,14 +1,15 @@
[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://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"]
scale = Vector2(3, 3)
script = ExtResource("1_mecoy")
ItemPickup = ExtResource("2_xhk0k")
TileId = "miner"
[node name="Sprite2D" type="Sprite2D" parent="."]

View File

@@ -8,8 +8,9 @@ size = Vector2(54, 54)
[node name="OreIronTile" type="StaticBody2D"]
collision_layer = 0
collision_mask = 0
script = ExtResource("1_exnim")
TileId = "stone_iron"
TileId = "ore_iron"
[node name="Sprite2D" type="Sprite2D" parent="."]
position = Vector2(1.49012e-08, -9.53674e-07)

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://fg03qxqphp7n"
path="res://.godot/imported/ReactorTile.png-f6f5bfaa813b044011d6b0a5736b9bc6.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Scenes/Tiles/ReactorTile.png"
dest_files=["res://.godot/imported/ReactorTile.png-f6f5bfaa813b044011d6b0a5736b9bc6.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

View File

@@ -0,0 +1,26 @@
[gd_scene load_steps=4 format=3 uid="uid://w6ni678js7cu"]
[ext_resource type="Script" uid="uid://c4k3ottt7j3b1" path="res://Scripts/Tiles/ReactorTile.cs" id="1_yldg2"]
[ext_resource type="Texture2D" uid="uid://fg03qxqphp7n" path="res://Scenes/Tiles/ReactorTile.png" id="3_fk1vt"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_8o613"]
size = Vector2(54, 54)
[node name="ReactorTile" type="StaticBody2D"]
script = ExtResource("1_yldg2")
TileId = "reactor"
[node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(0.3, 0.3)
texture = ExtResource("3_fk1vt")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
scale = Vector2(3, 3)
shape = SubResource("RectangleShape2D_8o613")
[node name="ProgressOverlay" type="ColorRect" parent="."]
offset_left = -81.0
offset_top = -81.0
offset_right = -27.0
offset_bottom = -27.0
scale = Vector2(3, 3)

View File

@@ -8,6 +8,7 @@ size = Vector2(54, 54)
[node name="StoneTile" type="StaticBody2D"]
collision_layer = 0
collision_mask = 0
script = ExtResource("1_rndy8")
TileId = "stone"

BIN
Scenes/Tiles/TurretTile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

View 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

View 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, -225)
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

View 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

View File

@@ -0,0 +1,14 @@
using Godot;
using AceFieldNewHorizon.Scripts.System;
namespace AceFieldNewHorizon.Scripts.AutoLoad;
public partial class DIInitializer : Node
{
public override void _Ready()
{
// Initialize the Simple Injector container as early as possible
DependencyInjection.Initialize();
GD.Print("[DIInitializer] Dependency Injection container initialized via AutoLoad.");
}
}

View File

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

View 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;
}
}

View File

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

13
Scripts/Entities/Enemy.cs Normal file
View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using AceFieldNewHorizon.Scripts.Tiles;
using Godot;
using AceFieldNewHorizon.Scripts.System;
namespace AceFieldNewHorizon.Scripts.Entities;
public partial class Enemy : BaseEnemy
{
// All the base functionality is now in BaseEnemy
// This class is kept for backward compatibility and can be used to add
// specific behaviors for the basic enemy type if needed
}

View File

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

View File

@@ -0,0 +1,283 @@
using System.Collections.Generic;
using AceFieldNewHorizon.Scripts.System;
using AceFieldNewHorizon.Scripts.Tiles;
using Godot;
namespace AceFieldNewHorizon.Scripts.Entities;
public abstract partial class BaseEnemy : CharacterBody2D
{
public const string EnemyGroupName = "Enemy";
[Export] public float MoveSpeed = 150.0f;
[Export] public float DetectionRadius = 300.0f;
[Export] public float AttackRadius = 80.0f;
[Export] public int Damage = 10;
[Export] public float AttackCooldown = 3.0f;
[Export] public int MaxHealth = 100;
[Export] public bool ShowDamageNumbers = true;
protected BaseTile TargetTile;
protected ReactorTile Reactor;
protected float AttackTimer = 0;
protected Area2D DetectionArea;
protected Area2D AttackArea;
protected int CurrentHealthValue;
protected ProgressBar HealthBar;
protected GridManager Grid;
// Track collisions with potential targets
protected readonly HashSet<BaseTile> CollidingTiles = [];
public int CurrentHealth
{
get => CurrentHealthValue;
protected set
{
CurrentHealthValue = Mathf.Clamp(value, 0, MaxHealth);
UpdateHealthBar();
if (CurrentHealthValue <= 0)
{
Die();
}
}
}
public bool IsDead => CurrentHealthValue <= 0;
public override void _Ready()
{
CurrentHealthValue = MaxHealth;
Grid = DependencyInjection.Container.GetInstance<GridManager>();
Reactor = GetTree().GetFirstNodeInGroup(ReactorTile.ReactorGroupName) as ReactorTile;
InitializeHealthBar();
InitializeDetectionArea();
InitializeAttackArea();
AddToGroup(EnemyGroupName);
}
protected virtual void InitializeHealthBar()
{
HealthBar = new ProgressBar
{
MaxValue = MaxHealth,
Value = CurrentHealthValue,
Size = new Vector2(40, 4),
ShowPercentage = false,
Visible = false
};
var healthBarContainer = new Control();
healthBarContainer.AddChild(HealthBar);
AddChild(healthBarContainer);
healthBarContainer.Position = new Vector2(-20, -20);
}
protected virtual void InitializeDetectionArea()
{
DetectionArea = new Area2D();
var collisionShape = new CollisionShape2D();
var shape = new CircleShape2D();
shape.Radius = DetectionRadius;
collisionShape.Shape = shape;
DetectionArea.AddChild(collisionShape);
AddChild(DetectionArea);
DetectionArea.BodyEntered += OnBodyEnteredDetection;
DetectionArea.BodyExited += OnBodyExitedDetection;
}
protected virtual void InitializeAttackArea()
{
AttackArea = GetNodeOrNull<Area2D>("AttackArea");
if (AttackArea != null)
{
AttackArea.BodyEntered += OnBodyEntered;
AttackArea.BodyExited += OnBodyExited;
}
}
public override void _Process(double delta)
{
if (IsDead) return;
if (TargetTile == null || TargetTile.IsDestroyed || !TargetTile.IsInsideTree())
{
UpdateTarget();
}
MoveTowardsTarget();
HandleAttacks(delta);
}
protected virtual void MoveTowardsTarget()
{
if (TargetTile != null)
{
var direction = GlobalPosition.DirectionTo(TargetTile.GlobalPosition);
Velocity = direction * MoveSpeed;
LookAt(TargetTile.GlobalPosition);
MoveAndSlide();
}
}
protected virtual void HandleAttacks(double delta)
{
if (AttackTimer > 0)
{
AttackTimer -= (float)delta;
}
else if (CollidingTiles.Count > 0)
{
foreach (var tile in CollidingTiles)
{
if (tile != null && !tile.IsDestroyed && tile.IsInsideTree())
{
TryAttackTile(tile);
break;
}
}
}
}
protected virtual void UpdateTarget()
{
// If we have a valid target in collision, use that
foreach (var tile in CollidingTiles)
{
if (tile != null && !tile.IsDestroyed && tile.IsInsideTree())
{
TargetTile = tile;
return;
}
}
// Otherwise find the reactor
if (Reactor != null && !Reactor.IsDestroyed && Reactor.IsInsideTree())
{
TargetTile = Reactor;
}
else
{
TargetTile = null;
}
}
protected virtual void TryAttackTile(BaseTile tile)
{
if (IsDead || tile == null || tile.IsDestroyed || !tile.IsInsideTree())
return;
AttackTimer = AttackCooldown;
bool wasDestroyed = tile.TakeDamage(Damage);
GD.Print($"Attacking {tile.Name} for {Damage} damage. Was destroyed: {wasDestroyed}");
if (wasDestroyed)
{
CollidingTiles.Remove(tile);
if (TargetTile == tile)
{
TargetTile = null;
}
}
}
protected virtual void OnBodyEntered(Node2D body)
{
if (body is BaseTile { IsDestroyed: false } tile && !body.IsInGroup("Hostile"))
{
GD.Print($"[Enemy] {body.Name} Entered attack range");
CollidingTiles.Add(tile);
if (TargetTile == null || TargetTile.IsDestroyed || !TargetTile.IsInsideTree())
{
TargetTile = tile;
}
// Attack immediately on collision
TryAttackTile(tile);
}
}
protected virtual void OnBodyExited(Node2D body)
{
if (body.GetParent() is BaseTile tile)
{
CollidingTiles.Remove(tile);
if (TargetTile == tile)
{
TargetTile = null;
}
}
}
protected virtual void OnBodyEnteredDetection(Node2D body)
{
// Can be overridden by derived classes
}
protected virtual void OnBodyExitedDetection(Node2D body)
{
// Can be overridden by derived classes
}
public virtual 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);
}
protected virtual void UpdateHealthBar()
{
if (HealthBar != null)
{
HealthBar.Value = CurrentHealthValue;
HealthBar.Visible = CurrentHealthValue < MaxHealth; // Only show when damaged
}
}
protected virtual 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);
}
}

View File

@@ -0,0 +1 @@
uid://6oduws4kbdlf

View File

@@ -1,4 +1,3 @@
using System;
using AceFieldNewHorizon.Scripts.System;
using Godot;
@@ -20,7 +19,7 @@ public partial class Player : CharacterBody2D
[Export] public float ZoomDecay = 0.9f;
[Export] public float ZoomSmoothing = 10.0f;
[Export] public ResourceManager Inventory;
public ResourceManager Inventory { get; private set; }
private Camera2D _camera;
private Vector2 _cameraTargetZoom = Vector2.One;
@@ -33,7 +32,10 @@ public partial class Player : CharacterBody2D
_camera = GetNode<Camera2D>("Camera2D");
_cameraTargetZoom = _camera.Zoom;
Inventory = DependencyInjection.Container.GetInstance<ResourceManager>();
AddToGroup(ItemPickup.PickupGroupName);
AddToGroup(NaturalResourceGenerator.ChunkTrackerGroupName);
}
public override void _Input(InputEvent @event)
@@ -59,13 +61,9 @@ public partial class Player : CharacterBody2D
// 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;

27
Scripts/Root.cs Normal file
View File

@@ -0,0 +1,27 @@
using Godot;
using AceFieldNewHorizon.Scripts.System;
using SimpleInjector;
namespace AceFieldNewHorizon.Scripts;
public partial class Root : Node
{
public override void _Ready()
{
// Dependency Injection container is now initialized via AutoLoad (DIInitializer.cs).
// Get references to the main system nodes from the scene tree
// and inject their dependencies.
// This assumes these nodes are direct children or easily accessible.
// You might need to adjust paths based on your scene setup.
// Example:
// var resourceManager = GetNode<ResourceManager>("ResourceSystem"); // Assuming ResourceManager is a child of Root
// DependencyInjection.Container.InjectProperties(resourceManager); // If ResourceManager had properties to inject
// For now, we'll manually resolve and assign for the main system nodes.
// The actual injection will happen in the _Ready methods of the system nodes themselves,
// by resolving from the static container.
// This is a common pattern when Godot instantiates the nodes.
}
}

1
Scripts/Root.cs.uid Normal file
View File

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

View File

@@ -40,15 +40,15 @@ public record BuildingData(
}
}
public partial class BuildingRegistry : Node
public class BuildingRegistry
{
private Dictionary<string, BuildingData> _registry = new();
[Export] public string JsonPath { get; set; } = "res://Data/Buildings.json";
public override void _Ready()
public BuildingRegistry(string jsonPath)
{
LoadFromJson(JsonPath);
LoadFromJson(jsonPath);
}
public void LoadFromJson(string path)

View File

@@ -0,0 +1,23 @@
using Godot;
using Container = SimpleInjector.Container;
namespace AceFieldNewHorizon.Scripts.System;
public static class DependencyInjection
{
public static Container Container { get; private set; }
public static void Initialize()
{
Container = new Container();
// Register your system services here
// As singletons, since they are typically unique global managers
Container.RegisterSingleton<ResourceManager>();
Container.RegisterSingleton<GridManager>();
Container.RegisterSingleton(() => new BuildingRegistry("res://Data/Buildings.json"));
Container.Verify();
GD.Print("[DI] Simple Injector container initialized and verified.");
}
}

View File

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

View File

@@ -43,15 +43,33 @@ public partial class GridManager : Node
public void FreeArea(Vector2I topLeft, Vector2I size, float rotation, GridLayer layer = GridLayer.Building)
{
// Get all cells that should be occupied by this building
var occupiedCells = GridUtils.GetOccupiedCells(topLeft, size, rotation);
foreach (var cell in occupiedCells)
// Create a list to store cells that should be removed
var cellsToRemove = new List<Vector2I>();
// First, find all cells that match this building's position and size
foreach (var cell in _layers[layer].Keys.ToList())
{
if (_layers[layer].ContainsKey(cell))
_layers[layer].Remove(cell);
var (building, buildingSize, buildingRotation) = _layers[layer][cell];
var buildingCells = GridUtils.GetOccupiedCells(cell, buildingSize, buildingRotation);
// If any of the building's cells match our target area, mark all of its cells for removal
if (buildingCells.Any(c => occupiedCells.Contains(c)))
{
cellsToRemove.AddRange(buildingCells);
}
}
// Remove all marked cells
foreach (var cell in cellsToRemove.Distinct())
{
_layers[layer].Remove(cell);
}
}
public Node2D? GetBuildingAtCell(Vector2I cell, GridLayer layer = GridLayer.Building)
public Node2D? GetTileAtCell(Vector2I cell, GridLayer layer = GridLayer.Building)
{
return _layers[layer].TryGetValue(cell, out var data) ? data.Building : null;
}

102
Scripts/System/Hud.cs Normal file
View File

@@ -0,0 +1,102 @@
using Godot;
using System.Collections.Generic;
namespace AceFieldNewHorizon.Scripts.System;
public partial class Hud : CanvasLayer
{
private ResourceManager _resourceManager;
private VBoxContainer _resourceDisplay;
private readonly Dictionary<string, Label> _resourceLabels = new();
private VBoxContainer _pickupLogContainer;
public override void _Ready()
{
_resourceDisplay = GetNode<VBoxContainer>("ResourceDisplay");
_pickupLogContainer = new VBoxContainer();
_pickupLogContainer.Name = "PickupLogContainer";
_pickupLogContainer.SetAnchorsPreset(Control.LayoutPreset.BottomLeft);
_pickupLogContainer.OffsetLeft = 10;
_pickupLogContainer.OffsetBottom = -10; // Negative offset from bottom anchor
_pickupLogContainer.GrowVertical = Control.GrowDirection.Begin; // Make it grow upwards
AddChild(_pickupLogContainer);
_resourceManager = DependencyInjection.Container.GetInstance<ResourceManager>();
if (_resourceManager == null)
{
GD.PushError("ResourceSystem not found in the scene tree!");
return;
}
_resourceManager.OnResourceChanged += UpdateResourceDisplay;
_resourceManager.OnResourcePickedUp += OnResourcePickedUp;
// Initialize display with current resources
foreach (var entry in _resourceManager.GetAllResources())
{
UpdateResourceDisplay(entry.Key, entry.Value);
}
}
private void UpdateResourceDisplay(string resourceId, int newAmount)
{
if (!_resourceLabels.TryGetValue(resourceId, out Label label))
{
label = new Label();
_resourceDisplay.AddChild(label);
_resourceLabels.Add(resourceId, label);
}
if (newAmount <= 0)
{
// Remove label if resource amount is zero or less
if (label.GetParent() != null)
{
_resourceDisplay.RemoveChild(label);
}
_resourceLabels.Remove(resourceId);
label.QueueFree(); // Free the label from memory
}
else
{
label.Text = $"{resourceId}: {newAmount}";
}
}
private void OnResourcePickedUp(string resourceId, int amount)
{
AddPickupLogEntry($"Picked up {amount} {resourceId}!");
}
private void AddPickupLogEntry(string message)
{
var logLabel = new Label();
logLabel.Text = message;
_pickupLogContainer.AddChild(logLabel);
var timer = new Timer();
timer.WaitTime = 3.0f;
timer.OneShot = true;
timer.Autostart = true;
logLabel.AddChild(timer); // Add timer as child of the label
timer.Timeout += () => OnLogEntryTimeout(logLabel);
}
private void OnLogEntryTimeout(Label logLabel)
{
// Start fading out the label
var tween = logLabel.CreateTween();
tween.TweenProperty(logLabel, "modulate", new Color(1, 1, 1, 0), 0.5f); // Fade out over 0.5 seconds
tween.TweenCallback(Callable.From(() => logLabel.QueueFree())); // Free after fading
}
public override void _ExitTree()
{
if (_resourceManager != null)
{
_resourceManager.OnResourceChanged -= UpdateResourceDisplay;
_resourceManager.OnResourcePickedUp -= OnResourcePickedUp;
}
}
}

View File

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

View File

@@ -1,25 +1,124 @@
using System.Collections;
using AceFieldNewHorizon.Scripts.Entities;
using Godot;
namespace AceFieldNewHorizon.Scripts.System;
public partial class ItemPickup : Node2D
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()
{
var area = GetNode<Area2D>("Area2D");
area.BodyEntered += OnBodyEntered;
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()
@@ -57,15 +156,32 @@ public partial class ItemPickup : Node2D
_sprite.Texture = texture;
}
private void OnBodyEntered(Node body)
private void OnEntered(Node body)
{
if (body.IsInGroup(PickupGroupName))
if (body is ItemPickup itemStack)
{
if (body.HasMethod("AddItem"))
body.Call("AddItem", ItemId, Quantity);
// 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(); // remove the pickup from the world
QueueFree();
}
}
}

View File

@@ -1,153 +1,492 @@
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
{
[Export] public GridManager Grid { get; set; }
[Export] public BuildingRegistry Registry { get; set; }
[Export] public int MapWidth = 100;
[Export] public int MapHeight = 100;
[Export] public float StoneDensity = 0.1f; // 10% chance for stone
[Export] public float IronDensity = 0.03f; // 3% chance for iron (within stone)
[Export] public int MinStoneVeinSize = 1;
[Export] public int MaxStoneVeinSize = 5;
[Export] public int MinIronVeinSize = 1;
[Export] public int MaxIronVeinSize = 3;
[Export] public int Seed;
public const string ChunkTrackerGroupName = "NrgTrackingTarget";
private RandomNumberGenerator _rng;
private readonly List<Vector2I> _groundTiles = [];
private readonly List<Vector2I> _stoneTiles = [];
private readonly List<Vector2I> _ironTiles = [];
public GridManager Grid { get; private set; }
public BuildingRegistry Registry { get; private set; }
public override void _Ready()
{
_rng = new RandomNumberGenerator();
_rng.Seed = (ulong)(Seed != 0 ? Seed : (int)GD.Randi());
GenerateTerrain();
PlaceResources();
}
[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;
private void GenerateTerrain()
{
// First pass: Generate base ground tiles
for (int x = 0; x < MapWidth; x++)
{
for (int y = 0; y < MapHeight; y++)
{
var cell = new Vector2I(x, y);
_groundTiles.Add(cell);
PlaceTile("ground", cell);
}
}
}
[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 void PlaceResources()
{
// Create a copy of ground tiles for iteration
var groundTilesToProcess = new List<Vector2I>(_groundTiles);
// Place stone veins
foreach (var cell in groundTilesToProcess)
{
if (_rng.Randf() < StoneDensity)
{
var veinSize = _rng.RandiRange(MinStoneVeinSize, MaxStoneVeinSize);
PlaceVein(cell, "stone", veinSize, _stoneTiles);
}
}
private const string LogPrefix = "[NaturalGeneration]";
// Create a copy of stone tiles for iteration
var stoneTilesToProcess = new List<Vector2I>(_stoneTiles);
// Place iron veins within stone
foreach (var stoneCell in stoneTilesToProcess)
{
if (_rng.Randf() < IronDensity)
{
var veinSize = _rng.RandiRange(MinIronVeinSize, MaxIronVeinSize);
PlaceVein(stoneCell, "ore_iron", veinSize, _ironTiles);
}
}
}
private RandomNumberGenerator _rng;
private readonly Dictionary<Vector2I, ChunkData> _loadedChunks = new();
private Vector2I _lastPlayerChunk = new(-1000, -1000);
private void PlaceVein(Vector2I startCell, string tileType, int maxVeinSize, ICollection<Vector2I> tileList)
{
var queue = new Queue<Vector2I>();
var placed = new HashSet<Vector2I>();
queue.Enqueue(startCell);
private Player _player;
int placedCount = 0;
while (queue.Count > 0 && placedCount < maxVeinSize)
{
var cell = queue.Dequeue();
if (placed.Contains(cell)) continue;
if (!IsInBounds(cell)) continue;
private readonly ConcurrentQueue<Action> _mainThreadActions = new();
private Task _generationTask;
private bool _isRunning = true;
switch (tileType)
{
// For iron, make sure we're placing on stone
case "ore_iron" when !_stoneTiles.Contains(cell):
continue;
// Remove from previous layer if needed
case "ore_iron" when _stoneTiles.Contains(cell):
_stoneTiles.Remove(cell);
break;
case "stone" when _groundTiles.Contains(cell):
_groundTiles.Remove(cell);
break;
}
public override void _Ready()
{
Grid = DependencyInjection.Container.GetInstance<GridManager>();
Registry = DependencyInjection.Container.GetInstance<BuildingRegistry>();
_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_portal is in the registry
var testBuilding = Registry.GetBuilding("enemy_portal");
if (testBuilding == null)
{
GD.PrintErr($"{LogPrefix} 'enemy_portal' is not found in BuildingRegistry!");
}
else
{
GD.Print($"{LogPrefix} Found enemy_portal in registry!");
}
GD.Print($"{LogPrefix} NaturalResourceGenerator ready, SpawnEnemyNest = {SpawnEnemyNest}");
GD.Print($"{LogPrefix} Spawning the core reactor...");
SpawnCoreReactor();
if (SpawnEnemyNest)
{
GD.Print($"{LogPrefix} Attempting to spawn enemy nest...");
SpawnRandomEnemyNest();
}
}
PlaceTile(tileType, cell);
tileList.Add(cell);
placed.Add(cell);
placedCount++;
public override void _Process(double delta)
{
// Process any pending main thread actions
while (_mainThreadActions.TryDequeue(out var action))
{
action.Invoke();
}
// Add adjacent cells to queue
for (var dx = -1; dx <= 1; dx++)
{
for (var dy = -1; dy <= 1; dy++)
{
if (dx == 0 && dy == 0) continue; // Skip self
var neighbor = new Vector2I(cell.X + dx, cell.Y + dy);
if (!placed.Contains(neighbor) && IsInBounds(neighbor))
{
queue.Enqueue(neighbor);
}
}
}
}
}
// 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 bool IsInBounds(Vector2I cell)
{
return cell.X >= 0 && cell.X < MapWidth &&
cell.Y >= 0 && cell.Y < MapHeight;
}
private void UpdatePlayerPosition(Vector2 playerPosition)
{
var playerChunk = WorldToChunkCoords(playerPosition);
private void PlaceTile(string tileType, Vector2I cell)
{
var building = Registry.GetBuilding(tileType);
if (building == null) return;
if (playerChunk == _lastPlayerChunk) return;
_lastPlayerChunk = playerChunk;
var scene = building.Scene;
var instance = (BaseTile)scene.Instantiate();
// Match PlacementManager's positioning logic
var rotatedSize = building.GetRotatedSize(0f);
var offset = GridUtils.GetCenterOffset(rotatedSize, 0f);
instance.Position = GridUtils.GridToWorld(cell) + offset;
instance.ZIndex = (int)building.Layer;
AddChild(instance);
// Start generation in background task if not already running
if (_generationTask == null || _generationTask.IsCompleted)
{
_generationTask = Task.Run(() => GenerateChunksAroundPlayer(playerChunk));
}
}
// Make sure to use the building's size from the registry
Grid.OccupyArea(cell, instance, building.Size, 0f, building.Layer);
}
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 (var x = 0; x < ChunkSize; x++)
{
for (var 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 SpawnCoreReactor()
{
// Place the reactor tile at the center of the map
PlaceTile("reactor", new Vector2I(0, 0));
}
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
var attempts = 0;
const int maxAttempts = 10;
while (attempts < maxAttempts)
{
if (PlaceTile("enemy_portal", 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
{
var building = Registry.GetBuilding(tileType);
if (building == null)
{
GD.PrintErr($"{LogPrefix} Building type not found in registry: {tileType}");
return false;
}
// Free area for ground layer if needed
if (building.Layer == GridLayer.Ground)
Grid.FreeArea(cell, building.Size, 0f, GridLayer.Ground);
else
GD.Print($"{LogPrefix} Attempting placing building tile {tileType} at {cell}.");
var scene = building.Scene;
if (scene == null)
{
GD.PrintErr($"{LogPrefix} Scene is null for building type: {tileType}");
return false;
}
if (scene.Instantiate() is not BaseTile instance)
{
GD.PrintErr($"{LogPrefix} Failed to instantiate scene for: {tileType}");
return false;
}
// Calculate position with proper offset based on building size
var rotatedSize = building.GetRotatedSize(0f);
var offset = GridUtils.GetCenterOffset(rotatedSize, 0f);
instance.ZIndex = (int)building.Layer;
instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset;
AddChild(instance);
// Occupy the appropriate area based on building size
Grid.OccupyArea(cell, instance, building.Size, 0f, building.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; } = [];
}

View File

@@ -8,40 +8,65 @@ 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; }
[Export] public int MaxConcurrentBuilds { get; set; } = 6; // Make it adjustable in editor
public GridManager Grid { get; private set; }
public ResourceManager Inventory { get; private set; }
public BuildingRegistry Registry { get; private set; }
private static readonly List<string> BuildableTiles = ["wall", "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();
Grid = DependencyInjection.Container.GetInstance<GridManager>();
Inventory = DependencyInjection.Container.GetInstance<ResourceManager>();
Registry = DependencyInjection.Container.GetInstance<BuildingRegistry>();
// Setup completion sound
_completionSound = new AudioStreamPlayer();
AddChild(_completionSound);
var sound = GD.Load<AudioStream>("res://Sounds/Events/ConstructionComplete.wav");
_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)
{
_completionSound.Stream = sound;
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();
.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)
{
@@ -54,13 +79,13 @@ public partial class PlacementManager : Node2D
{
// Remove completed builds
var completed = _buildTasks.Where(kvp => kvp.Value.IsCompleted)
.Select(kvp => kvp.Key)
.ToList();
.Select(kvp => kvp.Key)
.ToList();
foreach (var key in completed)
{
_buildTasks.Remove(key);
}
return _buildTasks.Count < MaxConcurrentBuilds;
}
@@ -72,7 +97,7 @@ public partial class PlacementManager : Node2D
public void Complete(bool wasCancelled = false)
{
if (IsCompleted) return;
IsCompleted = true;
WasCancelled = wasCancelled;
onCompleted?.Invoke();
@@ -153,6 +178,8 @@ public partial class PlacementManager : Node2D
public override void _Process(double delta)
{
if (!Enabled) return;
// Snap mouse to grid
var mousePos = GetGlobalMousePosition();
var newHoveredCell = GridUtils.WorldToGrid(mousePos);
@@ -188,24 +215,39 @@ public partial class PlacementManager : Node2D
_ghostBuilding.SetGhostMode(canPlace);
// Left click to place
if (Input.IsActionPressed("build_tile") && canPlace)
if (Input.IsActionPressed("build_tile"))
{
var building = Registry.GetBuilding(_currentBuildingId);
if (building == null) return;
if (!CanStartNewBuild())
{
// Optionally show feedback to player that build queue is full
_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))
{
// Optionally show feedback to player that they can't afford this building
_insufficientFundsSound.Play();
return;
}
// Create the building instance
var scene = building.Scene;
var buildingInstance = (BaseTile)scene.Instantiate();
buildingInstance.RotationDegrees = _currentRotation;
@@ -213,23 +255,21 @@ public partial class PlacementManager : Node2D
buildingInstance.Position = _ghostBuilding.Position;
AddChild(buildingInstance);
// Check if area is free before placing
if (!IsAreaFree(_hoveredCell, building.Size, _currentRotation, building.Layer))
{
RefundBuildingResources(_currentBuildingId);
buildingInstance.QueueFree();
return;
}
// Occupy the area
// 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;
buildingInstance.StartConstruction(building.BuildTime, () => {
// 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))
{
@@ -239,6 +279,7 @@ public partial class PlacementManager : Node2D
Grid.FreeArea(_hoveredCell, building.Size, _currentRotation, building.Layer);
buildingInstance.QueueFree();
}
task.Complete();
_buildTasks.Remove(buildingInstance);
}
@@ -250,11 +291,30 @@ public partial class PlacementManager : Node2D
!Grid.IsAreaFree(_hoveredCell, Vector2I.One, 0f))
{
// Right click to destroy from current layer
var building = Grid.GetBuildingAtCell(_hoveredCell);
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);
}
@@ -267,6 +327,21 @@ public partial class PlacementManager : Node2D
}
}
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();
@@ -276,41 +351,41 @@ public partial class PlacementManager : Node2D
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)
{
@@ -353,32 +428,28 @@ public static class GridManagerExtensions
public static (Vector2I Position, Vector2I Size, float Rotation)? GetBuildingInfoAtCell(this GridManager grid,
Vector2I cell, GridLayer layer)
{
if (grid.GetBuildingAtCell(cell, layer) is { } building)
if (grid.GetTileAtCell(cell, layer) is not { } building) return null;
// Find the top-left position of the building
for (var x = 0; x < 100; x++) // Arbitrary max size
{
// Find the top-left position of the building
for (int x = 0; x < 100; x++) // Arbitrary max size
for (var y = 0; y < 100; y++)
{
for (int y = 0; y < 100; y++)
{
var checkCell = new Vector2I(cell.X - x, cell.Y - y);
if (grid.GetBuildingAtCell(checkCell, layer) == building)
{
// Found the top-left corner, now find the size
var size = Vector2I.One;
// Search right
while (grid.GetBuildingAtCell(new Vector2I(checkCell.X + size.X, checkCell.Y), layer) ==
building)
size.X++;
// Search down
while (grid.GetBuildingAtCell(new Vector2I(checkCell.X, checkCell.Y + size.Y), layer) ==
building)
size.Y++;
var checkCell = new Vector2I(cell.X - x, cell.Y - y);
if (grid.GetTileAtCell(checkCell, layer) != building) continue;
// 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);
}
}
// Get rotation from the first cell
var rotation = 0f; // You'll need to store rotation in Grid to make this work
return (checkCell, size, rotation);
}
}

View File

@@ -13,6 +13,10 @@ public partial class ResourceManager : Node
// Event for when resource amounts change
[Signal]
public delegate void OnResourceChangedEventHandler(string resourceId, int newAmount);
// Event for when resources are picked up (added)
[Signal]
public delegate void OnResourcePickedUpEventHandler(string resourceId, int amount);
public override void _Ready()
{
@@ -43,6 +47,7 @@ public partial class ResourceManager : Node
}
EmitSignal(nameof(OnResourceChanged), resourceId, _resources[resourceId]);
EmitSignal(nameof(OnResourcePickedUp), resourceId, amount);
}
// Remove resources of a specific type

View File

@@ -10,22 +10,45 @@ public partial class BaseTile : Node2D
{
[Export] public string TileId { get; set; }
protected GridManager Grid { get; set; }
protected BuildingRegistry Registry { get; set; }
public int MaxDurability { get; private set; }
public int CurrentDurability { get; private set; }
public bool IsDestroyed { get; private set; }
private CollisionShape2D _collisionShape;
private Sprite2D _sprite;
private ColorRect _progressOverlay;
private Action _onConstructionComplete;
private Tween _damageTween;
public bool IsConstructing;
public bool IsConstructed;
public override void _Ready()
{
Grid = DependencyInjection.Container.GetInstance<GridManager>();
Registry = DependencyInjection.Container.GetInstance<BuildingRegistry>();
_collisionShape = GetNodeOrNull<CollisionShape2D>("CollisionShape2D");
_sprite = GetNodeOrNull<Sprite2D>("Sprite2D");
_progressOverlay = GetNodeOrNull<ColorRect>("ProgressOverlay");
if (_progressOverlay != null)
_progressOverlay.Visible = false;
// Get durability from BuildingRegistry
var buildingData = Registry?.GetBuilding(TileId);
MaxDurability = buildingData?.Durability ?? 100; // Default to 100 if not found
CurrentDurability = MaxDurability;
IsDestroyed = 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;
@@ -35,7 +58,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;
@@ -43,11 +66,91 @@ public partial class BaseTile : Node2D
_sprite.Modulate = Colors.White;
}
public virtual bool TakeDamage(int damage)
{
if (IsDestroyed || IsConstructing) return false;
GD.Print($"[Tile] {TileId} {GetInstanceId()} took {damage} damage");
CurrentDurability = Mathf.Max(0, CurrentDurability - damage);
// Visual feedback for taking damage
ShowDamageEffect();
if (CurrentDurability <= 0)
{
Destroy();
return true; // Tile was destroyed
}
return false; // Tile is still alive
}
private void ShowDamageEffect()
{
if (_sprite == null) return;
// Cancel any existing tween
_damageTween?.Kill();
_damageTween = CreateTween();
// Flash red briefly
_damageTween.TweenProperty(_sprite, "modulate", Colors.Red, 0.1f);
_damageTween.TweenProperty(_sprite, "modulate", Colors.White, 0.1f);
// Shake effect
var originalPosition = _sprite.Position;
_damageTween.TweenMethod(
Callable.From<Vector2>(pos => _sprite.Position = pos),
originalPosition + new Vector2(-2, -2),
originalPosition + new Vector2(2, 2),
0.05f
).SetTrans(Tween.TransitionType.Bounce).SetEase(Tween.EaseType.InOut);
_damageTween.TweenMethod(
Callable.From<Vector2>(pos => _sprite.Position = pos),
originalPosition + new Vector2(2, 2),
originalPosition,
0.05f
).SetTrans(Tween.TransitionType.Bounce).SetEase(Tween.EaseType.InOut);
}
public virtual void Destroy()
{
if (IsDestroyed) return;
IsDestroyed = true;
// Disable collision using SetDeferred to avoid physics update conflicts
_collisionShape?.SetDeferred("disabled", true);
// Fade out and remove
var tween = CreateTween();
tween.TweenProperty(this, "modulate:a", 0f, 0.3f);
tween.TweenCallback(Callable.From(() => CallDeferred("queue_free")));
// Emit signal or call method when tile is destroyed
CallDeferred(nameof(OnTileDestroyed));
}
protected virtual void OnTileDestroyed()
{
// Can be overridden by derived classes for custom destruction behavior
var cell = GridUtils.WorldToGrid(Position);
var buildingInfo = Registry.GetBuilding(TileId);
Grid.FreeArea(cell, buildingInfo.Size, Rotation, buildingInfo.Layer);
}
// Building progress visualization
public void StartConstruction(float buildTime, Action onComplete = null)
{
if (_progressOverlay == null || _sprite?.Texture == null)
IsConstructing = true;
if (_collisionShape != null)
_collisionShape.Disabled = true;
if (_progressOverlay == null || _sprite?.Texture == null)
{
IsConstructing = false;
onComplete?.Invoke();
return;
}
@@ -55,6 +158,10 @@ public partial class BaseTile : Node2D
_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
@@ -79,9 +186,17 @@ public partial class BaseTile : Node2D
// Fade out the overlay
await FadeOutOverlay(0.5f);
// Notify completion
// 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();

View File

@@ -0,0 +1,129 @@
using AceFieldNewHorizon.Scripts.Entities;
using Godot;
namespace AceFieldNewHorizon.Scripts.Tiles;
public partial class EnemyPortalTile : 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;
public override void _Ready()
{
base._Ready();
// Add to Hostile group to prevent enemies from attacking their own nest
AddToGroup("Hostile");
// Create and configure the timer
_spawnTimer = new Timer
{
Autostart = true,
WaitTime = SpawnDelay
};
AddChild(_spawnTimer);
_spawnTimer.Timeout += OnSpawnTimerTimeout;
var sprite = GetNode<Sprite2D>("Sprite2D");
// Create and configure the shadow sprite
var shadow = new Sprite2D
{
Texture = sprite.Texture,
Scale = sprite.Scale * 1.05f, // Slightly larger than the original
Modulate = new Color(0, 0, 0, 0.5f), // Slightly more transparent
Position = new Vector2(0, 6), // Closer to the sprite (reduced from 30)
ZIndex = -1
};
AddChild(shadow);
// Create floating animation
const float floatOffset = 5.0f;
const float floatDuration = 2.0f;
var tween = CreateTween().SetLoops();
tween.TweenProperty(sprite, "position:y", -floatOffset, floatDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
tween.TweenProperty(sprite, "position:y", floatOffset, floatDuration * 2)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
tween.TweenProperty(sprite, "position:y", 0, floatDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
// Animate shadow
tween.Parallel().TweenProperty(shadow, "position:y", 12 - (floatOffset * 0.3f), floatDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
tween.Parallel().TweenProperty(shadow, "scale", sprite.Scale * 1.02f, floatDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
tween.Parallel().TweenProperty(shadow, "modulate:a", 0.6f, floatDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
tween.Parallel().TweenProperty(shadow, "position:y", 12 + (floatOffset * 0.4f), floatDuration * 2)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine)
.SetDelay(floatDuration);
tween.Parallel().TweenProperty(shadow, "scale", sprite.Scale * 1.08f, floatDuration * 2)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine)
.SetDelay(floatDuration);
tween.Parallel().TweenProperty(shadow, "modulate:a", 0.4f, floatDuration * 2)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine)
.SetDelay(floatDuration);
}
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;
}
}

View File

@@ -0,0 +1 @@
uid://26hl5mk4mqur

View File

@@ -8,6 +8,7 @@ public partial class GroundTile : BaseTile
{
var sprite = GetNode<Sprite2D>("Sprite2D");
sprite.Modulate = new Color(0.75f, 0.75f, 0.75f); // Makes the sprite 25% darker
sprite.ZIndex = -10;
base._Ready();
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,13 @@
namespace AceFieldNewHorizon.Scripts.Tiles;
public partial class ReactorTile : BaseTile
{
public static readonly string ReactorGroupName = "Reactor";
public override void _Ready()
{
base._Ready();
AddToGroup(ReactorGroupName);
}
}

View File

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

127
Scripts/Tiles/TurretTile.cs Normal file
View 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 - _barrelTip.GlobalPosition).Normalized();
var targetAngle = direction.Angle() + 90f;
// 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] Turret firing!");
}
}
}

View File

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

BIN
Sounds/Events/Building.wav Normal file

Binary file not shown.

View 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

Binary file not shown.

View 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

Binary file not shown.

View 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

Binary file not shown.

View 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

Binary file not shown.

View 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

View File

@@ -15,8 +15,15 @@ run/main_scene="uid://c22aprj452aha"
config/features=PackedStringArray("4.4", "C#", "GL Compatibility")
config/icon="res://icon.svg"
[autoload]
ResourceManager="res://Scripts/System/ResourceManager.cs"
DiInitializer="*res://Scripts/AutoLoad/DIInitializer.cs"
[display]
window/size/viewport_width=1920
window/size/viewport_height=1080
window/stretch/mode="viewport"
[dotnet]
@@ -75,8 +82,14 @@ switch_tile={
"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]
anti_aliasing/quality/msaa_2d=3
anti_aliasing/quality/msaa_3d=3
anti_aliasing/quality/screen_space_aa=1
anti_aliasing/quality/use_taa=true