diff options
Diffstat (limited to 'OnekoLocal.cs')
| -rw-r--r-- | OnekoLocal.cs | 216 |
1 files changed, 207 insertions, 9 deletions
diff --git a/OnekoLocal.cs b/OnekoLocal.cs index 71387e1..89dacb9 100644 --- a/OnekoLocal.cs +++ b/OnekoLocal.cs @@ -26,7 +26,11 @@ class OnekoLocal : Oneko Name = OnekoOnline.Config.GetValue("NekoName", "Oneko"); - AIStates = [new BoredState(this), new ChaseMouseState(this), new ItchyState(this)]; + AIStates = [ + new BoredState(this), new ChaseMouseState(this), new ItchyState(this), + new SleepState(this), new RunAwayFromCatState(this), new RandomSpotState(this), + new ChaseOtherCatState(this), new YawnState(this) + ]; CurrentAIState = AIStates[0]; } @@ -39,9 +43,21 @@ class OnekoLocal : Oneko updateTimer = 0f; } + public override void Draw() + { + #if DEBUG + Raylib.DrawText($"Playfullness {OnekoAIState.Playfullness}", 0, 210, 10, Color.White); + Raylib.DrawText($"Energy {OnekoAIState.Energy}", 0, 220, 10, Color.White); + Raylib.DrawText($"State {CurrentAIState.GetType().Name}", 0, 230, 10, Color.White); + #endif + + base.Draw(); + } + public void OnekoUpdate(float delta) { //AI STUFF + if (Random.Shared.NextSingle() > 0.997f) OnekoAIState.Playfullness = Random.Shared.NextSingle(); OnekoAIState HighestPriority = AIStates.MaxBy(state => state.GetPriority())!; if (HighestPriority != CurrentAIState) { CurrentAIState.ExitState(); @@ -62,11 +78,18 @@ class OnekoLocal : Oneko writer.Put(FrameId); OnekoOnline.Client?.ConnectedServer.Send(writer, LiteNetLib.DeliveryMethod.Unreliable); } + + Position = Vector2.Clamp(Position, Vector2.Zero, new(OnekoOnline.WindowX, OnekoOnline.WindowY)); } void MoveTowardsPosition(Vector2 TargetPosition) { - Vector2 TargetDirection = (TargetPosition-Position).LimitLength(10f); + if (TargetPosition.Round() == Position) { + Animation = Idle; + return; + } + + Vector2 TargetDirection = (TargetPosition-Position).LimitLength(8+(OnekoAIState.Energy*4f)); if (TargetDirection.Length() > 1) { Animation = GetDirectionalRun(TargetDirection); } @@ -83,6 +106,19 @@ class OnekoLocal : Oneko public virtual void StartState() {} public virtual void ExitState() {} protected bool IsCurrentState => Neko.CurrentAIState == this; + + //Mood + private static float _playfulness = 0.8f; + public static float Playfullness { + get => _playfulness; + set => _playfulness = Math.Clamp(value, 0f, 1f); + } + + private static float _energy = 0.8f; + public static float Energy { + get => _energy; + set => _energy = Math.Clamp(value, 0f, 1f); + } } class BoredState(OnekoLocal neko) : OnekoAIState(neko) @@ -94,25 +130,121 @@ class OnekoLocal : Oneko public override void Update(float delta) { InactivityTimer += delta; + Energy -= delta/100f; 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; + if (InactivityTimer > 5f && InactivityTimer < 6.5f) Neko.Animation = ScratchSelf; + if (InactivityTimer > 10f) { + if (Energy < 0.5f) SleepState.Sleeping = true; + else RandomSpotState.NewSpotNow = true; + } } public override void ExitState() => InactivityTimer = 0f; } + class SleepState : OnekoAIState + { + float SleepTimer = 0f; + public static bool Sleeping; + + public SleepState(OnekoLocal neko) : base(neko) + { + Mouse.Clicked += mouse => { + if (Sleeping && Energy > 0.25f && mouse is MouseLocal) Sleeping = false; + }; + } + + public override int GetPriority() + { + if (Energy == 0f) Sleeping = true; + if (Energy > 0.5f && Sleeping) return 1; + if (Sleeping) return 900; + else return -1; + } + + public override void Update(float delta) + { + SleepTimer += delta; + Energy += delta/90f; + + if (SleepTimer < 1f) Neko.Animation = Yawn; + else Neko.Animation = Sleep; + } + + public override void ExitState() { + SleepTimer = 0f; + Sleeping = false; + } + } + + class RunAwayFromCatState(OnekoLocal neko) : OnekoAIState(neko) + { + Oneko? ClosestNeko; + float RandOffset; + + public override int GetPriority() + { + if (AllNekos.Count < 2) return -1; + //If a cat is too close, run! + ClosestNeko = AllNekos.Where(neko => neko != Neko && Vector2.Distance(neko.Position, Neko.Position) < 30).FirstOrDefault(); + if (ClosestNeko != null) return 5; + return -1; + } + + public override void StartState() => RandOffset = Random.Shared.NextSingle() * 100f; + + public override void Update(float delta) + { + if (ClosestNeko is null) return; + Energy -= delta/50f; + Playfullness += delta/60f; + Vector2 Direction = Raymath.Vector2Rotate(Directions.Up, DateTime.Now.Second+RandOffset); + Neko.MoveTowardsPosition(ClosestNeko.Position + Direction*100); + } + } + + class ChaseOtherCatState(OnekoLocal neko) : OnekoAIState(neko) + { + Oneko? Victim; + DateTime LastChase = DateTime.UnixEpoch; + float RandOffset; + + public override int GetPriority() + { + //If feeling playful, chase another cat! + if (Victim != null && LastChase.AddSeconds(Energy > 0.5 ? 25 : 15) > DateTime.Now) return 80; + if (Playfullness < 0.5f || AllNekos.Count < 2 || LastChase.AddMinutes(Energy > 0.5 ? 1 : 3) > DateTime.Now) return -1; + Victim = AllNekos.Where(neko => neko != Neko).MinBy(neko => Random.Shared.NextSingle()); + if (Victim != null) return 80; + return -1; + } + + public override void StartState() { + RandOffset = Random.Shared.NextSingle() * 100f; + LastChase = DateTime.Now; + } + + public override void Update(float delta) + { + if (Victim is null) return; + Playfullness -= delta/40f; + Energy -= delta/60f; + Neko.MoveTowardsPosition(Victim.Position + Raymath.Vector2Rotate(Directions.Down, DateTime.Now.Second+RandOffset)*24); + } + } + class ChaseMouseState : OnekoAIState { Mouse? MouseToChase; + Vector2 MousePosition; float AlertTimer = 0f; public ChaseMouseState(OnekoLocal neko) : base(neko) { Mouse.Clicked += mouse => { + Playfullness += 0.03f; if (MouseToChase != mouse && Random.Shared.NextSingle() > 0.3f) { AlertTimer = 0.5f; MouseToChase = mouse; @@ -122,8 +254,11 @@ class OnekoLocal : Oneko public override int GetPriority() { + if (Playfullness < 0.5f) return -1; 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; + if (MouseToChase is null || !MouseToChase.Visible) return -1; + if (MouseToChase.Position == MousePosition && !IsCurrentState) return -1; + if (Vector2.Distance(MouseToChase.Position, Neko.Position) > 30) return (int)(Playfullness*150f); return -1; } @@ -136,7 +271,11 @@ class OnekoLocal : Oneko } if (MouseToChase is null || !MouseToChase.Visible) return; - Neko.MoveTowardsPosition(MouseToChase.Position); + MousePosition = MouseToChase.Position; + Neko.MoveTowardsPosition(MousePosition); + + Energy -= delta/40f; + Playfullness -= delta/30f; } public override void ExitState() { @@ -145,6 +284,45 @@ class OnekoLocal : Oneko } } + class RandomSpotState(OnekoLocal neko) : OnekoAIState(neko) + { + DateTime NextInvestigation = DateTime.Now; + Vector2 Spot; + float AlertTimer = 0f; + public static bool NewSpotNow = false; + + public override int GetPriority() + { + if (NewSpotNow) { + NewSpotNow = false; + return 50; + } + if (NextInvestigation < DateTime.Now || (Energy > 0.75f && Random.Shared.NextSingle() > 0.99f)) return 50; + return -1; + } + + public override void StartState() + { + Spot = new(Random.Shared.NextSingle()*OnekoOnline.WindowX, Random.Shared.NextSingle()*OnekoOnline.WindowY); + } + + public override void Update(float delta) + { + if (AlertTimer < 1f) { + AlertTimer += delta; + Neko.Animation = Alert; + return; + } + Neko.MoveTowardsPosition(Spot); + Energy -= delta/40f; + + if (Vector2.Distance(Spot, Neko.Position) < 10) + NextInvestigation = DateTime.Now.AddSeconds(Random.Shared.NextSingle() * 120); + } + + public override void ExitState() => AlertTimer = 0f; + } + class ItchyState(OnekoLocal neko) : OnekoAIState(neko) { float ItchTimer = -1f; @@ -152,8 +330,9 @@ class OnekoLocal : Oneko public override int GetPriority() { - if (ItchTimer > 0f || Random.Shared.NextSingle() > 0.997f) return 1000; - else return -1; + if (Neko.CurrentAIState is SleepState && Energy < 0.5f) return -1; + if (ItchTimer > 0f || Random.Shared.NextSingle() > 0.995f) return 1000; + return -1; } public override void StartState() { @@ -166,4 +345,23 @@ class OnekoLocal : Oneko Neko.Animation = Cleaning ? Clean : ScratchSelf; } } + + class YawnState(OnekoLocal neko) : OnekoAIState(neko) + { + float YawnTimer = -1f; + + public override int GetPriority() + { + if (Energy < 0.3f && Neko.CurrentAIState is not SleepState && (YawnTimer > 0f || Random.Shared.NextSingle() > 0.96f)) return 2000; + else return -1; + } + + public override void StartState() => YawnTimer = 1.5f; + + public override void Update(float delta) { + YawnTimer -= delta; + Energy -= delta/80f; + Neko.Animation = Yawn; + } + } }
\ No newline at end of file |
