✨ Reactor, and enemies attacking the tiles
This commit is contained in:
@@ -33,6 +33,15 @@
|
|||||||
"layer": 1,
|
"layer": 1,
|
||||||
"size": [1, 1]
|
"size": [1, 1]
|
||||||
},
|
},
|
||||||
|
"reactor": {
|
||||||
|
"scene": "res://Scenes/Tiles/ReactorTile.tscn",
|
||||||
|
"cost": {},
|
||||||
|
"durability": 200,
|
||||||
|
"buildTime": 5.0,
|
||||||
|
"allowedRotations": [0],
|
||||||
|
"layer": 1,
|
||||||
|
"size": [3, 3]
|
||||||
|
},
|
||||||
"enemy_nest": {
|
"enemy_nest": {
|
||||||
"scene": "res://Scenes/Tiles/EnemyNest.tscn",
|
"scene": "res://Scenes/Tiles/EnemyNest.tscn",
|
||||||
"cost": {},
|
"cost": {},
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=4 format=3 uid="uid://b3ffcucytwmk"]
|
[gd_scene load_steps=5 format=3 uid="uid://b3ffcucytwmk"]
|
||||||
|
|
||||||
[ext_resource type="Texture2D" uid="uid://dlhpiyxtmp707" path="res://Scenes/Entities/Enemy.jpg" id="1_8q37v"]
|
[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"]
|
[ext_resource type="Script" uid="uid://cvsmy820b8dwl" path="res://Scripts/Entities/Enemy.cs" id="1_jajit"]
|
||||||
@@ -6,6 +6,9 @@
|
|||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_jajit"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_jajit"]
|
||||||
size = Vector2(60, 74)
|
size = Vector2(60, 74)
|
||||||
|
|
||||||
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_wmvnc"]
|
||||||
|
size = Vector2(70, 84)
|
||||||
|
|
||||||
[node name="Enemy" type="CharacterBody2D"]
|
[node name="Enemy" type="CharacterBody2D"]
|
||||||
collision_layer = 2
|
collision_layer = 2
|
||||||
collision_mask = 3
|
collision_mask = 3
|
||||||
@@ -17,3 +20,8 @@ shape = SubResource("RectangleShape2D_jajit")
|
|||||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||||
scale = Vector2(0.1, 0.1)
|
scale = Vector2(0.1, 0.1)
|
||||||
texture = ExtResource("1_8q37v")
|
texture = ExtResource("1_8q37v")
|
||||||
|
|
||||||
|
[node name="AttackArea" type="Area2D" parent="."]
|
||||||
|
|
||||||
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="AttackArea"]
|
||||||
|
shape = SubResource("RectangleShape2D_wmvnc")
|
||||||
|
@@ -14,9 +14,8 @@ script = ExtResource("2_oss8w")
|
|||||||
[node name="PlacementSystem" type="Node2D" parent="."]
|
[node name="PlacementSystem" type="Node2D" parent="."]
|
||||||
script = ExtResource("2_sxhdm")
|
script = ExtResource("2_sxhdm")
|
||||||
|
|
||||||
[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)
|
scale = Vector2(0.35, 0.35)
|
||||||
Inventory = NodePath("")
|
|
||||||
|
|
||||||
[node name="HUD" parent="." instance=ExtResource("8_hud_scene")]
|
[node name="HUD" parent="." instance=ExtResource("8_hud_scene")]
|
||||||
|
|
||||||
|
BIN
Scenes/Tiles/ReactorTile.png
Normal file
BIN
Scenes/Tiles/ReactorTile.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 544 KiB |
34
Scenes/Tiles/ReactorTile.png.import
Normal file
34
Scenes/Tiles/ReactorTile.png.import
Normal 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
|
26
Scenes/Tiles/ReactorTile.tscn
Normal file
26
Scenes/Tiles/ReactorTile.tscn
Normal 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)
|
@@ -1,4 +1,7 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using AceFieldNewHorizon.Scripts.Tiles;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
using AceFieldNewHorizon.Scripts.System;
|
||||||
|
|
||||||
namespace AceFieldNewHorizon.Scripts.Entities;
|
namespace AceFieldNewHorizon.Scripts.Entities;
|
||||||
|
|
||||||
@@ -8,18 +11,23 @@ public partial class Enemy : CharacterBody2D
|
|||||||
|
|
||||||
[Export] public float MoveSpeed = 150.0f;
|
[Export] public float MoveSpeed = 150.0f;
|
||||||
[Export] public float DetectionRadius = 300.0f;
|
[Export] public float DetectionRadius = 300.0f;
|
||||||
[Export] public float AttackRange = 50.0f;
|
[Export] public float AttackRadius = 80.0f;
|
||||||
[Export] public int Damage = 10;
|
[Export] public int Damage = 10;
|
||||||
[Export] public float AttackCooldown = 1.0f;
|
[Export] public float AttackCooldown = 1.0f;
|
||||||
[Export] public int MaxHealth = 100;
|
[Export] public int MaxHealth = 100;
|
||||||
[Export] public bool ShowDamageNumbers = true;
|
[Export] public bool ShowDamageNumbers = true;
|
||||||
|
|
||||||
private Player _player;
|
private BaseTile _targetTile;
|
||||||
|
private ReactorTile _reactor;
|
||||||
private float _attackTimer = 0;
|
private float _attackTimer = 0;
|
||||||
private bool _isPlayerInRange = false;
|
|
||||||
private Area2D _detectionArea;
|
private Area2D _detectionArea;
|
||||||
|
private Area2D _attackArea;
|
||||||
private int _currentHealth;
|
private int _currentHealth;
|
||||||
private ProgressBar _healthBar;
|
private ProgressBar _healthBar;
|
||||||
|
private GridManager _gridManager;
|
||||||
|
|
||||||
|
// Track collisions with potential targets
|
||||||
|
private readonly HashSet<BaseTile> _collidingTiles = [];
|
||||||
|
|
||||||
public int CurrentHealth
|
public int CurrentHealth
|
||||||
{
|
{
|
||||||
@@ -41,6 +49,10 @@ public partial class Enemy : CharacterBody2D
|
|||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_currentHealth = MaxHealth;
|
_currentHealth = MaxHealth;
|
||||||
|
_gridManager = DependencyInjection.Container.GetInstance<GridManager>();
|
||||||
|
|
||||||
|
// Find the reactor in the scene
|
||||||
|
_reactor = GetTree().GetFirstNodeInGroup(ReactorTile.ReactorGroupName) as ReactorTile;
|
||||||
|
|
||||||
// Create health bar
|
// Create health bar
|
||||||
_healthBar = new ProgressBar
|
_healthBar = new ProgressBar
|
||||||
@@ -55,9 +67,9 @@ public partial class Enemy : CharacterBody2D
|
|||||||
var healthBarContainer = new Control();
|
var healthBarContainer = new Control();
|
||||||
healthBarContainer.AddChild(_healthBar);
|
healthBarContainer.AddChild(_healthBar);
|
||||||
AddChild(healthBarContainer);
|
AddChild(healthBarContainer);
|
||||||
healthBarContainer.Position = new Vector2(-20, -20); // Adjust position as needed
|
healthBarContainer.Position = new Vector2(-20, -20);
|
||||||
|
|
||||||
// Create detection area
|
// Create detection area for finding targets
|
||||||
_detectionArea = new Area2D();
|
_detectionArea = new Area2D();
|
||||||
var collisionShape = new CollisionShape2D();
|
var collisionShape = new CollisionShape2D();
|
||||||
var shape = new CircleShape2D();
|
var shape = new CircleShape2D();
|
||||||
@@ -66,9 +78,16 @@ public partial class Enemy : CharacterBody2D
|
|||||||
_detectionArea.AddChild(collisionShape);
|
_detectionArea.AddChild(collisionShape);
|
||||||
AddChild(_detectionArea);
|
AddChild(_detectionArea);
|
||||||
|
|
||||||
|
_attackArea = GetNodeOrNull<Area2D>("AttackArea");
|
||||||
|
|
||||||
// Connect signals
|
// Connect signals
|
||||||
_detectionArea.BodyEntered += OnBodyEnteredDetection;
|
_detectionArea.BodyEntered += OnBodyEnteredDetection;
|
||||||
_detectionArea.BodyExited += OnBodyExitedDetection;
|
_detectionArea.BodyExited += OnBodyExitedDetection;
|
||||||
|
if (_attackArea != null)
|
||||||
|
{
|
||||||
|
_attackArea.BodyEntered += OnBodyEntered;
|
||||||
|
_attackArea.BodyExited += OnBodyExited;
|
||||||
|
}
|
||||||
|
|
||||||
AddToGroup(EnemyGroupName);
|
AddToGroup(EnemyGroupName);
|
||||||
}
|
}
|
||||||
@@ -77,28 +96,119 @@ public partial class Enemy : CharacterBody2D
|
|||||||
{
|
{
|
||||||
if (IsDead) return;
|
if (IsDead) return;
|
||||||
|
|
||||||
if (_player != null && _isPlayerInRange)
|
// Find the best target if we don't have one
|
||||||
|
if (_targetTile == null || _targetTile.IsDestroyed || !_targetTile.IsInsideTree())
|
||||||
{
|
{
|
||||||
// Face the player
|
UpdateTarget();
|
||||||
LookAt(_player.GlobalPosition);
|
}
|
||||||
|
|
||||||
// Move towards player if not in attack range
|
// Move towards current target
|
||||||
var direction = GlobalPosition.DirectionTo(_player.GlobalPosition);
|
if (_targetTile != null)
|
||||||
var distance = GlobalPosition.DistanceTo(_player.GlobalPosition);
|
|
||||||
|
|
||||||
if (distance > AttackRange)
|
|
||||||
{
|
{
|
||||||
|
var direction = GlobalPosition.DirectionTo(_targetTile.GlobalPosition);
|
||||||
Velocity = direction * MoveSpeed;
|
Velocity = direction * MoveSpeed;
|
||||||
|
LookAt(_targetTile.GlobalPosition);
|
||||||
|
MoveAndSlide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attack any colliding tiles
|
||||||
|
if (_attackTimer > 0)
|
||||||
|
{
|
||||||
|
_attackTimer -= (float)delta;
|
||||||
|
}
|
||||||
|
else if (_collidingTiles.Count > 0)
|
||||||
|
{
|
||||||
|
// Attack the first colliding tile
|
||||||
|
foreach (var tile in _collidingTiles)
|
||||||
|
{
|
||||||
|
if (tile != null && !tile.IsDestroyed && tile.IsInsideTree())
|
||||||
|
{
|
||||||
|
TryAttackTile(tile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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
|
else
|
||||||
{
|
{
|
||||||
Velocity = Vector2.Zero;
|
_targetTile = null;
|
||||||
TryAttackPlayer(delta);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveAndSlide();
|
private 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBodyExited(Node2D body)
|
||||||
|
{
|
||||||
|
if (body.GetParent() is BaseTile tile)
|
||||||
|
{
|
||||||
|
_collidingTiles.Remove(tile);
|
||||||
|
if (_targetTile == tile)
|
||||||
|
{
|
||||||
|
_targetTile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBodyEnteredDetection(Node2D body)
|
||||||
|
{
|
||||||
|
// Keep this for initial target detection if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBodyExitedDetection(Node2D body)
|
||||||
|
{
|
||||||
|
// Keep this for target cleanup if needed
|
||||||
|
}
|
||||||
|
|
||||||
public void TakeDamage(int damage, Vector2? hitPosition = null)
|
public void TakeDamage(int damage, Vector2? hitPosition = null)
|
||||||
{
|
{
|
||||||
@@ -157,37 +267,4 @@ public partial class Enemy : CharacterBody2D
|
|||||||
timer.Timeout += () => QueueFree();
|
timer.Timeout += () => QueueFree();
|
||||||
timer.Start(0.5f);
|
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,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using AceFieldNewHorizon.Scripts.System;
|
using AceFieldNewHorizon.Scripts.System;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
|
@@ -8,11 +8,20 @@ public partial class Hud : CanvasLayer
|
|||||||
private ResourceManager _resourceManager;
|
private ResourceManager _resourceManager;
|
||||||
private VBoxContainer _resourceDisplay;
|
private VBoxContainer _resourceDisplay;
|
||||||
private readonly Dictionary<string, Label> _resourceLabels = new();
|
private readonly Dictionary<string, Label> _resourceLabels = new();
|
||||||
|
private VBoxContainer _pickupLogContainer;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_resourceDisplay = GetNode<VBoxContainer>("ResourceDisplay");
|
_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>();
|
_resourceManager = DependencyInjection.Container.GetInstance<ResourceManager>();
|
||||||
if (_resourceManager == null)
|
if (_resourceManager == null)
|
||||||
{
|
{
|
||||||
@@ -21,6 +30,7 @@ public partial class Hud : CanvasLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
_resourceManager.OnResourceChanged += UpdateResourceDisplay;
|
_resourceManager.OnResourceChanged += UpdateResourceDisplay;
|
||||||
|
_resourceManager.OnResourcePickedUp += OnResourcePickedUp;
|
||||||
|
|
||||||
// Initialize display with current resources
|
// Initialize display with current resources
|
||||||
foreach (var entry in _resourceManager.GetAllResources())
|
foreach (var entry in _resourceManager.GetAllResources())
|
||||||
@@ -54,11 +64,39 @@ public partial class Hud : CanvasLayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
public override void _ExitTree()
|
||||||
{
|
{
|
||||||
if (_resourceManager != null)
|
if (_resourceManager != null)
|
||||||
{
|
{
|
||||||
_resourceManager.OnResourceChanged -= UpdateResourceDisplay;
|
_resourceManager.OnResourceChanged -= UpdateResourceDisplay;
|
||||||
|
_resourceManager.OnResourcePickedUp -= OnResourcePickedUp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -68,6 +68,9 @@ public partial class NaturalResourceGenerator : Node2D
|
|||||||
|
|
||||||
GD.Print($"{LogPrefix} NaturalResourceGenerator ready, SpawnEnemyNest = {SpawnEnemyNest}");
|
GD.Print($"{LogPrefix} NaturalResourceGenerator ready, SpawnEnemyNest = {SpawnEnemyNest}");
|
||||||
|
|
||||||
|
GD.Print($"{LogPrefix} Spawning the core reactor...");
|
||||||
|
SpawnCoreReactor();
|
||||||
|
|
||||||
if (SpawnEnemyNest)
|
if (SpawnEnemyNest)
|
||||||
{
|
{
|
||||||
GD.Print($"{LogPrefix} Attempting to spawn enemy nest...");
|
GD.Print($"{LogPrefix} Attempting to spawn enemy nest...");
|
||||||
@@ -393,6 +396,12 @@ public partial class NaturalResourceGenerator : Node2D
|
|||||||
GD.Print($"{LogPrefix} Finished placing vein - placed {placedCount}/{maxSize} {tileType} tiles");
|
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()
|
private void SpawnRandomEnemyNest()
|
||||||
{
|
{
|
||||||
// Generate a random position within the specified distance from origin
|
// Generate a random position within the specified distance from origin
|
||||||
@@ -402,7 +411,7 @@ public partial class NaturalResourceGenerator : Node2D
|
|||||||
var nestPosition = new Vector2I((int)offset.X, (int)offset.Y);
|
var nestPosition = new Vector2I((int)offset.X, (int)offset.Y);
|
||||||
|
|
||||||
// Try to find a valid position for the nest
|
// Try to find a valid position for the nest
|
||||||
int attempts = 0;
|
var attempts = 0;
|
||||||
const int maxAttempts = 10;
|
const int maxAttempts = 10;
|
||||||
|
|
||||||
while (attempts < maxAttempts)
|
while (attempts < maxAttempts)
|
||||||
@@ -428,25 +437,18 @@ public partial class NaturalResourceGenerator : Node2D
|
|||||||
{
|
{
|
||||||
try
|
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);
|
var building = Registry.GetBuilding(tileType);
|
||||||
if (building == null)
|
if (building == null)
|
||||||
{
|
{
|
||||||
GD.PrintErr($"{LogPrefix} Building type not found in registry: {tileType}");
|
GD.PrintErr($"{LogPrefix} Building type not found in registry: {tileType}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// GD.Print($"{LogPrefix} Found building in registry: {tileType}");
|
|
||||||
|
// 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;
|
var scene = building.Scene;
|
||||||
if (scene == null)
|
if (scene == null)
|
||||||
@@ -454,29 +456,24 @@ public partial class NaturalResourceGenerator : Node2D
|
|||||||
GD.PrintErr($"{LogPrefix} Scene is null for building type: {tileType}");
|
GD.PrintErr($"{LogPrefix} Scene is null for building type: {tileType}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// GD.Print($"{LogPrefix} Scene loaded for {tileType}");
|
|
||||||
|
|
||||||
if (scene.Instantiate() is not BaseTile instance)
|
if (scene.Instantiate() is not BaseTile instance)
|
||||||
{
|
{
|
||||||
GD.PrintErr($"{LogPrefix} Failed to instantiate scene for: {tileType}");
|
GD.PrintErr($"{LogPrefix} Failed to instantiate scene for: {tileType}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// GD.Print($"{LogPrefix} Successfully instantiated {tileType}");
|
|
||||||
|
|
||||||
// Use the same positioning logic as PlacementManager
|
// Calculate position with proper offset based on building size
|
||||||
var rotatedSize = building.GetRotatedSize(0f);
|
var rotatedSize = building.GetRotatedSize(0f);
|
||||||
var offset = GridUtils.GetCenterOffset(rotatedSize, 0f);
|
var offset = GridUtils.GetCenterOffset(rotatedSize, 0f);
|
||||||
|
|
||||||
instance.ZIndex = (int)building.Layer;
|
instance.ZIndex = (int)building.Layer;
|
||||||
instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset;
|
instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset;
|
||||||
instance.Grid = Grid;
|
|
||||||
AddChild(instance);
|
AddChild(instance);
|
||||||
|
|
||||||
// For enemy nest, use Building layer
|
// Occupy the appropriate area based on building size
|
||||||
var layer = tileType == "enemy_nest" ? GridLayer.Building : building.Layer;
|
Grid.OccupyArea(cell, instance, building.Size, 0f, building.Layer);
|
||||||
Grid.OccupyArea(cell, instance, building.Size, 0f, layer);
|
|
||||||
|
|
||||||
// GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell} on layer {layer}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@@ -203,7 +203,6 @@ public partial class PlacementManager : Node2D
|
|||||||
var scene = building.Scene;
|
var scene = building.Scene;
|
||||||
|
|
||||||
_ghostBuilding = (BaseTile)scene.Instantiate();
|
_ghostBuilding = (BaseTile)scene.Instantiate();
|
||||||
_ghostBuilding.Grid = Grid;
|
|
||||||
_ghostBuilding.SetGhostMode(true);
|
_ghostBuilding.SetGhostMode(true);
|
||||||
_ghostBuilding.RotationDegrees = _currentRotation;
|
_ghostBuilding.RotationDegrees = _currentRotation;
|
||||||
_ghostBuilding.ZAsRelative = false;
|
_ghostBuilding.ZAsRelative = false;
|
||||||
@@ -251,7 +250,6 @@ public partial class PlacementManager : Node2D
|
|||||||
// Create the building instance
|
// Create the building instance
|
||||||
var scene = building.Scene;
|
var scene = building.Scene;
|
||||||
var buildingInstance = (BaseTile)scene.Instantiate();
|
var buildingInstance = (BaseTile)scene.Instantiate();
|
||||||
buildingInstance.Grid = Grid;
|
|
||||||
buildingInstance.RotationDegrees = _currentRotation;
|
buildingInstance.RotationDegrees = _currentRotation;
|
||||||
buildingInstance.ZIndex = (int)building.Layer;
|
buildingInstance.ZIndex = (int)building.Layer;
|
||||||
buildingInstance.Position = _ghostBuilding.Position;
|
buildingInstance.Position = _ghostBuilding.Position;
|
||||||
|
@@ -14,6 +14,10 @@ public partial class ResourceManager : Node
|
|||||||
[Signal]
|
[Signal]
|
||||||
public delegate void OnResourceChangedEventHandler(string resourceId, int newAmount);
|
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()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
base._Ready();
|
base._Ready();
|
||||||
@@ -43,6 +47,7 @@ public partial class ResourceManager : Node
|
|||||||
}
|
}
|
||||||
|
|
||||||
EmitSignal(nameof(OnResourceChanged), resourceId, _resources[resourceId]);
|
EmitSignal(nameof(OnResourceChanged), resourceId, _resources[resourceId]);
|
||||||
|
EmitSignal(nameof(OnResourcePickedUp), resourceId, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove resources of a specific type
|
// Remove resources of a specific type
|
||||||
|
@@ -9,23 +9,39 @@ namespace AceFieldNewHorizon.Scripts.Tiles;
|
|||||||
public partial class BaseTile : Node2D
|
public partial class BaseTile : Node2D
|
||||||
{
|
{
|
||||||
[Export] public string TileId { get; set; }
|
[Export] public string TileId { get; set; }
|
||||||
[Export] public GridManager Grid { 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 CollisionShape2D _collisionShape;
|
||||||
private Sprite2D _sprite;
|
private Sprite2D _sprite;
|
||||||
private ColorRect _progressOverlay;
|
private ColorRect _progressOverlay;
|
||||||
private Action _onConstructionComplete;
|
private Action _onConstructionComplete;
|
||||||
|
private Tween _damageTween;
|
||||||
|
|
||||||
public bool IsConstructing;
|
public bool IsConstructing;
|
||||||
public bool IsConstructed;
|
public bool IsConstructed;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
|
Grid = DependencyInjection.Container.GetInstance<GridManager>();
|
||||||
|
Registry = DependencyInjection.Container.GetInstance<BuildingRegistry>();
|
||||||
|
|
||||||
_collisionShape = GetNodeOrNull<CollisionShape2D>("CollisionShape2D");
|
_collisionShape = GetNodeOrNull<CollisionShape2D>("CollisionShape2D");
|
||||||
_sprite = GetNodeOrNull<Sprite2D>("Sprite2D");
|
_sprite = GetNodeOrNull<Sprite2D>("Sprite2D");
|
||||||
_progressOverlay = GetNodeOrNull<ColorRect>("ProgressOverlay");
|
_progressOverlay = GetNodeOrNull<ColorRect>("ProgressOverlay");
|
||||||
if (_progressOverlay != null)
|
if (_progressOverlay != null)
|
||||||
_progressOverlay.Visible = false;
|
_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 virtual void SetGhostMode(bool canPlace)
|
public virtual void SetGhostMode(bool canPlace)
|
||||||
@@ -50,6 +66,78 @@ public partial class BaseTile : Node2D
|
|||||||
_sprite.Modulate = Colors.White;
|
_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
|
||||||
|
}
|
||||||
|
|
||||||
// Building progress visualization
|
// Building progress visualization
|
||||||
public void StartConstruction(float buildTime, Action onComplete = null)
|
public void StartConstruction(float buildTime, Action onComplete = null)
|
||||||
{
|
{
|
||||||
|
@@ -18,6 +18,9 @@ public partial class EnemyNestTile : BaseTile
|
|||||||
{
|
{
|
||||||
base._Ready();
|
base._Ready();
|
||||||
|
|
||||||
|
// Add to Hostile group to prevent enemies from attacking their own nest
|
||||||
|
AddToGroup("Hostile");
|
||||||
|
|
||||||
// Create and configure the timer
|
// Create and configure the timer
|
||||||
_spawnTimer = new Timer
|
_spawnTimer = new Timer
|
||||||
{
|
{
|
||||||
|
13
Scripts/Tiles/ReactorTile.cs
Normal file
13
Scripts/Tiles/ReactorTile.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
1
Scripts/Tiles/ReactorTile.cs.uid
Normal file
1
Scripts/Tiles/ReactorTile.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c4k3ottt7j3b1
|
@@ -121,7 +121,7 @@ public partial class TurretTile : BaseTile
|
|||||||
// Reset attack cooldown
|
// Reset attack cooldown
|
||||||
_attackTimer = AttackCooldown;
|
_attackTimer = AttackCooldown;
|
||||||
|
|
||||||
GD.Print("Turret attacking enemy!");
|
GD.Print("[Turret] Turret firing!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user