You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
5.9 KiB
C#

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<Bullet>();
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;
}