✨ Enemy and nest
This commit is contained in:
		
							
								
								
									
										89
									
								
								Scripts/Entities/Enemy.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								Scripts/Entities/Enemy.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| using Godot; | ||||
|  | ||||
| namespace AceFieldNewHorizon.Scripts.Entities; | ||||
|  | ||||
| public partial class Enemy : CharacterBody2D | ||||
| { | ||||
|     [Export] public float MoveSpeed = 150.0f; | ||||
|     [Export] public float DetectionRadius = 300.0f; | ||||
|     [Export] public float AttackRange = 50.0f; | ||||
|     [Export] public int Damage = 10; | ||||
|     [Export] public float AttackCooldown = 1.0f; | ||||
|      | ||||
|     private Player _player; | ||||
|     private float _attackTimer = 0; | ||||
|     private bool _isPlayerInRange = false; | ||||
|     private Area2D _detectionArea; | ||||
|      | ||||
|     public override void _Ready() | ||||
|     { | ||||
|         // Create detection area | ||||
|         _detectionArea = new Area2D(); | ||||
|         var collisionShape = new CollisionShape2D(); | ||||
|         var shape = new CircleShape2D(); | ||||
|         shape.Radius = DetectionRadius; | ||||
|         collisionShape.Shape = shape; | ||||
|         _detectionArea.AddChild(collisionShape); | ||||
|         AddChild(_detectionArea); | ||||
|          | ||||
|         // Connect signals | ||||
|         _detectionArea.BodyEntered += OnBodyEnteredDetection; | ||||
|         _detectionArea.BodyExited += OnBodyExitedDetection; | ||||
|     } | ||||
|      | ||||
|     public override void _Process(double delta) | ||||
|     { | ||||
|         if (_player != null && _isPlayerInRange) | ||||
|         { | ||||
|             // Face the player | ||||
|             LookAt(_player.GlobalPosition); | ||||
|              | ||||
|             // Move towards player if not in attack range | ||||
|             var direction = GlobalPosition.DirectionTo(_player.GlobalPosition); | ||||
|             var distance = GlobalPosition.DistanceTo(_player.GlobalPosition); | ||||
|              | ||||
|             if (distance > AttackRange) | ||||
|             { | ||||
|                 Velocity = direction * MoveSpeed; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Velocity = Vector2.Zero; | ||||
|                 TryAttackPlayer(delta); | ||||
|             } | ||||
|              | ||||
|             MoveAndSlide(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private void TryAttackPlayer(double delta) | ||||
|     { | ||||
|         _attackTimer += (float)delta; | ||||
|          | ||||
|         if (_attackTimer >= AttackCooldown) | ||||
|         { | ||||
|             _attackTimer = 0; | ||||
|             // Here you can implement the attack logic | ||||
|             // For example: _player.TakeDamage(Damage); | ||||
|             GD.Print("Enemy attacks player!"); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private void OnBodyEnteredDetection(Node2D body) | ||||
|     { | ||||
|         if (body is Player player) | ||||
|         { | ||||
|             _player = player; | ||||
|             _isPlayerInRange = true; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private void OnBodyExitedDetection(Node2D body) | ||||
|     { | ||||
|         if (body == _player) | ||||
|         { | ||||
|             _isPlayerInRange = false; | ||||
|             Velocity = Vector2.Zero; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1
									
								
								Scripts/Entities/Enemy.cs.uid
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Scripts/Entities/Enemy.cs.uid
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| uid://cvsmy820b8dwl | ||||
| @@ -25,6 +25,10 @@ public partial class NaturalResourceGenerator : Node2D | ||||
|     [Export] public int MaxIronVeinSize = 3; | ||||
|     [Export] public int Seed; | ||||
|  | ||||
|     [Export] public bool SpawnEnemyNest = true; | ||||
|     [Export] public int MinDistanceFromOrigin = 20; // Minimum distance from world origin (0,0) | ||||
|     [Export] public int MaxDistanceFromOrigin = 50; // Maximum distance from world origin | ||||
|  | ||||
|     private const string LogPrefix = "[NaturalGeneration]"; | ||||
|  | ||||
|     private RandomNumberGenerator _rng; | ||||
| @@ -41,6 +45,32 @@ public partial class NaturalResourceGenerator : Node2D | ||||
|     { | ||||
|         _rng = new RandomNumberGenerator(); | ||||
|         _rng.Seed = (ulong)(Seed != 0 ? Seed : (int)GD.Randi()); | ||||
|      | ||||
|         // Test if building registry is assigned | ||||
|         if (Registry == null) | ||||
|         { | ||||
|             GD.PrintErr($"{LogPrefix} BuildingRegistry is not assigned!"); | ||||
|             return; | ||||
|         } | ||||
|      | ||||
|         // Test if enemy_nest is in the registry | ||||
|         var testBuilding = Registry.GetBuilding("enemy_nest"); | ||||
|         if (testBuilding == null) | ||||
|         { | ||||
|             GD.PrintErr($"{LogPrefix} 'enemy_nest' is not found in BuildingRegistry!"); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             GD.Print($"{LogPrefix} Found enemy_nest in registry!"); | ||||
|         } | ||||
|      | ||||
|         GD.Print($"{LogPrefix} NaturalResourceGenerator ready, SpawnEnemyNest = {SpawnEnemyNest}"); | ||||
|      | ||||
|         if (SpawnEnemyNest) | ||||
|         { | ||||
|             GD.Print($"{LogPrefix} Attempting to spawn enemy nest..."); | ||||
|             SpawnRandomEnemyNest(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void _Process(double delta) | ||||
| @@ -361,12 +391,52 @@ public partial class NaturalResourceGenerator : Node2D | ||||
|         GD.Print($"{LogPrefix} Finished placing vein - placed {placedCount}/{maxSize} {tileType} tiles"); | ||||
|     } | ||||
|  | ||||
|     private void SpawnRandomEnemyNest() | ||||
|     { | ||||
|         // Generate a random position within the specified distance from origin | ||||
|         var angle = _rng.Randf() * Mathf.Pi * 2; | ||||
|         var distance = _rng.RandfRange(MinDistanceFromOrigin, MaxDistanceFromOrigin); | ||||
|         var offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * distance; | ||||
|         var nestPosition = new Vector2I((int)offset.X, (int)offset.Y); | ||||
|          | ||||
|         // Try to find a valid position for the nest | ||||
|         int attempts = 0; | ||||
|         const int maxAttempts = 10; | ||||
|          | ||||
|         while (attempts < maxAttempts) | ||||
|         { | ||||
|             if (PlaceTile("enemy_nest", nestPosition)) | ||||
|             { | ||||
|                 GD.Print($"{LogPrefix} Placed enemy nest at {nestPosition}"); | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             // Try a different position if placement failed | ||||
|             angle = _rng.Randf() * Mathf.Pi * 2; | ||||
|             distance = _rng.RandfRange(MinDistanceFromOrigin, MaxDistanceFromOrigin); | ||||
|             offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * distance; | ||||
|             nestPosition = new Vector2I((int)offset.X, (int)offset.Y); | ||||
|             attempts++; | ||||
|         } | ||||
|          | ||||
|         GD.PrintErr($"{LogPrefix} Failed to place enemy nest after {maxAttempts} attempts"); | ||||
|     } | ||||
|  | ||||
|     private bool PlaceTile(string tileType, Vector2I cell) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             // First, remove any existing tile at this position | ||||
|             Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground); | ||||
|             // For enemy nest, we want to place it on top of existing ground | ||||
|             if (tileType != "enemy_nest") | ||||
|             { | ||||
|                 // Original behavior for other tile types | ||||
|                 Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // For enemy nest, check if there's ground below (skip for now) | ||||
|                 GD.Print($"{LogPrefix} Attempting placing nest at {cell}."); | ||||
|             } | ||||
|  | ||||
|             var building = Registry.GetBuilding(tileType); | ||||
|             if (building == null) | ||||
| @@ -374,6 +444,7 @@ public partial class NaturalResourceGenerator : Node2D | ||||
|                 GD.PrintErr($"{LogPrefix} Building type not found in registry: {tileType}"); | ||||
|                 return false; | ||||
|             } | ||||
|             // GD.Print($"{LogPrefix} Found building in registry: {tileType}"); | ||||
|  | ||||
|             var scene = building.Scene; | ||||
|             if (scene == null) | ||||
| @@ -381,27 +452,35 @@ public partial class NaturalResourceGenerator : Node2D | ||||
|                 GD.PrintErr($"{LogPrefix} Scene is null for building type: {tileType}"); | ||||
|                 return false; | ||||
|             } | ||||
|             // GD.Print($"{LogPrefix} Scene loaded for {tileType}"); | ||||
|  | ||||
|             if (scene.Instantiate() is not BaseTile instance) | ||||
|             { | ||||
|                 GD.PrintErr($"{LogPrefix} Failed to instantiate scene for: {tileType}"); | ||||
|                 return false; | ||||
|             } | ||||
|             // GD.Print($"{LogPrefix} Successfully instantiated {tileType}"); | ||||
|  | ||||
|             // Use the same positioning logic as PlacementManager | ||||
|             var rotatedSize = building.GetRotatedSize(0f); // 0f for no rotation | ||||
|             var offset = GridUtils.GetCenterOffset(rotatedSize, 0f); // 0f for no rotation | ||||
|             instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset; | ||||
|             var rotatedSize = building.GetRotatedSize(0f); | ||||
|             var offset = GridUtils.GetCenterOffset(rotatedSize, 0f); | ||||
|              | ||||
|             instance.ZIndex = (int)building.Layer; | ||||
|             instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset; | ||||
|             instance.Grid = Grid; | ||||
|             AddChild(instance); | ||||
|             Grid.OccupyArea(cell, instance, building.Size, 0f, building.Layer); | ||||
|             // GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell}"); | ||||
|              | ||||
|             // For enemy nest, use Building layer | ||||
|             var layer = tileType == "enemy_nest" ? GridLayer.Building : building.Layer; | ||||
|             Grid.OccupyArea(cell, instance, building.Size, 0f, layer); | ||||
|              | ||||
|             // GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell} on layer {layer}"); | ||||
|             return true; | ||||
|         } | ||||
|         catch (Exception e) | ||||
|         { | ||||
|             GD.PrintErr($"{LogPrefix} Error placing {tileType} at {cell}: {e.Message}"); | ||||
|             GD.Print($"{LogPrefix} Stack trace: {e.StackTrace}"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										35
									
								
								Scripts/Tiles/EnemyNestTile.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Scripts/Tiles/EnemyNestTile.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| using Godot; | ||||
|  | ||||
| namespace AceFieldNewHorizon.Scripts.Tiles; | ||||
|  | ||||
| public partial class EnemyNestTile : BaseTile | ||||
| { | ||||
|     [Export] public PackedScene EnemyScene; | ||||
|     private Timer _spawnTimer; | ||||
|  | ||||
|     public override void _Ready() | ||||
|     { | ||||
|         base._Ready(); | ||||
|          | ||||
|         // Create and configure the timer | ||||
|         _spawnTimer = new Timer(); | ||||
|         AddChild(_spawnTimer); | ||||
|         _spawnTimer.Timeout += OnSpawnTimerTimeout; | ||||
|         _spawnTimer.Start(1.0f); // Start with 1 second interval | ||||
|     } | ||||
|  | ||||
|     private void OnSpawnTimerTimeout() | ||||
|     { | ||||
|         if (EnemyScene != null) | ||||
|         { | ||||
|             var enemy = EnemyScene.Instantiate(); | ||||
|             GetParent().AddChild(enemy); | ||||
|              | ||||
|             // Position the enemy at the nest's position | ||||
|             if (enemy is Node2D enemy2D) | ||||
|             { | ||||
|                 enemy2D.GlobalPosition = GlobalPosition; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1
									
								
								Scripts/Tiles/EnemyNestTile.cs.uid
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Scripts/Tiles/EnemyNestTile.cs.uid
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| uid://26hl5mk4mqur | ||||
		Reference in New Issue
	
	Block a user