using System.Numerics; using LiteNetLib.Utils; using OnekoOnline.Net; using Raylib_cs; namespace OnekoOnline; class OnekoLocal : Oneko { public static Oneko? Instance; static int Id => OnekoOnline.Client?.Id ?? -1; OnekoAIState CurrentAIState; readonly OnekoAIState[] AIStates; OnekoAnimation Animation; float updateTimer = 0f; const float updateRate = 1f/5f; int Frame = 0; int FrameCounter = 0; public OnekoLocal() : base() { Instance ??= this; Name = OnekoOnline.Config.GetValue("NekoName", "Oneko"); AIStates = [new BoredState(this), new ChaseMouseState(this), new ItchyState(this)]; CurrentAIState = AIStates[0]; } public override void Update(float delta) { updateTimer += delta; if (updateTimer < updateRate) return; OnekoUpdate(updateTimer); updateTimer = 0f; } public void OnekoUpdate(float delta) { //AI STUFF OnekoAIState HighestPriority = AIStates.MaxBy(state => state.GetPriority())!; if (HighestPriority != CurrentAIState) { CurrentAIState.ExitState(); CurrentAIState = HighestPriority; CurrentAIState.StartState(); } CurrentAIState.Update(delta); FrameCounter = (FrameCounter+1)%(Animation.AnimSpeed+1); Frame = (int)MathF.Round(FrameCounter/(float)Animation.AnimSpeed); Sprite = Animation.GetFrame(Frame); if (OnekoOnline.Client!.Connected) { NetDataWriter writer = new(); writer.Put(new PacketInfo(PacketType.OnekoState, Id)); writer.Put(Position); writer.Put(FrameId); OnekoOnline.Client?.ConnectedServer.Send(writer, LiteNetLib.DeliveryMethod.Unreliable); } } void MoveTowardsPosition(Vector2 TargetPosition) { Vector2 TargetDirection = (TargetPosition-Position).LimitLength(10f); if (TargetDirection.Length() > 1) { Animation = GetDirectionalRun(TargetDirection); } Position += TargetDirection; } protected abstract class OnekoAIState(OnekoLocal neko) { protected OnekoLocal Neko = neko; public abstract int GetPriority(); public abstract void Update(float delta); public virtual void StartState() {} public virtual void ExitState() {} protected bool IsCurrentState => Neko.CurrentAIState == this; } class BoredState(OnekoLocal neko) : OnekoAIState(neko) { float InactivityTimer = 0f; public override int GetPriority() => 0; public override void Update(float delta) { InactivityTimer += delta; Neko.Animation = Idle; if (InactivityTimer > 1.5f && InactivityTimer < 3f) Neko.Animation = Clean; if (InactivityTimer > 5f) Neko.Animation = ScratchSelf; if (InactivityTimer > 7f) Neko.Animation = Yawn; if (InactivityTimer > 8f) Neko.Animation = Sleep; } public override void ExitState() => InactivityTimer = 0f; } class ChaseMouseState : OnekoAIState { Mouse? MouseToChase; float AlertTimer = 0f; public ChaseMouseState(OnekoLocal neko) : base(neko) { Mouse.Clicked += mouse => { if (MouseToChase != mouse && Random.Shared.NextSingle() > 0.3f) { AlertTimer = 0.5f; MouseToChase = mouse; } }; } public override int GetPriority() { MouseToChase ??= Mouse.AllMice.Where(m => m.Visible).MinBy(m => Vector2.Distance(m.Position, Neko.Position)); if (MouseToChase != null && MouseToChase.Visible && Vector2.Distance(MouseToChase.Position, Neko.Position) > 15) return 100; return -1; } public override void Update(float delta) { if (AlertTimer < 1f) { AlertTimer += delta; Neko.Animation = Alert; return; } if (MouseToChase is null || !MouseToChase.Visible) return; Neko.MoveTowardsPosition(MouseToChase.Position); } public override void ExitState() { MouseToChase = null; AlertTimer = 0f; } } class ItchyState(OnekoLocal neko) : OnekoAIState(neko) { float ItchTimer = -1f; bool Cleaning = false; public override int GetPriority() { if (ItchTimer > 0f || Random.Shared.NextSingle() > 0.997f) return 1000; else return -1; } public override void StartState() { ItchTimer = 1f + Random.Shared.NextSingle()*4f; Cleaning = Random.Shared.NextSingle() > 0.5f; } public override void Update(float delta) { ItchTimer -= delta; Neko.Animation = Cleaning ? Clean : ScratchSelf; } } }