Unstable v0.10.601.0
This commit is contained in:
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe }
|
||||
public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe, Freeze }
|
||||
|
||||
abstract partial class AIController : ISteerable
|
||||
{
|
||||
|
||||
@@ -99,6 +99,7 @@ namespace Barotrauma
|
||||
|
||||
public LatchOntoAI LatchOntoAI { get; private set; }
|
||||
public SwarmBehavior SwarmBehavior { get; private set; }
|
||||
public PetBehavior PetBehavior { get; private set; }
|
||||
|
||||
public CharacterParams.TargetParams SelectedTargetingParams { get { return selectedTargetingParams; } }
|
||||
|
||||
@@ -151,7 +152,10 @@ namespace Barotrauma
|
||||
private set
|
||||
{
|
||||
reverse = value;
|
||||
FishAnimController.reverse = reverse;
|
||||
if (FishAnimController != null)
|
||||
{
|
||||
FishAnimController.reverse = reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +208,9 @@ namespace Barotrauma
|
||||
case "swarmbehavior":
|
||||
SwarmBehavior = new SwarmBehavior(subElement, this);
|
||||
break;
|
||||
case "petbehavior":
|
||||
PetBehavior = new PetBehavior(subElement, this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +228,7 @@ namespace Barotrauma
|
||||
avoidLookAheadDistance = Math.Max(colliderWidth * 3, 1.5f);
|
||||
}
|
||||
|
||||
private CharacterParams.AIParams AIParams => Character.Params.AI;
|
||||
public CharacterParams.AIParams AIParams => Character.Params.AI;
|
||||
private CharacterParams.TargetParams GetTargetParams(string targetTag) => AIParams.GetTarget(targetTag, false);
|
||||
private CharacterParams.TargetParams GetTargetParams(AITarget aiTarget) => GetTargetParams(GetTargetingTag(aiTarget));
|
||||
private string GetTargetingTag(AITarget aiTarget)
|
||||
@@ -423,6 +430,9 @@ namespace Barotrauma
|
||||
bool run = false;
|
||||
switch (State)
|
||||
{
|
||||
case AIState.Freeze:
|
||||
SteeringManager.Reset();
|
||||
break;
|
||||
case AIState.Idle:
|
||||
UpdateIdle(deltaTime);
|
||||
break;
|
||||
@@ -582,6 +592,7 @@ namespace Barotrauma
|
||||
if (!Character.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
LatchOntoAI?.Update(this, deltaTime);
|
||||
PetBehavior?.Update(deltaTime);
|
||||
}
|
||||
IsSteeringThroughGap = false;
|
||||
if (SwarmBehavior != null)
|
||||
@@ -1619,7 +1630,7 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
if (SelectedAiTarget.Entity is Character target)
|
||||
if (SelectedAiTarget.Entity is Character || SelectedAiTarget.Entity is Item)
|
||||
{
|
||||
Limb mouthLimb = Character.AnimController.GetLimb(LimbType.Head);
|
||||
if (mouthLimb == null)
|
||||
@@ -1629,12 +1640,34 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
Vector2 mouthPos = Character.AnimController.SimplePhysicsEnabled ? SimPosition : Character.AnimController.GetMouthPosition().Value;
|
||||
Vector2 attackSimPosition = Character.GetRelativeSimPosition(target);
|
||||
Vector2 attackSimPosition = Character.GetRelativeSimPosition(SelectedAiTarget.Entity);
|
||||
Vector2 limbDiff = attackSimPosition - mouthPos;
|
||||
float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 2);
|
||||
if (limbDiff.LengthSquared() < extent * extent)
|
||||
{
|
||||
Character.SelectCharacter(target);
|
||||
if (SelectedAiTarget.Entity is Character targetCharacter)
|
||||
{
|
||||
Character.SelectCharacter(targetCharacter);
|
||||
}
|
||||
else if (SelectedAiTarget.Entity is Item item)
|
||||
{
|
||||
if (!item.Removed && item.body != null)
|
||||
{
|
||||
float itemBodyExtent = item.body.GetMaxExtent() * 2;
|
||||
if (limbDiff.LengthSquared() < itemBodyExtent * itemBodyExtent)
|
||||
{
|
||||
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.1f), deltaTime);
|
||||
item.body.ApplyForce(-limbDiff * item.body.Mass);
|
||||
|
||||
if (item.Condition <= 0.0f)
|
||||
{
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
}
|
||||
|
||||
PetBehavior?.OnEat(item.GetTags(), 0.1f / item.MaxCondition);
|
||||
}
|
||||
}
|
||||
}
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(limbDiff) * 3);
|
||||
Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos);
|
||||
}
|
||||
|
||||
@@ -715,6 +715,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReportProblem(Character reporter, Order order)
|
||||
{
|
||||
if (reporter == null || order == null) { return; }
|
||||
var visibleHulls = new List<Hull>(reporter.GetVisibleHulls());
|
||||
foreach (var hull in visibleHulls)
|
||||
{
|
||||
PropagateHullSafety(reporter, hull);
|
||||
RefreshTargets(reporter, order, hull);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSpeaking()
|
||||
{
|
||||
if (Character.Oxygen < 20.0f)
|
||||
|
||||
+1
@@ -66,6 +66,7 @@ namespace Barotrauma
|
||||
//if (rootContainer != null) { return false; }
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable == null) { return false; }
|
||||
if (pickable is Holdable h && h.Attachable && h.Attached) { return false; }
|
||||
var wire = item.GetComponent<Wire>();
|
||||
if (wire != null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
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<Item> 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<Item>();
|
||||
|
||||
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<ItemProduction> itemsToProduce = new List<ItemProduction>();
|
||||
private readonly List<Food> foods = new List<Food>();
|
||||
|
||||
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<string> 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<NameTag>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,6 +381,12 @@ namespace Barotrauma
|
||||
{
|
||||
//only one limb left, the character is now full eaten
|
||||
Entity.Spawner?.AddToRemoveQueue(target);
|
||||
|
||||
if (Character.AIController is EnemyAIController enemyAi)
|
||||
{
|
||||
enemyAi.PetBehavior?.OnEat("dead", 1.0f);
|
||||
}
|
||||
|
||||
character.SelectedCharacter = null;
|
||||
}
|
||||
else //sever a random joint
|
||||
|
||||
@@ -753,6 +753,7 @@ namespace Barotrauma
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (connectedLimbs.Contains(limb)) { continue; }
|
||||
if (!character.IsDead && !limb.CanBeSeveredAlive) { continue; }
|
||||
limb.IsSevered = true;
|
||||
if (limb.type == LimbType.RightHand)
|
||||
{
|
||||
|
||||
@@ -215,9 +215,6 @@ namespace Barotrauma
|
||||
public bool IsDismissed => Info != null && Info.IsDismissed;
|
||||
|
||||
private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
private readonly List<float> speedMultipliers = new List<float>();
|
||||
private float greatestNegativeSpeedMultiplier = 1f;
|
||||
private float greatestPositiveSpeedMultiplier = 1f;
|
||||
|
||||
public Entity ViewTarget
|
||||
{
|
||||
@@ -274,6 +271,11 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsPet)
|
||||
{
|
||||
return (AIController as EnemyAIController).PetBehavior.GetName();
|
||||
}
|
||||
|
||||
if (info != null && !string.IsNullOrWhiteSpace(info.Name)) { return info.Name; }
|
||||
var displayName = Params.DisplayName;
|
||||
if (string.IsNullOrWhiteSpace(displayName))
|
||||
@@ -473,6 +475,11 @@ namespace Barotrauma
|
||||
get { return CharacterHealth.IsUnconscious; }
|
||||
}
|
||||
|
||||
public bool IsPet
|
||||
{
|
||||
get { return AIController is EnemyAIController enemyController && enemyController.PetBehavior != null; }
|
||||
}
|
||||
|
||||
public float Oxygen
|
||||
{
|
||||
get { return CharacterHealth.OxygenAmount; }
|
||||
@@ -488,7 +495,7 @@ namespace Barotrauma
|
||||
get { return oxygenAvailable; }
|
||||
set { oxygenAvailable = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
|
||||
public float Stun
|
||||
{
|
||||
get { return IsRagdolled ? 1.0f : CharacterHealth.StunTimer; }
|
||||
@@ -638,7 +645,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!canBeDragged) { return false; }
|
||||
if (Removed || !AnimController.Draggable) { return false; }
|
||||
return IsDead || Stun > 0.0f || LockHands || IsIncapacitated;
|
||||
return IsDead || Stun > 0.0f || LockHands || IsIncapacitated || IsPet;
|
||||
}
|
||||
set { canBeDragged = value; }
|
||||
}
|
||||
@@ -1218,10 +1225,13 @@ namespace Barotrauma
|
||||
return targetMovement;
|
||||
}
|
||||
|
||||
private float greatestNegativeSpeedMultiplier = 1f;
|
||||
private float greatestPositiveSpeedMultiplier = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to modify the character's speed via StatusEffects
|
||||
/// </summary>
|
||||
public float SpeedMultiplier { get; private set; }
|
||||
public float SpeedMultiplier { get; private set; } = 1;
|
||||
|
||||
public void StackSpeedMultiplier(float val)
|
||||
{
|
||||
@@ -1247,6 +1257,40 @@ namespace Barotrauma
|
||||
greatestNegativeSpeedMultiplier = 1f;
|
||||
}
|
||||
|
||||
private float greatestNegativeHealthMultiplier = 1f;
|
||||
private float greatestPositiveHealthMultiplier = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to modify the character's health via StatusEffects
|
||||
/// </summary>
|
||||
public float HealthMultiplier { get; private set; } = 1;
|
||||
|
||||
public void StackHealthMultiplier(float val)
|
||||
{
|
||||
if (val < 1f)
|
||||
{
|
||||
if (val < greatestNegativeHealthMultiplier)
|
||||
{
|
||||
greatestNegativeHealthMultiplier = val;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (val > greatestPositiveHealthMultiplier)
|
||||
{
|
||||
greatestPositiveHealthMultiplier = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateHealthMultiplier()
|
||||
{
|
||||
HealthMultiplier = greatestPositiveHealthMultiplier - (1f - greatestNegativeHealthMultiplier);
|
||||
// Reset, status effects should set the values again, if the conditions match
|
||||
greatestPositiveHealthMultiplier = 1f;
|
||||
greatestNegativeHealthMultiplier = 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Speed reduction from the current limb specific damage. Min 0, max 1.
|
||||
/// </summary>
|
||||
@@ -2132,6 +2176,10 @@ namespace Barotrauma
|
||||
{
|
||||
SelectCharacter(FocusedCharacter);
|
||||
}
|
||||
else if (FocusedCharacter != null && !FocusedCharacter.IsIncapacitated && IsKeyHit(InputType.Use) && FocusedCharacter.IsPet && CanInteract)
|
||||
{
|
||||
(FocusedCharacter.AIController as EnemyAIController).PetBehavior.Play();
|
||||
}
|
||||
else if (FocusedCharacter != null && IsKeyHit(InputType.Health) && FocusedCharacter.CharacterHealth.UseHealthWindow && CanInteract && CanInteractWith(FocusedCharacter, 160f, false))
|
||||
{
|
||||
if (FocusedCharacter == SelectedCharacter)
|
||||
@@ -2369,6 +2417,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
ApplyStatusEffects(AnimController.InWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime);
|
||||
|
||||
UpdateControlled(deltaTime, cam);
|
||||
|
||||
@@ -2377,6 +2426,8 @@ namespace Barotrauma
|
||||
{
|
||||
UpdateOxygen(deltaTime);
|
||||
}
|
||||
|
||||
CalculateHealthMultiplier();
|
||||
CharacterHealth.Update(deltaTime);
|
||||
|
||||
if (IsIncapacitated)
|
||||
|
||||
@@ -141,17 +141,17 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
float max = maxVitality;
|
||||
if (Character?.Info?.Job?.Prefab != null)
|
||||
{
|
||||
return maxVitality + Character.Info.Job.Prefab.VitalityModifier;
|
||||
max += Character.Info.Job.Prefab.VitalityModifier;
|
||||
}
|
||||
return maxVitality;
|
||||
return max * Character.HealthMultiplier;
|
||||
}
|
||||
set
|
||||
{
|
||||
maxVitality = Math.Max(0, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public float MinVitality
|
||||
@@ -450,9 +450,13 @@ namespace Barotrauma
|
||||
matchingAfflictions.AddRange(limbHealth.Afflictions);
|
||||
}
|
||||
}
|
||||
matchingAfflictions.RemoveAll(a =>
|
||||
!a.Prefab.Identifier.Equals(affliction, StringComparison.OrdinalIgnoreCase) &&
|
||||
!a.Prefab.AfflictionType.Equals(affliction, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!string.IsNullOrEmpty(affliction))
|
||||
{
|
||||
matchingAfflictions.RemoveAll(a =>
|
||||
!a.Prefab.Identifier.Equals(affliction, StringComparison.OrdinalIgnoreCase) &&
|
||||
!a.Prefab.AfflictionType.Equals(affliction, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (matchingAfflictions.Count == 0) return;
|
||||
|
||||
@@ -617,8 +621,8 @@ namespace Barotrauma
|
||||
|
||||
private void AddAffliction(Affliction newAffliction)
|
||||
{
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) return;
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) return;
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
|
||||
if (newAffliction.Prefab.AfflictionType == "huskinfection")
|
||||
{
|
||||
var huskPrefab = newAffliction.Prefab as AfflictionPrefabHusk;
|
||||
@@ -636,7 +640,10 @@ namespace Barotrauma
|
||||
affliction.Strength = newStrength;
|
||||
affliction.Source = newAffliction.Source;
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) Kill();
|
||||
if (Vitality <= MinVitality)
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -650,7 +657,10 @@ namespace Barotrauma
|
||||
Character.HealthUpdateInterval = 0.0f;
|
||||
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) Kill();
|
||||
if (Vitality <= MinVitality)
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -707,7 +717,11 @@ namespace Barotrauma
|
||||
UpdateLimbAfflictionOverlays();
|
||||
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) Kill();
|
||||
|
||||
if (Vitality <= MinVitality)
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOxygen(float deltaTime)
|
||||
|
||||
@@ -163,7 +163,13 @@ namespace Barotrauma
|
||||
{
|
||||
item.AddTag("job:" + job.Name);
|
||||
}
|
||||
var idCardTags = itemElement.GetAttributeStringArray("tags", new string[0]);
|
||||
foreach (string tag in idCardTags)
|
||||
{
|
||||
item.AddTag(tag);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
{
|
||||
wifiComponent.TeamID = character.TeamID;
|
||||
|
||||
@@ -62,9 +62,9 @@ namespace Barotrauma
|
||||
{
|
||||
Sprites.Add(new Sprite(subElement));
|
||||
}
|
||||
}
|
||||
|
||||
Color = new Color(element.GetAttributeVector4("color", Vector4.One));
|
||||
}
|
||||
|
||||
Color = element.GetAttributeColor("color", Color.White);
|
||||
|
||||
LifeTime = element.GetAttributeFloat("lifetime", 10.0f);
|
||||
FadeOutTime = Math.Min(LifeTime, element.GetAttributeFloat("fadeouttime", 1.0f));
|
||||
|
||||
@@ -120,6 +120,7 @@ namespace Barotrauma
|
||||
AddChildEvents(initialEventSet);
|
||||
void AddChildEvents(EventSet eventSet)
|
||||
{
|
||||
if (eventSet == null) { return; }
|
||||
foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.First))
|
||||
{
|
||||
if (!level.LevelData.NonRepeatableEvents.Contains(ep))
|
||||
|
||||
@@ -717,11 +717,10 @@ namespace Barotrauma.Items.Components
|
||||
bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
|
||||
(leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);
|
||||
|
||||
if (leakFixed && leak.FlowTargetHull != null)
|
||||
if (leakFixed && leak.FlowTargetHull?.DisplayName != null)
|
||||
{
|
||||
if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
|
||||
{
|
||||
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogLeaksFixed", "[roomname]", leak.FlowTargetHull.DisplayName, true), null, 0.0f, "leaksfixed", 10.0f);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace Barotrauma.Items.Components
|
||||
public bool DrawHudWhenEquipped
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
protected set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Can the item be selected by interacting with it.")]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Xml.Linq;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class NameTag : ItemComponent
|
||||
{
|
||||
[InGameEditable, Serialize("", false, description: "Name written on the tag.", alwaysUseInstanceValues: true)]
|
||||
public string WrittenName { get; set; }
|
||||
|
||||
public NameTag(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
AllowInGameEditing = true;
|
||||
DrawHudWhenEquipped = true;
|
||||
item.EditableWhenEquipped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Terminal : ItemComponent
|
||||
{
|
||||
private const int MaxMessageLength = 150;
|
||||
private const int MaxMessageLength = ChatMessage.MaxLength;
|
||||
|
||||
public string DisplayedWelcomeMessage
|
||||
{
|
||||
|
||||
@@ -304,13 +304,16 @@ namespace Barotrauma
|
||||
|
||||
for (int j = 0; j < otherInventory.capacity; j++)
|
||||
{
|
||||
if (otherInventory.Items[j] == item) otherInventory.Items[j] = null;
|
||||
if (otherInventory.Items[j] == item) { otherInventory.Items[j] = null; }
|
||||
}
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (Items[j] == existingItem) Items[j] = null;
|
||||
if (Items[j] == existingItem) { Items[j] = null; }
|
||||
}
|
||||
|
||||
(otherInventory.Owner as Character)?.DeselectItem(item);
|
||||
(otherInventory.Owner as Character)?.DeselectItem(existingItem);
|
||||
|
||||
bool swapSuccessful = false;
|
||||
if (otherIsEquipped)
|
||||
{
|
||||
|
||||
@@ -119,6 +119,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool EditableWhenEquipped { get; set; } = false;
|
||||
|
||||
//the inventory in which the item is contained in
|
||||
public Inventory ParentInventory
|
||||
{
|
||||
@@ -2257,7 +2259,7 @@ namespace Barotrauma
|
||||
var propertyOwner = allProperties.Find(p => p.Second == property);
|
||||
if (allProperties.Count > 1)
|
||||
{
|
||||
msg.WriteRangedInteger(allProperties.FindIndex(p => p.Second == property), 0, allProperties.Count - 1);
|
||||
msg.Write((byte)allProperties.FindIndex(p => p.Second == property));
|
||||
}
|
||||
|
||||
object value = property.GetValue(propertyOwner.First);
|
||||
@@ -2339,7 +2341,7 @@ namespace Barotrauma
|
||||
int propertyIndex = 0;
|
||||
if (allProperties.Count > 1)
|
||||
{
|
||||
propertyIndex = msg.ReadRangedInteger(0, allProperties.Count - 1);
|
||||
propertyIndex = msg.ReadByte();
|
||||
}
|
||||
|
||||
bool allowEditing = true;
|
||||
@@ -2360,6 +2362,7 @@ namespace Barotrauma
|
||||
if (type == typeof(string))
|
||||
{
|
||||
string val = msg.ReadString();
|
||||
logValue = val;
|
||||
if (allowEditing)
|
||||
{
|
||||
property.TrySetValue(parentObject, val);
|
||||
|
||||
@@ -73,13 +73,13 @@ namespace Barotrauma
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
} = true;
|
||||
|
||||
public bool DamagesCharacters
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
} = true;
|
||||
|
||||
public bool Removed
|
||||
{
|
||||
|
||||
@@ -65,8 +65,8 @@ namespace Barotrauma
|
||||
Noise = new Vector2(
|
||||
PerlinNoise.GetPerlin(Rect.X / 1000.0f, Rect.Y / 1000.0f),
|
||||
PerlinNoise.GetPerlin(Rect.Y / 1000.0f + 0.5f, Rect.X / 1000.0f + 0.5f));
|
||||
|
||||
Color = DirtColor = Color.Lerp(new Color(10, 10, 10, 100), new Color(54, 57, 28, 200), Noise.X);
|
||||
|
||||
DirtColor = Color.Lerp(new Color(10, 10, 10, 100), new Color(54, 57, 28, 200), Noise.X);
|
||||
}
|
||||
|
||||
public bool SetColor(Color color)
|
||||
|
||||
@@ -480,6 +480,9 @@ namespace Barotrauma
|
||||
case "IsDead":
|
||||
{ if (parentObject is Character character) { return character.IsDead; } }
|
||||
break;
|
||||
case "IsHuman":
|
||||
{ if (parentObject is Character character) { return character.IsHuman; } }
|
||||
break;
|
||||
case "IsOn":
|
||||
{ if (parentObject is LightComponent lightComponent) { return lightComponent.IsOn; } }
|
||||
break;
|
||||
@@ -543,6 +546,9 @@ namespace Barotrauma
|
||||
case "SpeedMultiplier":
|
||||
{ if (parentObject is Character character && value is float) { character.StackSpeedMultiplier((float)value); return true; } }
|
||||
break;
|
||||
case "HealthMultiplier":
|
||||
{ if (parentObject is Character character && value is float) { character.StackHealthMultiplier((float)value); return true; } }
|
||||
break;
|
||||
case "IsOn":
|
||||
{ if (parentObject is LightComponent lightComponent && value is bool) { lightComponent.IsOn = (bool)value; return true; } }
|
||||
break;
|
||||
|
||||
@@ -8,14 +8,7 @@ namespace Barotrauma
|
||||
partial class ConditionalSprite
|
||||
{
|
||||
public readonly List<PropertyConditional> conditionals = new List<PropertyConditional>();
|
||||
public bool IsActive
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Target == null) { return false; }
|
||||
return Comparison == PropertyConditional.Comparison.And ? conditionals.All(c => c.Matches(Target)) : conditionals.Any(c => c.Matches(Target));
|
||||
}
|
||||
}
|
||||
public bool IsActive { get; private set; } = true;
|
||||
|
||||
public readonly PropertyConditional.Comparison Comparison;
|
||||
public readonly bool Exclusive;
|
||||
@@ -55,5 +48,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckConditionals()
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsActive = Comparison == PropertyConditional.Comparison.And ? conditionals.All(c => c.Matches(Target)) : conditionals.Any(c => c.Matches(Target));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,16 +687,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (targetIdentifiers != null && currentTargets.Count == 0) { return; }
|
||||
|
||||
if (!HasRequiredItems(entity)) { return; }
|
||||
// If "This" is defined as the target, let's target the conditions only to this entity, because when you use "NearbyTargets", this entity is also taken into account.
|
||||
// If we want to target both, leaving "This" out and using only "NearbyTargets" should work.
|
||||
// Currently we don't have means for targeting only the nearby characters, though.
|
||||
if (HasTargetType(TargetType.This))
|
||||
{
|
||||
if (!HasRequiredConditions(((ISerializableEntity)entity).ToEnumerable())) { return; }
|
||||
}
|
||||
else if (!HasRequiredConditions(currentTargets)) { return; }
|
||||
|
||||
if (!HasRequiredItems(entity) || !HasRequiredConditions(currentTargets)) { return; }
|
||||
|
||||
if (duration > 0.0f && !Stackable)
|
||||
{
|
||||
@@ -919,6 +911,13 @@ namespace Barotrauma
|
||||
Entity.Spawner.AddToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Server) + characterSpawnInfo.Offset,
|
||||
onSpawn: newCharacter =>
|
||||
{
|
||||
if (newCharacter.AIController is EnemyAIController enemyAi &&
|
||||
enemyAi.PetBehavior != null &&
|
||||
entity is Item item &&
|
||||
item.ParentInventory is CharacterInventory inv)
|
||||
{
|
||||
enemyAi.PetBehavior.Owner = inv.Owner as Character;
|
||||
}
|
||||
characters.Add(newCharacter);
|
||||
if (characters.Count == characterSpawnInfo.Count)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user