using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml.Linq; namespace Barotrauma { class PetBehavior { public float Hunger { get; set; } = 50.0f; public float Happiness { get; set; } = 50.0f; public float MaxHappiness { get; set; } public float MaxHunger { get; set; } public float HappinessDecreaseRate { get; set; } public float HungerIncreaseRate { get; set; } public float PlayForce { get; set; } public float PlayTimer { get; set; } public EnemyAIController AiController { get; private set; } = null; public Character Owner { get; set; } private class ItemProduction { public struct Item { public ItemPrefab Prefab; public float Commonness; } public List Items; public Vector2 HungerRange; public Vector2 HappinessRange; public float Rate; public float HungerRate; public float InvHungerRate; public float HappinessRate; public float InvHappinessRate; private readonly float totalCommonness; private float timer; public ItemProduction(XElement element) { Items = new List(); HungerRate = element.GetAttributeFloat("hungerrate", 0.0f); InvHungerRate = element.GetAttributeFloat("invhungerrate", 0.0f); HappinessRate = element.GetAttributeFloat("happinessrate", 0.0f); InvHappinessRate = element.GetAttributeFloat("invhappinessrate", 0.0f); string[] requiredHappinessStr = element.GetAttributeString("requiredhappiness", "0-100").Split('-'); string[] requiredHungerStr = element.GetAttributeString("requiredhunger", "0-100").Split('-'); HappinessRange = new Vector2(0, 100); HungerRange = new Vector2(0, 100); float tempF; if (requiredHappinessStr.Length >= 2) { if (float.TryParse(requiredHappinessStr[0], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { HappinessRange.X = tempF; } if (float.TryParse(requiredHappinessStr[1], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { HappinessRange.Y = tempF; } } if (requiredHungerStr.Length >= 2) { if (float.TryParse(requiredHungerStr[0], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { HungerRange.X = tempF; } if (float.TryParse(requiredHungerStr[1], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { HungerRange.Y = tempF; } } Rate = element.GetAttributeFloat("rate", 0.016f); totalCommonness = 0.0f; foreach (XElement subElement in element.Elements()) { switch (subElement.Name.LocalName.ToLowerInvariant()) { case "item": Item newItemToProduce = new Item { Prefab = ItemPrefab.Find("", subElement.GetAttributeString("identifier", "")), Commonness = subElement.GetAttributeFloat("commonness", 0.0f) }; totalCommonness += newItemToProduce.Commonness; Items.Add(newItemToProduce); break; } } timer = 1.0f; } public void Update(PetBehavior pet, float deltaTime) { if (pet.Happiness < HappinessRange.X || pet.Happiness > HappinessRange.Y) { return; } if (pet.Hunger < HungerRange.X || pet.Hunger > HungerRange.Y) { return; } float currentRate = Rate; currentRate += HappinessRate * (pet.Happiness - HappinessRange.X) / (HappinessRange.Y - HappinessRange.X); currentRate += InvHappinessRate * (1.0f - ((pet.Happiness - HappinessRange.X) / (HappinessRange.Y - HappinessRange.X))); currentRate += HungerRate * (pet.Hunger - HungerRange.X) / (HungerRange.Y - HungerRange.X); currentRate += InvHungerRate * (1.0f - ((pet.Hunger - HungerRange.X) / (HungerRange.Y - HungerRange.X))); timer -= currentRate * deltaTime; if (timer <= 0.0f) { timer = 1.0f; float r = Rand.Range(0.0f, totalCommonness); float aggregate = 0.0f; for (int i = 0; i < Items.Count; i++) { aggregate += Items[i].Commonness; if (aggregate >= r) { Entity.Spawner.AddToSpawnQueue(Items[i].Prefab, pet.AiController.Character.WorldPosition); break; } } } } } private class Food { public string Tag; public Vector2 HungerRange; public float Hunger; public float Happiness; public float Priority; public CharacterParams.TargetParams TargetParams = null; } private readonly List itemsToProduce = new List(); private readonly List foods = new List(); public PetBehavior(XElement element, EnemyAIController aiController) { AiController = aiController; AiController.Character.CanBeDragged = true; MaxHappiness = element.GetAttributeFloat("maxhappiness", 100.0f); MaxHunger = element.GetAttributeFloat("maxhunger", 100.0f); Happiness = MaxHappiness * 0.5f; Hunger = MaxHunger * 0.5f; HappinessDecreaseRate = element.GetAttributeFloat("happinessdecreaserate", 0.1f); HungerIncreaseRate = element.GetAttributeFloat("hungerincreaserate", 0.25f); PlayForce = element.GetAttributeFloat("playforce", 15.0f); foreach (XElement subElement in element.Elements()) { switch (subElement.Name.LocalName.ToLowerInvariant()) { case "itemproduction": itemsToProduce.Add(new ItemProduction(subElement)); break; case "eat": Food food = new Food { Tag = subElement.GetAttributeString("tag", "") }; string[] requiredHungerStr = subElement.GetAttributeString("requiredhunger", "0-100").Split('-'); food.HungerRange = new Vector2(0, 100); if (requiredHungerStr.Length >= 2) { if (float.TryParse(requiredHungerStr[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float tempF)) { food.HungerRange.X = tempF; } if (float.TryParse(requiredHungerStr[1], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { food.HungerRange.Y = tempF; } } food.Hunger = subElement.GetAttributeFloat("hunger", -1); food.Happiness = subElement.GetAttributeFloat("happiness", 1); food.Priority = subElement.GetAttributeFloat("priority", 100); foods.Add(food); break; } } } public void OnEat(IEnumerable tags, float amount) { for (int i = 0; i < foods.Count; i++) { if (tags.Any(t => t.Equals(foods[i].Tag, System.StringComparison.OrdinalIgnoreCase))) { Hunger += foods[i].Hunger * amount; Happiness += foods[i].Happiness * amount; break; } } } public void OnEat(string tag, float amount) { for (int i = 0; i < foods.Count; i++) { if (tag.Equals(foods[i].Tag, System.StringComparison.OrdinalIgnoreCase)) { Hunger += foods[i].Hunger * amount; Happiness += foods[i].Happiness * amount; break; } } } public void Play() { if (PlayTimer > 0.0f) { return; } PlayTimer = 5.0f; AiController.Character.Stun = 1.0f; Happiness += 10.0f; if (Happiness > MaxHappiness) { Happiness = MaxHappiness; } AiController.Character.AnimController.MainLimb.body.LinearVelocity += new Vector2(0, PlayForce); } public string GetName() { if (AiController.Character.Inventory != null) { var items = AiController.Character.Inventory.Items; for (int i = 0; i < items.Length; i++) { var item = items[i]; if (item == null) { continue; } var tag = item.GetComponent(); if (tag != null && !string.IsNullOrWhiteSpace(tag.WrittenName)) { return tag.WrittenName; } } } return AiController.Character.Name; } public void Update(float deltaTime) { var character = AiController.Character; if (character?.Removed ?? true || character.IsDead) { return; } if (GameMain.NetworkMember?.IsClient ?? false) { return; } //TODO: syncing Hunger += HungerIncreaseRate * deltaTime; Happiness -= HappinessDecreaseRate * deltaTime; PlayTimer -= deltaTime; for (int i = 0; i < foods.Count; i++) { if (Hunger >= foods[i].HungerRange.X && Hunger <= foods[i].HungerRange.Y) { if (foods[i].TargetParams == null && AiController.AIParams.TryAddNewTarget(foods[i].Tag, AIState.Eat, foods[i].Priority, out CharacterParams.TargetParams targetParams)) { foods[i].TargetParams = targetParams; } } else if (foods[i].TargetParams != null) { AiController.AIParams.RemoveTarget(foods[i].TargetParams); foods[i].TargetParams = null; } } if (Hunger < 0.0f) { Hunger = 0.0f; } if (Hunger > MaxHunger) { Hunger = MaxHunger; } if (Happiness < 0.0f) { Happiness = 0.0f; } if (Happiness > MaxHappiness) { Happiness = MaxHappiness; } if (PlayTimer < 0.0f) { PlayTimer = 0.0f; } if (Hunger >= MaxHunger * 0.99f) { character.CharacterHealth.ApplyAffliction(character.AnimController.MainLimb, new Affliction(AfflictionPrefab.InternalDamage, 8.0f * deltaTime)); } else if (Hunger < MaxHunger * 0.1f) { character.CharacterHealth.ReduceAffliction(null, null, 8.0f * deltaTime); } if (character.SelectedBy != null) { character.Stun = 1.0f; } for (int i = 0; i < itemsToProduce.Count; i++) { itemsToProduce[i].Update(this, deltaTime); } } } }