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 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("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 { spawn }, new Array { 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 { 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().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(); } }