summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSarah B. <1029chris@gmail.com>2024-01-23 18:51:16 -0800
committerSarah B. <1029chris@gmail.com>2024-01-23 18:51:16 -0800
commit163c983130fe090182b0bb3ab7b7402921705083 (patch)
tree26b3c6f69b96331fe65c46392a2f925be985d4bf
parent3f4fe74715124357ba3341e635fdb2bda3fcd92d (diff)
Added lots of oneko AI!
-rw-r--r--OnekoLocal.cs216
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