| | | 1 | | using Godot; |
| | | 2 | | |
| | | 3 | | |
| | | 4 | | namespace Safarimacik.Model; |
| | | 5 | | |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// Base class for all animals. |
| | | 9 | | /// </summary> |
| | | 10 | | /// This class is abstract and should not be instantiated directly. |
| | | 11 | | /// It provides the basic properties and methods for all animals. |
| | | 12 | | /// <remarks> |
| | | 13 | | /// The Animal class contains properties for hunger, thirst, age, sex, and position. |
| | | 14 | | /// It also contains methods for moving, eating, drinking, mating, and checking vitals. |
| | | 15 | | /// The sex is true if male. |
| | | 16 | | /// </remarks> |
| | | 17 | | public abstract class Animal { |
| | | 18 | | private AnimalState _state; |
| | 1 | 19 | | protected double _hunger = 100; |
| | 1 | 20 | | protected double _thirst = 100; |
| | 1 | 21 | | protected float _moveSpeed = 25f; |
| | 1 | 22 | | protected int _age = 0; |
| | 0 | 23 | | protected Func<Vector2, float> _tileSpeed = _ => 0.0f; |
| | | 24 | | protected HashSet<Vector2I> _foods; |
| | | 25 | | protected HashSet<Vector2I> _waters; |
| | | 26 | | protected bool _sex; // true if male |
| | | 27 | | protected Vector2 _position; |
| | | 28 | | protected IRandomGenerator _rng; |
| | | 29 | | |
| | | 30 | | public event Action? StateChanged; |
| | | 31 | | public event Action<List<(Vector2, float)>>? StepsTaken; |
| | | 32 | | public event Action<Animal>? OffspringSpawned; |
| | | 33 | | public event Action? LifeEnded; |
| | | 34 | | |
| | 1 | 35 | | public int Lifetime => _age; |
| | 1 | 36 | | public bool Sex => _sex; |
| | 1 | 37 | | public Vector2 Position => _position; |
| | 1 | 38 | | public Vector2I TilePosition => new((int)_position.X / 16, (int)_position.Y / 16); |
| | | 39 | | public AnimalState State { |
| | 1 | 40 | | get => _state; |
| | 1 | 41 | | set { |
| | 1 | 42 | | _state = value; |
| | 1 | 43 | | OnStateChanged(); |
| | 1 | 44 | | } |
| | | 45 | | } |
| | | 46 | | public Func<Vector2, float> TileSpeed { |
| | 1 | 47 | | set { _tileSpeed = value; } |
| | | 48 | | } |
| | | 49 | | |
| | 1 | 50 | | public Animal(Vector2 position) : this(position, new RandomGenerator()) { } |
| | | 51 | | |
| | 1 | 52 | | public Animal(Vector2 position, IRandomGenerator rng) { |
| | 1 | 53 | | _foods = []; |
| | 1 | 54 | | _waters = []; |
| | 1 | 55 | | _position = position; |
| | 1 | 56 | | _state = new IdleAnimalState(this); |
| | 1 | 57 | | _sex = new RandomNumberGenerator().Randf() < 0.5; |
| | 1 | 58 | | _rng = rng; |
| | 1 | 59 | | OnStateChanged(); |
| | 1 | 60 | | } |
| | | 61 | | |
| | | 62 | | /// <summary> |
| | | 63 | | /// Moves the animal through the given route, taking the tilespeed into account. (Should be called each tick.) |
| | | 64 | | /// </summary> |
| | | 65 | | /// <param name="path">List of points the animal moves through</param> |
| | | 66 | | /// <remarks> |
| | | 67 | | /// The method removes points from 'path' that have been reached by the animal during it's movement. |
| | | 68 | | /// </remarks> |
| | 1 | 69 | | public void Move(List<Vector2> path) { |
| | 1 | 70 | | float distanceRemaining = _moveSpeed; |
| | | 71 | | |
| | 1 | 72 | | List<(Vector2, float)> stepsTaken = []; |
| | | 73 | | |
| | 1 | 74 | | while (distanceRemaining > 0 && path.Count > 0) { |
| | 1 | 75 | | Vector2 nextPoint = path[0]; |
| | 1 | 76 | | Vector2 toNext = nextPoint - _position; |
| | | 77 | | |
| | 1 | 78 | | if (distanceRemaining >= toNext.Length() / _tileSpeed(_position)) { |
| | 1 | 79 | | _position = nextPoint; |
| | 1 | 80 | | path.RemoveAt(0); |
| | 1 | 81 | | distanceRemaining -= toNext.Length() / _tileSpeed(_position); |
| | 1 | 82 | | } else { |
| | 1 | 83 | | Vector2 direction = toNext.Normalized(); |
| | 1 | 84 | | _position += direction * distanceRemaining * _tileSpeed(_position); |
| | 1 | 85 | | distanceRemaining = 0; |
| | 1 | 86 | | } |
| | 1 | 87 | | stepsTaken.Add((_position, _moveSpeed * _tileSpeed(_position))); |
| | 1 | 88 | | } |
| | 1 | 89 | | OnStepsTaken(stepsTaken); |
| | 1 | 90 | | } |
| | | 91 | | |
| | 1 | 92 | | public bool IsAdult() { |
| | 1 | 93 | | return _age > 30; |
| | 1 | 94 | | } |
| | | 95 | | |
| | 1 | 96 | | public void SaveFood(Vector2I foodPosition) { |
| | 1 | 97 | | _foods.Add(foodPosition); |
| | 1 | 98 | | } |
| | | 99 | | |
| | 1 | 100 | | public void SaveWater(Vector2I waterPosition) { |
| | 1 | 101 | | _waters.Add(waterPosition); |
| | 1 | 102 | | } |
| | | 103 | | |
| | 1 | 104 | | public void RemoveFoodFromMemory(Vector2I foodPosition) { |
| | 1 | 105 | | _foods.Remove(foodPosition); |
| | 1 | 106 | | } |
| | | 107 | | |
| | 1 | 108 | | public Vector2I ClosestWater() { |
| | 1 | 109 | | return _waters.MinBy(TilePosition.DistanceTo); |
| | 1 | 110 | | } |
| | | 111 | | |
| | 1 | 112 | | public Vector2I ClosestFood() { |
| | 1 | 113 | | return _foods.MinBy(TilePosition.DistanceTo); |
| | 1 | 114 | | } |
| | | 115 | | |
| | 1 | 116 | | public void Kill() { |
| | 1 | 117 | | LifeEnded?.Invoke(); |
| | 1 | 118 | | } |
| | | 119 | | |
| | 1 | 120 | | public virtual bool IsHungry() { |
| | 1 | 121 | | return _hunger < 80; |
| | 1 | 122 | | } |
| | | 123 | | |
| | 1 | 124 | | public virtual bool IsThirsty() { |
| | 1 | 125 | | return _thirst < 80; |
| | 1 | 126 | | } |
| | | 127 | | |
| | 1 | 128 | | public virtual void Age() { |
| | 1 | 129 | | _age += 1; |
| | 1 | 130 | | } |
| | | 131 | | |
| | 1 | 132 | | public virtual void Crave() { |
| | 1 | 133 | | _hunger -= Math.Sqrt(_age) * 0.02 + 0.1; |
| | 1 | 134 | | _thirst -= Math.Sqrt(_age) * 0.01 + 0.05; |
| | 1 | 135 | | } |
| | | 136 | | |
| | 1 | 137 | | public virtual void Mate() { |
| | 1 | 138 | | _hunger -= 30; |
| | 1 | 139 | | _thirst -= 30; |
| | 1 | 140 | | } |
| | | 141 | | |
| | 1 | 142 | | public virtual void CheckVitals() { |
| | 1 | 143 | | if (_hunger <= 0 || _thirst <= 0 || _age >= 1000) { |
| | 1 | 144 | | Kill(); |
| | 1 | 145 | | } |
| | 1 | 146 | | } |
| | | 147 | | |
| | 1 | 148 | | public virtual void Drink(double nutrition) { |
| | 1 | 149 | | _thirst = Math.Max(_thirst + nutrition, 100); |
| | 1 | 150 | | } |
| | | 151 | | |
| | 1 | 152 | | public virtual void Eat(double nutrition) { |
| | 1 | 153 | | _hunger = Math.Max(_hunger + nutrition, 100); |
| | 1 | 154 | | } |
| | | 155 | | |
| | 1 | 156 | | protected void OnStateChanged() { |
| | 1 | 157 | | StateChanged?.Invoke(); |
| | 1 | 158 | | } |
| | | 159 | | |
| | 1 | 160 | | protected void OnStepsTaken(List<(Vector2, float)> steps) { |
| | 1 | 161 | | StepsTaken?.Invoke(steps); |
| | 1 | 162 | | } |
| | | 163 | | |
| | 1 | 164 | | protected void OnOffspringSpawned(Animal offspring) { |
| | 1 | 165 | | OffspringSpawned?.Invoke(offspring); |
| | 1 | 166 | | } |
| | | 167 | | } |