using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; 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; [SerializeField] private float _talkRange = 2.0f; /// /// 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; [SerializeField] private GameObject _caffeineNeed; [SerializeField] private WantedConsumable _wantedDrink; [SerializeField] private GameObject _hungerNeed; [SerializeField] private WantedConsumable _wantedFood; [SerializeField] private GameObject _toiletNeed; private AudioSource _audioSource; private float _talkPauseTime = 15.0f; [SerializeField, ShowOnly] private float _talkTimer; private bool _hasTalkedWhileHyperactive = false; private bool _hasTalkedWhileOvercaffeinated = false; private List _needList = new List(); void Start() { _developerNeeds = gameObject.GetComponent(); _audioSource = GetComponent(); _talkTimer = UnityEngine.Random.Range(5.0f, 15.0f); _fingersLeft = _baseStats.Fingers; } private void Update() { if (!_audioSource.isPlaying) { _talkTimer -= Time.deltaTime; if (_talkTimer < 0.0f) { _talkTimer = _talkPauseTime; TalkIfInRange(); } } } [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) { UpdateNeedPositions(_caffeineNeed); _needList.Remove(_caffeineNeed); NeedFullfilled(_caffeineNeed); _caffeineNeed = null; } if (_wantedDrink != WantedConsumable.None) { // TODO: Wie wäre es damit, das nicht fest zu coden? if (drinkType == _wantedDrink) { Talk($"The Devolper thanks Gottfried for the {drinkType.GetAsString()}"); _happiness += 0.2; } else { Talk($"The Devolper blames Gottfried for bringing {drinkType.GetAsString()} but he actaully wanted a {_wantedDrink.GetAsString()}"); _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) { UpdateNeedPositions(_hungerNeed); _needList.Remove(_hungerNeed); NeedFullfilled(_hungerNeed); _hungerNeed = null; Talk("The Developer thanks Gottfried for the Pizza"); } 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) { UpdateNeedPositions(_toiletNeed); _needList.Remove(_toiletNeed); NeedFullfilled(_toiletNeed); _toiletNeed = null; Talk("The Developer finally can go to the toilet"); } 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 && _caffeineLevel <= 1.5; _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(_needList.Count); _wantedDrink = WantedConsumable.Mate; _needList.Add(_caffeineNeed); } else { _caffeineNeed = _developerNeeds.SpawnCoffeeNeed(_needList.Count); _wantedDrink = WantedConsumable.Coffee; _needList.Add(_caffeineNeed); } } if (_hungerLevel < GameManager.Instance.NeedNotificationThreshold && _hungerNeed == null) { _hungerNeed = _developerNeeds.SpawnHungerNeed(_needList.Count); _wantedFood = WantedConsumable.Pizza; _needList.Add(_hungerNeed); } if (_urgeToUrinateLevel < GameManager.Instance.NeedNotificationThreshold && _toiletNeed == null) { // TODO: Go to toilet Debug.Log("Ich muss aufs Klo!"); _toiletNeed = _developerNeeds.SpawnToiletNeed(_needList.Count); _needList.Add(_toiletNeed); } if (_hungerLevel <= 0.0) { Die(); } } private void UpdateNeedPositions(GameObject reference) { int referenceIndex = _needList.IndexOf(reference); if (referenceIndex == -1) { Debug.LogError("Reference GameObject not in List."); return; } for (int i = referenceIndex + 1; i < _needList.Count; i++) { GameObject currentItem = _needList[i]; currentItem.GetComponent().originalY -= 0.5f; } } 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; if (_isHyperactive) { if (!_hasTalkedWhileHyperactive) { Talk("The Developer is surprisingly productive right now duo to caffeine"); _hasTalkedWhileHyperactive = true; } } else { _hasTalkedWhileHyperactive = false; } if (_isOvercaffeinated) { if (!_hasTalkedWhileOvercaffeinated) { Talk("The Developer drank too much caffeine, The Developer needs a break immediately"); _hasTalkedWhileOvercaffeinated = true; } return 0.0; } else { _hasTalkedWhileOvercaffeinated = false; } // 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."); } private bool IsGottfriedInRange() { float distanceToPlayer = Vector3.Distance(transform.position, GameManager.Instance.PlayerTransform.position); return distanceToPlayer <= _talkRange; } /// /// Starts talking when player is near to the Dev. /// public void TalkIfInRange() { if (IsGottfriedInRange()) { Talk(); } } /// /// Dev starts talking. /// public void Talk(string context = null) { if (context == null) { context = GameManager.Instance.GetContextAsString(); } if (!_audioSource.isPlaying) { GetComponent().Generate(context); } } }