using Godot; public partial class Watcher : Node2D { public enum State { Waiting, Opening, Looking, PreparingToTeleport, Teleporting, LitUp } [Export] public float EyeDotSpeed = 2f; [Export] public Vector2 EyeDotDistance = new(4f, 3f); [Export] public float EyeTeleportDistance = 48f; public State CurrentState { get => _state; private set { _state = value; _timeSinceState = 0; GD.Print($"watcher: new state is {_state}"); switch (_state) { case State.Waiting: HideEye(); break; case State.Opening: _dotSprite.Position = _dotSpriteInitialPos; ShowEye(); break; case State.Looking: break; case State.PreparingToTeleport: _sprite.PlayBackwards("default"); break; case State.Teleporting: HideEye(); break; case State.LitUp: _isLitUp = false; ((AudioStreamPlayer2D)FindChild("SighPlayer2D")).Play(); var newPosition = (_player.Position - Position).Normalized(); if (newPosition.IsZeroApprox()) newPosition = new Vector2(0, -1); Position = _player.Position + newPosition * Constants.HalfScreenSize * 1.5f; // TODO: magic number HideEye(); break; default: break; } } } private Player _player; private AnimatedSprite2D _sprite; private Sprite2D _dotSprite; private Vector2 _dotSpriteInitialPos; private State _state; private float _timeSinceState; private bool _isPlayerInside; private bool _isLitUp; public override void _Ready() { _sprite = (AnimatedSprite2D)FindChild("AnimatedSprite2D"); _dotSprite = (Sprite2D)FindChild("Sprite2D"); _dotSpriteInitialPos = _dotSprite.Position; CurrentState = State.Waiting; } public override void _PhysicsProcess(double delta) { _timeSinceState += (float)delta; switch (_state) { case State.Waiting: break; case State.Opening: break; case State.Looking: { var playerRelative = _player.Position - Position; var normal = playerRelative.Normalized(); _dotSprite.Position = _dotSprite.Position.Lerp(normal * EyeDotDistance, EyeDotSpeed * (float)delta); if (_timeSinceState > 5f) // 5 seconds to look at the player CurrentState = State.PreparingToTeleport; } break; case State.PreparingToTeleport: break; case State.Teleporting: if (_timeSinceState > 1f) // 1 second to teleport { var newPosition = _player.Position - Position; if (newPosition.Length() > EyeTeleportDistance) newPosition = newPosition.Normalized() * EyeTeleportDistance; Position += newPosition; CurrentState = State.Opening; } break; case State.LitUp: if (_timeSinceState > 2f) // 2 seconds of cooldown { CurrentState = State.Opening; } break; default: break; } CheckIfKilledPlayer(); CheckIfLitUp(); } private void AnimationEnded() { switch (CurrentState) { case State.Opening: CurrentState = State.Looking; break; case State.PreparingToTeleport: CurrentState = State.Teleporting; break; case State.LitUp: //QueueFree(); GD.Print("TODO: watcher: teleport"); break; case State.Waiting: case State.Looking: case State.Teleporting: default: break; } } private void LightEntered(Area2D area) { if (area.GetParentOrNull() is null) return; // Not a flashlight _isLitUp = true; GD.Print("watcher: light enter"); } private void LightExited(Area2D area) { if (area.GetParentOrNull() is null) return; // Not a flashlight _isLitUp = false; GD.Print("watcher: light exit"); } private void PlayerEntered(Node2D body) { if (body is not Player player) return; _isPlayerInside = true; } private void PlayerLeft(Node2D body) { if (body is not Player player) return; _isPlayerInside = false; } private void PlayerActivated(Node2D body) { GD.Print("watcher: player activated1"); if (CurrentState != State.Waiting || body is not Player player) return; // TODO: turn off the player activation field for better performance GD.Print("watcher: player activated"); _player = player; CurrentState = State.Opening; } void HideEye() { Modulate = new Color(1, 1, 1, 0); _sprite.Stop(); } void ShowEye() { Modulate = new Color(1, 1, 1, 1); _sprite.Play("default"); } void CheckIfKilledPlayer() { if (_player is null || !_isPlayerInside) return; if (CurrentState is State.LitUp or State.PreparingToTeleport or State.Teleporting or State.Waiting) return; _player.Kill(this); } void CheckIfLitUp() { if (!_isLitUp) return; if (CurrentState is State.Waiting or State.Opening or State.Teleporting or State.LitUp) return; CurrentState = State.LitUp; } }