2025-05-13 19:22:01 +08:00

317 lines
6.9 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Godot;
using Godot.Bridge;
using Godot.Collections;
using Godot.NativeInterop;
[ScriptPath("res://Scripts/Entities/Enemy.cs")]
public partial class Enemy : Entity
{
public enum MovementType
{
None,
Wander,
LoopAround,
BackAndForth,
Circle
}
public enum Respawn
{
No,
NoMoney,
HalfMoney
}
public enum State
{
Unaware,
Noticed,
Chase
}
[Export(PropertyHint.None, "")]
private MovementType movementType = MovementType.Wander;
[Export(PropertyHint.None, "")]
private Vector2 circleMulti = Vector2.One;
[Export(PropertyHint.None, "")]
private float radius = 120f;
[Export(PropertyHint.None, "")]
private float moveTime = 100f;
[Export(PropertyHint.None, "")]
private Array<IDs> additionalEnemies;
[Export(PropertyHint.None, "")]
private StringName battleMes = "Test";
[Export(PropertyHint.None, "")]
public SaveFile.Flags flag;
[Export(PropertyHint.None, "")]
public SaveFile.Flags spawn = SaveFile.Flags.None;
[Export(PropertyHint.None, "")]
public SaveFile.Flags despawn = SaveFile.Flags.None;
[Export(PropertyHint.None, "")]
private Node2D[] points;
[Export(PropertyHint.None, "")]
private int current;
[Export(PropertyHint.None, "")]
public Respawn respawnType;
[Export(PropertyHint.None, "")]
private bool smooth = true;
[Export(PropertyHint.None, "")]
private bool immuneToWater;
private static readonly AudioStream noticedSound = GD.Load<AudioStream>("res://Audio/Sounds/snd_b.wav");
private Vector2 last;
private Vector2 next;
private State state;
private float cooldown = 180f;
private float noticeCD;
private float circleState;
private bool back;
public static readonly StringName waterTag = "WaterBullet";
public override void _EnterTree()
{
base._EnterTree();
if ((SaveFile.current.flags.Contains(flag) && respawnType == Respawn.No) || !SaveFile.CanDo(new Array<SaveFile.Flags> { spawn }, new Array<SaveFile.Flags> { despawn }))
{
QueueFree();
return;
}
if (movementType == MovementType.LoopAround || movementType == MovementType.BackAndForth)
{
cooldown = moveTime;
}
trigger.BodyEntered += OnTouch;
trigger.AreaEntered += AreaEnter;
trigger.CollisionMask = 129u;
Node2D[] array = points;
if (array != null && array.Length != 0)
{
last = points[current].GlobalPosition;
int num = (int)Main.Repeat(current + 1, points.Length);
next = points[num].GlobalPosition;
current = num;
}
}
public override void _Process(double delta)
{
if (TextSystem.instance.Visible)
{
return;
}
base._Process(delta);
if (noticeCD > 0f)
{
noticeCD -= Main.deltaTime;
}
switch (state)
{
case State.Unaware:
if (noticeCD > 0f)
{
break;
}
if (radius > 0f && base.GlobalPosition.DistanceTo(Player.instance.GlobalPosition) < radius)
{
cooldown = 30f;
StopMoving(Animations.Idle);
Emoticon(Emoticons.Exclaim);
Audio.PlaySound(noticedSound);
state = State.Noticed;
break;
}
switch (movementType)
{
case MovementType.Circle:
{
circleState = Main.Repeat(circleState + Main.deltaTime, moveTime);
float s = Mathf.DegToRad(Mathf.Lerp(0f, 360f, circleState / moveTime));
Vector2 vector = startPos + new Vector2(Mathf.Cos(s) * circleMulti.X, Mathf.Sin(s) * circleMulti.Y);
LookAt(vector);
base.GlobalPosition = base.GlobalPosition.Lerp(vector, Main.deltaTime * 0.065f);
break;
}
case MovementType.None:
StopMoving();
break;
default:
if (moving == null)
{
if (cooldown >= 0f)
{
currentAnim = Animations.Idle;
cooldown -= Main.deltaTime;
}
else
{
DoAutoMove(startPos + new Vector2(Main.RandomRange(0f - radius, radius), Main.RandomRange(0f - radius, radius)) / 3f, 0.5f);
cooldown = Main.RandomRange(120, 250);
}
}
break;
case MovementType.LoopAround:
case MovementType.BackAndForth:
base.Velocity = Vector2.Zero;
if (cooldown >= 0f)
{
currentAnim = Animations.Walk;
float weight = 1f - cooldown / moveTime;
if (smooth)
{
weight = Mathf.SmoothStep(0f, 1f, weight);
}
if (radius < 10f)
{
base.GlobalPosition = last.Lerp(next, weight);
}
else
{
base.GlobalPosition = base.GlobalPosition.Lerp(last.Lerp(next, weight), Main.deltaTime * 0.1f);
}
cooldown -= Main.deltaTime;
LookAt(next);
break;
}
currentAnim = Animations.Idle;
last = base.GlobalPosition;
if (back)
{
current--;
if (current <= 0)
{
back = false;
current = 0;
}
}
else
{
current++;
if (current == points.Length)
{
if (movementType == MovementType.BackAndForth)
{
back = true;
current -= 2;
if (current < 0)
{
current = 0;
back = false;
}
}
else
{
current = 0;
}
}
}
next = points[current].GlobalPosition;
cooldown = moveTime;
break;
}
break;
case State.Noticed:
if (cooldown > 0f)
{
LookAt(Player.instance.GlobalPosition);
cooldown -= Main.deltaTime;
}
else
{
state = State.Chase;
}
break;
case State.Chase:
if (base.GlobalPosition.DistanceTo(Player.instance.GlobalPosition) < radius && base.GlobalPosition.DistanceTo(startPos) < radius)
{
currentAnim = Animations.Walk;
base.Velocity = base.GlobalPosition.DirectionTo(Player.instance.GlobalPosition) * baseSpeed;
LookAt(Player.instance.GlobalPosition);
}
else
{
cooldown = 30f;
noticeCD = 30f;
state = State.Unaware;
StopMoving(Animations.Idle);
}
break;
}
}
public override void _PhysicsProcess(double delta)
{
if (!TextSystem.instance.Visible)
{
base._PhysicsProcess(delta);
}
}
private void OnTouch(Node other)
{
if (other is Player && Room.current.lifeTime >= 60f && base.ProcessMode == ProcessModeEnum.Inherit)
{
additionalEnemies.Insert(0, id);
Coroutine.Start(BattleDR.StartBattle(this, useOWBG: false, additionalEnemies.ToArray(), new List<Entity> { this }, battleMes));
}
}
private void AreaEnter(Node other)
{
if (other.HasMeta(waterTag))
{
if (!immuneToWater)
{
Coroutine.Start(HitByWater(((Node2D)other).GlobalPosition.X < base.GlobalPosition.X), this);
anim.ProcessMode = ProcessModeEnum.Always;
anim.Play("Hurt");
Audio.PlaySound("snd_bomb.wav");
SetDeferred("process_mode", 4);
immuneToWater = true;
}
Main.Particle("WaterBurst", other.GetParent<Node2D>().GlobalPosition, 1, null, localSpace: false);
other.GetParent().QueueFree();
}
}
private IEnumerator HitByWater(bool fromLeft)
{
Vector2 dir = new Vector2(fromLeft ? 1 : (-1), -1f) * 2f;
for (float a = 0f; a < 300f; a += Main.deltaTime)
{
if (!GodotObject.IsInstanceValid(this))
{
yield break;
}
base.GlobalPosition += dir * Main.deltaTime;
yield return null;
}
QueueFree();
}
}