using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Serialization; using Utility; using Random = UnityEngine.Random; [Serializable] public struct DeveloperStats { public double BaseEfficiency; public int Fingers; public double CaffeineDrainFactor; public double HungerDrainFactor; public double UrinationDrainFactor; public double HappinessDrainFactor; // TODO: Not yet used public double CoffeePreference; public double MatePreference; public DeveloperStats(double baseEfficiency, int fingers, double caffeineDrainFactor, double hungerDrainFactor, double urinationDrainFactor, double happinessDrainFactor, double coffeePreference, double matePreference) { BaseEfficiency = baseEfficiency; Fingers = fingers; CaffeineDrainFactor = caffeineDrainFactor; HungerDrainFactor = hungerDrainFactor; UrinationDrainFactor = urinationDrainFactor; HappinessDrainFactor = happinessDrainFactor; CoffeePreference = coffeePreference; MatePreference = matePreference; } public static readonly DeveloperStats Default = new DeveloperStats(1.0, 10, 1, 1, 1, 1, 0.5, 0.5); } public class Developer : MonoBehaviour { private string _name; [SerializeField] private DeveloperStats _baseStats = DeveloperStats.Default; [SerializeField, ShowOnly] private double _currentEfficiency = 1.0; [SerializeField] private int _fingersLeft = 10; [SerializeField] private double _caffeineLevel = 1.0; [SerializeField] private double _hungerLevel = 1.0; [SerializeField] private double _urgeToUrinateLevel = 1.0; [SerializeField] private double _happiness = 0.75; [SerializeField, ShowOnly] private bool _isSleeping = false; [SerializeField, ShowOnly] private bool _isHyperactive = false; [SerializeField, ShowOnly] private bool _isOvercaffeinated = false; [SerializeField] private DeveloperNeeds _developerNeeds; /// /// Gibt die Grunddaten des Entwicklers zurück. /// public DeveloperStats BaseStats => _baseStats; /// /// Gibt die Anzahl der Finger zurück. /// public int FingersLeft => _fingersLeft; /// /// Gibt die aktuelle Effizienz des Entwicklers in Prozent zurück. /// public double CurrentEfficiency => _currentEfficiency; public string Name => _name; [Serializable, Flags] public enum WantedConsumable { None, Food = 0x01, Drink = 0x02, Coffee = Drink | 0x04, Mate = Drink | 0x08, Pizza = Food | 0x10 } [SerializeField] private GameObject _caffeineNeed; [SerializeField] private WantedConsumable _wantedDrink; [SerializeField] private GameObject _hungerNeed; [SerializeField] private WantedConsumable _wantedFood; [SerializeField] private GameObject _toiletNeed; void Start() { _developerNeeds = gameObject.GetComponent(); _fingersLeft = _baseStats.Fingers; } [ContextMenu("Give Coffee")] private void TestCoffee() { GiveDrink(0.3, WantedConsumable.Coffee); } [ContextMenu("Give Mate")] private void TestMate() { GiveDrink(0.3, WantedConsumable.Mate); } [ContextMenu("Give Food")] private void TestFood() { GiveFood(0.3, WantedConsumable.Pizza); } [ContextMenu("Drain Bladder")] private void TestPee() { Pee(0.3, true); } public void GiveDrink(double caffeineAmount, WantedConsumable drinkType) { if (!drinkType.HasFlag(WantedConsumable.Drink)) throw new ArgumentException(nameof(drinkType), $"{nameof(drinkType)} must be a value with the \"{WantedConsumable.Drink}\" flag"); _caffeineLevel = Math.Min(_caffeineLevel + caffeineAmount, 2.0); if (_caffeineNeed != null && _caffeineLevel > GameManager.Instance.NeedNotificationThreshold) { NeedFullfilled(_caffeineNeed); _caffeineNeed = null; } if (_wantedDrink != WantedConsumable.None) { // TODO: Wie wäre es damit, das nicht fest zu coden? if (drinkType == _wantedDrink) _happiness += 0.2; else _happiness -= 0.2; } else { _happiness += 0.2; } _wantedDrink = WantedConsumable.None; } public void GiveFood(double foodAmount, WantedConsumable foodType) { if (!foodType.HasFlag(WantedConsumable.Food)) throw new ArgumentException(nameof(foodType), $"{nameof(foodType)} must be a value with the \"{WantedConsumable.Food}\" flag"); _hungerLevel = Math.Min(_hungerLevel + foodAmount, 1.0); if (_hungerNeed != null && _hungerLevel > GameManager.Instance.NeedNotificationThreshold) { NeedFullfilled(_hungerNeed); _hungerNeed = null; } if (_wantedFood != WantedConsumable.None) { if (foodType == _wantedFood) _happiness += 0.2; else _happiness -= 0.2; } else { _happiness += 0.2; } _wantedFood = WantedConsumable.None; } /// /// Der Entwickler Pinkelt die angegebene Menge aus. /// /// Die Menge die ausgepinkelt wird. /// Wenn true, dann pinkelt der Entwickler auf einer Toilette. Wenn false, dann pinkelt er auf den Boden (was schlecht ist) public void Pee(double peeAmount, bool onToilet) { _urgeToUrinateLevel = Math.Min(_urgeToUrinateLevel += peeAmount, 1.0); if (_toiletNeed != null && _urgeToUrinateLevel > GameManager.Instance.NeedNotificationThreshold) { NeedFullfilled(_toiletNeed); _toiletNeed = null; } if (onToilet) _happiness += 0.2; else _happiness -= 0.2; } public void UpdateStats(double caffeineDrain, double hungerDrain, double urinationDrain, double happinessDrain) { _caffeineLevel -= caffeineDrain * _baseStats.CaffeineDrainFactor; _hungerLevel -= hungerDrain * _baseStats.HungerDrainFactor; _urgeToUrinateLevel -= urinationDrain * _baseStats.UrinationDrainFactor; _happiness -= happinessDrain * _baseStats.UrinationDrainFactor; _caffeineLevel = Math.Max(_caffeineLevel, 0.0); _hungerLevel = Math.Max(_hungerLevel, 0.0); _urgeToUrinateLevel = Math.Max(_urgeToUrinateLevel, 0.0); _happiness = Math.Max(_happiness, 0.0); _isHyperactive = _caffeineLevel > 1.0; _isOvercaffeinated = _caffeineLevel > 1.5; _isSleeping = _caffeineLevel <= 0.0; if (_caffeineLevel < GameManager.Instance.NeedNotificationThreshold && _caffeineNeed == null) { // TODO: Wir können hier anhand von Präferenz gewichten. if (Random.Range(0.0f, 1.0f) > 0.5f) { _caffeineNeed = _developerNeeds.SpawnMateNeed(); _wantedDrink = WantedConsumable.Mate; } else { _caffeineNeed = _developerNeeds.SpawnCoffeeNeed(); _wantedDrink = WantedConsumable.Coffee; } } if (_hungerLevel < GameManager.Instance.NeedNotificationThreshold && _hungerNeed == null) { _hungerNeed = _developerNeeds.SpawnHungerNeed(); _wantedFood = WantedConsumable.Pizza; } if (_urgeToUrinateLevel < GameManager.Instance.NeedNotificationThreshold && _toiletNeed == null) { // TODO: Go to toilet Debug.Log("Ich muss aufs Klo!"); _toiletNeed = _developerNeeds.SpawnToiletNeed(); } if (_hungerLevel <= 0.0) { Die(); } } private void NeedFullfilled(GameObject needObject) { Instantiate(GameManager.Instance.NeedFullfilledParticleEffect, needObject.transform.position, needObject.transform.rotation); Destroy(needObject); } public void UpdateEfficiency() { _currentEfficiency = _baseStats.BaseEfficiency * (_fingersLeft / 10.0) * CalculateCaffeineEfficiency() * CalculateHungerEfficiency() * CalculateUrinationEfficiency() * CalculateHappinessEfficiency(); } // TODO: Es könnte sich als schwierig erweisen, die Effizienz hoch zu halten. // Man könnte ggf. die Funktionen so anpassen, dass sie eine ganze Weile 100% Effizienz geben. // TODO: Das auch nur ansatzweise zu balancieren wird wiztig 🤯 private double CalculateCaffeineEfficiency() { if (_isSleeping) return 0.0; // Die Formel hat schon recht vielversprechendes Verhalten im Bereich > 1 //if (_isHyperactive) // return 1.0 + (_caffeineLevel - 1.0) * (_caffeineLevel - 1.0); if (_isOvercaffeinated) return 0.0; // https://easings.net/#easeOutCubic return 1.0 - Math.Pow(1.0 - _caffeineLevel, 3.0); } private double CalculateHungerEfficiency() { if (_hungerLevel > 1.0) return 1.0; // https://easings.net/#easeOutCirc return Math.Sqrt(1.0 - Math.Pow(_hungerLevel - 1.0, 2.0)); } private double CalculateUrinationEfficiency() { if (_urgeToUrinateLevel > 1.0) return 1.0; // https://easings.net/#easeOutExpo return Math.Abs(_urgeToUrinateLevel - 1.0) < 0.0001f ? 1.0 : 1.0 - Math.Pow(2, -10 * _urgeToUrinateLevel); } private double CalculateHappinessEfficiency() { // 50% bei 0 Happiness, 100% bei 0.75 Happiness, 125% bei 1 Happiness return 0.5 + _happiness * 2.0 / 3.0; } /// /// Der Entwickler wird verletzt und der Idiot bricht sich ausgerechnet einen Finger... /// public void Hurt() { _fingersLeft--; // Ob er stirbt oder nicht, für uns hat er auf jeden Fall seinen Nutzen verloren. if (_fingersLeft == 0) Die(); } private void Die() { Debug.Log($"{Name} ist verreckt."); } }