using Godot; using RogueTank.Combat; using RogueTank.Util; namespace RogueTank.Actors; public partial class PlayerTank : CharacterBody2D, IDamageable { [Signal] public delegate void StatsChangedEventHandler(); [Signal] public delegate void DiedEventHandler(); [Signal] public delegate void ShotFiredEventHandler(); public Vector2 DebugLastMoveInput { get; private set; } = Vector2.Zero; // Stats public float MoveSpeed { get; set; } = 165f; public float FireRate { get; set; } = 4.0f; // shots/sec public float Damage { get; set; } = 12f; public float Armor { get; set; } = 0f; // flat reduction public float BulletSpeed { get; set; } = 520f; public int BulletPierce { get; set; } = 0; public float MagnetRadius { get; set; } = 42f; public float MaxHp { get; set; } = 100f; public float Hp { get; private set; } = 100f; public int Level { get; private set; } = 1; public int Xp { get; private set; } public int XpToNext { get; private set; } = 30; public int Coins { get; private set; } // References public Node2D? ProjectilesRoot { get; set; } public PackedScene? BulletScene { get; set; } public Node2D? PickupsRoot { get; set; } private Sprite2D _sprite = null!; private Sprite2D _turret = null!; private Area2D _magnet = null!; private CollisionShape2D _magnetShape = null!; private float _shootCd; public override void _Ready() { Hp = MaxHp; CollisionLayer = 1u << 3; // player CollisionMask = (1u << 0) | (1u << 1); // walls + enemies _sprite = new Sprite2D { Texture = PixelArt.MakeTankTexture(16, new Color(0.15f, 0.75f, 0.35f), new Color(0.1f, 0.9f, 0.35f)), Centered = true, TextureFilter = CanvasItem.TextureFilterEnum.Nearest }; AddChild(_sprite); _turret = new Sprite2D { Texture = PixelArt.MakeTankTexture(16, new Color(0.0f, 0.0f, 0.0f, 0.0f), new Color(0.95f, 0.95f, 0.95f)), Centered = true, TextureFilter = CanvasItem.TextureFilterEnum.Nearest, Modulate = new Color(0.85f, 0.95f, 0.9f) }; AddChild(_turret); var col = new CollisionShape2D { Shape = new CircleShape2D { Radius = 7f } }; AddChild(col); _magnet = new Area2D(); _magnet.CollisionLayer = 1u << 5; // magnet _magnet.CollisionMask = 0; _magnetShape = new CollisionShape2D { Shape = new CircleShape2D { Radius = MagnetRadius } }; _magnet.AddChild(_magnetShape); AddChild(_magnet); } public override void _PhysicsProcess(double delta) { var d = (float)delta; var input = ReadMoveInput(); DebugLastMoveInput = input; if (input.Length() > 1f) input = input.Normalized(); Velocity = input * MoveSpeed; MoveAndSlide(); AimTurret(); HandleShooting(d); UpdateMagnet(); } private static Vector2 ReadMoveInput() { // Prefer InputMap actions; if they're missing/broken, fallback to raw keys. if (InputMap.HasAction("move_left") && InputMap.HasAction("move_right") && InputMap.HasAction("move_up") && InputMap.HasAction("move_down")) { return Input.GetVector("move_left", "move_right", "move_up", "move_down"); } float x = 0f; float y = 0f; if (Input.IsKeyPressed(Key.A) || Input.IsKeyPressed(Key.Left)) x -= 1f; if (Input.IsKeyPressed(Key.D) || Input.IsKeyPressed(Key.Right)) x += 1f; if (Input.IsKeyPressed(Key.W) || Input.IsKeyPressed(Key.Up)) y -= 1f; if (Input.IsKeyPressed(Key.S) || Input.IsKeyPressed(Key.Down)) y += 1f; return new Vector2(x, y); } private void AimTurret() { var viewport = GetViewport(); if (viewport == null) return; var worldMouse = GetGlobalMousePosition(); _turret.Rotation = (worldMouse - GlobalPosition).Angle(); } private void HandleShooting(float delta) { _shootCd -= delta; var wantShoot = (InputMap.HasAction("shoot") && Input.IsActionPressed("shoot")) || Input.IsMouseButtonPressed(MouseButton.Left); if (!wantShoot || _shootCd > 0f) return; if (BulletScene == null || ProjectilesRoot == null) return; _shootCd = 1f / Mathf.Max(0.1f, FireRate); var bullet = BulletScene.Instantiate(); var dir = new Vector2(Mathf.Cos(_turret.Rotation), Mathf.Sin(_turret.Rotation)); bullet.GlobalPosition = GlobalPosition + dir * 10f; bullet.Speed = BulletSpeed; bullet.Damage = Damage; bullet.Pierce = BulletPierce; bullet.Init(dir); ProjectilesRoot.AddChild(bullet); EmitSignal(SignalName.ShotFired); } private void UpdateMagnet() { if (_magnetShape.Shape is CircleShape2D cs) cs.Radius = MagnetRadius; } public void AddCoins(int amount) { Coins += amount; EmitSignal(SignalName.StatsChanged); } public bool AddXp(int amount) { Xp += amount; var leveled = false; while (Xp >= XpToNext) { Xp -= XpToNext; Level++; XpToNext = Mathf.RoundToInt(XpToNext * 1.22f + 10); leveled = true; } EmitSignal(SignalName.StatsChanged); return leveled; } public void Heal(float amount) { Hp = Mathf.Min(MaxHp, Hp + amount); EmitSignal(SignalName.StatsChanged); } public void TakeDamage(float amount) { var final = Mathf.Max(1f, amount - Armor); Hp -= final; if (Hp <= 0f) { Hp = 0f; EmitSignal(SignalName.StatsChanged); EmitSignal(SignalName.Died); QueueFree(); return; } EmitSignal(SignalName.StatsChanged); } public Area2D GetMagnetArea() => _magnet; }