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 = 0; 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"); // 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, 12), // 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(); 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; } }