Unstable 0.17.0.0

This commit is contained in:
Markus Isberg
2022-02-26 02:43:01 +09:00
parent a83f375681
commit 3974067915
913 changed files with 32472 additions and 32364 deletions
@@ -18,45 +18,72 @@ namespace Barotrauma.MapCreatures.Behavior
public readonly BallastFloraBehavior? ParentBallastFlora;
public int ID = -1;
public Item ClaimedItem;
public Item? ClaimedItem;
public int ClaimedItemId = -1;
public float MaxHealth = 100f;
public float Health = 100f;
private float health = 100;
public float Health
{
get { return health; }
set { health = MathHelper.Clamp(value, 0.0f, MaxHealth); }
}
public float RemoveTimer = 60.0f;
public bool SpawningItem;
public Item? AttackItem;
public bool IsRoot;
/// <summary>
/// Decorative branches that grow around the root
/// </summary>
public bool IsRootGrowth;
public bool Removed;
public bool DisconnectedFromRoot;
public Hull? CurrentHull;
public float Pulse = 1.0f;
private bool inflate;
private float pulseDelay = Rand.Range(0f, 3f);
public readonly BallastFloraBranch? ParentBranch;
/// <summary>
/// How far from the root this branch is
/// </summary>
public readonly int BranchDepth;
public float AccumulatedDamage;
public float DamageVisualizationTimer;
public Vector2 ShakeAmount;
// Adjacent tiles, used to free up sides when this branch gets removed
public readonly Dictionary<TileSide, BallastFloraBranch> Connections = new Dictionary<TileSide, BallastFloraBranch>();
public BallastFloraBranch(BallastFloraBehavior? parent, Vector2 position, VineTileType type, FoliageConfig? flowerConfig = null, FoliageConfig? leafConfig = null, Rectangle? rect = null)
public BallastFloraBranch(BallastFloraBehavior? parent, BallastFloraBranch? parentBranch, Vector2 position, VineTileType type, FoliageConfig? flowerConfig = null, FoliageConfig? leafConfig = null, Rectangle? rect = null)
: base(null, position, type, flowerConfig, leafConfig, rect)
{
ParentBranch = parentBranch;
ParentBallastFlora = parent;
if (parentBranch != null)
{
BranchDepth = parentBranch.BranchDepth + 1;
}
}
public void UpdateHealth()
{
if (MaxHealth <= Health) { return; }
Color healthColor = Color.White * (1.0f - Health / MaxHealth);
HealthColor = healthColor;
HealthColor = Color.Lerp(HealthColor, healthColor, 0.05f);
}
public void UpdatePulse(float deltaTime, float inflateSpeed, float deflateSpeed, float delay)
{
if (ParentBallastFlora == null) { return; }
if (ParentBallastFlora == null || DisconnectedFromRoot) { return; }
if (pulseDelay > 0)
{
@@ -91,7 +118,7 @@ namespace Barotrauma.MapCreatures.Behavior
public List<Tuple<Vector2, Vector2>> debugSearchLines = new List<Tuple<Vector2, Vector2>>();
#endif
private static List<BallastFloraBehavior> _entityList = new List<BallastFloraBehavior>();
private readonly static List<BallastFloraBehavior> _entityList = new List<BallastFloraBehavior>();
public static IEnumerable<BallastFloraBehavior> EntityList => _entityList;
public enum NetworkHeader
@@ -101,119 +128,126 @@ namespace Barotrauma.MapCreatures.Behavior
BranchCreate,
BranchRemove,
BranchDamage,
Infect
Infect,
Remove
}
public enum AttackType
{
Fire,
Explosives,
Other
Other,
CutFromRoot
}
public struct AITarget
{
public string[] Tags;
public Identifier[] Tags;
public int Priority;
public AITarget(XElement element)
public AITarget(ContentXElement element)
{
Tags = element.GetAttributeStringArray("tags", new string[0]);
Tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>())!;
Priority = element.GetAttributeInt("priority", 0);
}
public bool Matches(Item item)
{
foreach (string tag in item.GetTags())
{
foreach (Identifier targetTag in Tags)
{
foreach (string targetTag in Tags)
{
if (tag == targetTag) { return true; }
}
if (item.HasTag(targetTag)) { return true; }
}
return false;
}
}
[Serialize(0.25f, true, "Scale of the branches")]
[Serialize(0.25f, IsPropertySaveable.Yes, "Scale of the branches.")]
public float BaseBranchScale { get; set; }
[Serialize(0.25f, true, "Scale of the flowers")]
[Serialize(0.25f, IsPropertySaveable.Yes, "Scale of the flowers.")]
public float BaseFlowerScale { get; set; }
[Serialize(0.5f, true, "Scale of the leaves")]
[Serialize(0.5f, IsPropertySaveable.Yes, "Scale of the leaves.")]
public float BaseLeafScale { get; set; }
[Serialize(0.33f, true, "Chance for a flower to appear on the branch")]
[Serialize(0.33f, IsPropertySaveable.Yes, "Chance for a flower to appear on a branch.")]
public float FlowerProbability { get; set; }
[Serialize(0.7f, true, "Change for leaves to appear for the branch")]
[Serialize(0.7f, IsPropertySaveable.Yes, "Chance for leaves to appear on a branch.")]
public float LeafProbability { get; set; }
[Serialize(3f, true, "Delay between pulses")]
[Serialize(3f, IsPropertySaveable.Yes, "Delay between pulses.")]
public float PulseDelay { get; set; }
[Serialize(3f, true, "How fast the flower inflates during a pulse")]
[Serialize(3f, IsPropertySaveable.Yes, "How fast the flower inflates during a pulse.")]
public float PulseInflateSpeed { get; set; }
[Serialize(1f, true, "How fast the flower deflates")]
[Serialize(1f, IsPropertySaveable.Yes, "How fast the flower deflates.")]
public float PulseDeflateSpeed { get; set; }
[Serialize(32, true, "How many vines must grow before the plant breaks thru the wall")]
[Serialize(32, IsPropertySaveable.Yes, "How many vines must grow before the plant breaks through the wall.")]
public int BreakthroughPoint { get; set; }
[Serialize(false, true, "Has the plant grown large enough to expose itself")]
[Serialize(false, IsPropertySaveable.Yes, "Has the plant grown large enough to expose itself.")]
public bool HasBrokenThrough { get; set; }
[Serialize(300, true, "How far the ballast flora can detect items")]
[Serialize(300, IsPropertySaveable.Yes, "How far the ballast flora can detect items from.")]
public int Sight { get; set; }
[Serialize(100, true, "How much health the branches have")]
[Serialize(100, IsPropertySaveable.Yes, "How much health the branches have.")]
public int BranchHealth { get; set; }
[Serialize(400, true, "How much health the stem has")]
public int StemHealth { get; set; }
[Serialize(400, IsPropertySaveable.Yes, "How much health the root has.")]
public int RootHealth { get; set; }
[Serialize(300f, true, "How much power the ballast flora takes from junction boxes")]
[Serialize(0.0005f, IsPropertySaveable.Yes, "How fast the root's health regenerates per each grown branch.")]
public float HealthRegenPerBranch { get; set; }
[Serialize(30, IsPropertySaveable.Yes, "How far away from the root branches can regenerate health (in number of branches). The amount of regen decreases lineary further from the root.")]
public int MaxBranchHealthRegenDistance { get; set; }
[Serialize("255,255,255,255", IsPropertySaveable.Yes)]
public Color RootColor { get; set; }
[Serialize(300f, IsPropertySaveable.Yes, "How much power the ballast flora takes from junction boxes.")]
public float PowerConsumptionMin { get; set; }
[Serialize(3000f, true, "How much the power drain spikes")]
[Serialize(3000f, IsPropertySaveable.Yes, "How much the power drain spikes.")]
public float PowerConsumptionMax { get; set; }
[Serialize(10f, true, "How long it takes for power drain to wind down")]
[Serialize(10f, IsPropertySaveable.Yes, "How long it takes for power drain to wind down.")]
public float PowerConsumptionDuration { get; set; }
[Serialize(250f, true, "How much power does it take to accelerate growth")]
[Serialize(250f, IsPropertySaveable.Yes, "How much power does it take to accelerate growth.")]
public float PowerRequirement { get; set; }
[Serialize(5f, true, "Maximum anger, anger increases when the plant gets damaged and increases growth speed")]
[Serialize(5f, IsPropertySaveable.Yes, "Maximum anger, anger increases when the plant gets damaged and increases growth speed.")]
public float MaxAnger { get; set; }
[Serialize(10000f, true, "Maximum power buffer")]
[Serialize(10000f, IsPropertySaveable.Yes, "Maximum power buffer.")]
public float MaxPowerCapacity { get; set; }
[Serialize("", true, "Item prefab that is spawned when threatened")]
public string AttackItemPrefab { get; set; } = "";
[Serialize("", IsPropertySaveable.Yes, "Item prefab that is spawned when threatened.")]
public Identifier AttackItemPrefab { get; set; } = Identifier.Empty;
[Serialize(0.8f, true, "How resistant the ballast flora is to exlposives before it blooms")]
[Serialize(0.8f, IsPropertySaveable.Yes, "How resistant the ballast flora is to explosives before it blooms.")]
public float ExplosionResistance { get; set; }
[Serialize(5f, true, "How much damage is taken from open fires")]
[Serialize(5f, IsPropertySaveable.Yes, "How much damage is taken from open fires.")]
public float FireVulnerability { get; set; }
[Serialize(0.5f, true, "How much resistance against fire is gained while submerged.")]
[Serialize(0.5f, IsPropertySaveable.Yes, "How much resistance against fire is gained while submerged.")]
public float SubmergedWaterResistance { get; set; }
[Serialize(0.8f, true, "What depth the branches will be drawn on")]
[Serialize(0.8f, IsPropertySaveable.Yes, "What depth the branches will be drawn on.")]
public float BranchDepth { get; set; }
[Serialize("", true, "What sound to play when the ballast flora bursts thru walls")]
[Serialize("", IsPropertySaveable.Yes, "What sound to play when the ballast flora bursts through walls.")]
public string BurstSound { get; set; } = "";
private float availablePower;
[Serialize(0f, true, "How much power the ballast flora has stored.")]
[Serialize(0f, IsPropertySaveable.Yes, "How much power the ballast flora has stored.")]
public float AvailablePower
{
get => availablePower;
@@ -222,7 +256,7 @@ namespace Barotrauma.MapCreatures.Behavior
private float anger;
[Serialize(1f, true, "How enraged the flora is, affects how fast it grows.")]
[Serialize(1f, IsPropertySaveable.Yes, "How enraged the flora is, affects how fast it grows.")]
public float Anger
{
get => anger;
@@ -235,13 +269,13 @@ namespace Barotrauma.MapCreatures.Behavior
public BallastFloraPrefab Prefab { get; private set; }
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
public Vector2 Offset;
public readonly List<Item> ClaimedTargets = new List<Item>();
public readonly List<PowerTransfer> ClaimedJunctionBoxes = new List<PowerTransfer>();
public readonly List<PowerContainer> ClaimedBatteries = new List<PowerContainer>();
public readonly HashSet<Item> ClaimedTargets = new HashSet<Item>();
public readonly HashSet<PowerTransfer> ClaimedJunctionBoxes = new HashSet<PowerTransfer>();
public readonly HashSet<PowerContainer> ClaimedBatteries = new HashSet<PowerContainer>();
public readonly Dictionary<Item, int> IgnoredTargets = new Dictionary<Item, int>();
private readonly List<Tuple<UInt16, int>> tempClaimedTargets = new List<Tuple<ushort, int>>();
@@ -252,11 +286,12 @@ namespace Barotrauma.MapCreatures.Behavior
public float PowerConsumptionTimer;
private float defenseCooldown, toxinsCooldown, fireCheckCooldown;
private float damageIndicatorTimer, selfDamageTimer, toxinsTimer;
private float selfDamageTimer, toxinsTimer;
private readonly List<BallastFloraBranch> branchesVulnerableToFire = new List<BallastFloraBranch>();
public readonly List<BallastFloraBranch> Branches = new List<BallastFloraBranch>();
private BallastFloraBranch? root;
private readonly List<Body> bodies = new List<Body>();
public readonly BallastFloraStateMachine StateMachine;
@@ -319,15 +354,15 @@ namespace Barotrauma.MapCreatures.Behavior
SerializableProperties = SerializableProperty.DeserializeProperties(this, prefab.Element);
LoadPrefab(prefab.Element);
StateMachine = new BallastFloraStateMachine(this);
if (firstGrowth) { GenerateStem(); }
if (firstGrowth) { GenerateRoot(); }
_entityList.Add(this);
}
partial void LoadPrefab(XElement element);
partial void LoadPrefab(ContentXElement element);
public void LoadTargets(XElement element)
public void LoadTargets(ContentXElement element)
{
foreach (XElement subElement in element.Elements())
foreach (var subElement in element.Elements())
{
Targets.Add(new AITarget(subElement));
}
@@ -358,6 +393,10 @@ namespace Barotrauma.MapCreatures.Behavior
{
be.Add(new XAttribute("claimed", (int)(branch.ClaimedItem?.ID ?? -1)));
}
if (branch.ParentBranch != null)
{
be.Add(new XAttribute("parentbranch", (int)(branch.ParentBranch?.ID ?? -1)));
}
saveElement.Add(be);
}
@@ -382,7 +421,7 @@ namespace Barotrauma.MapCreatures.Behavior
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
Offset = element.GetAttributeVector2("offset", Vector2.Zero);
foreach (XElement subElement in element.Elements())
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
@@ -412,8 +451,15 @@ namespace Barotrauma.MapCreatures.Behavior
int sides = getInt("sides");
int blockedSides = getInt("blockedsides");
int claimedId = branchElement.GetAttributeInt("claimed", -1);
int parentBranchId = branchElement.GetAttributeInt("parentbranch", -1);
BallastFloraBranch newBranch = new BallastFloraBranch(this, pos, VineTileType.CrossJunction, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafconfig))
BallastFloraBranch? parentBranch = null;
if (parentBranchId > -1)
{
parentBranch = Branches[parentBranchId];
}
BallastFloraBranch newBranch = new BallastFloraBranch(this, parentBranch, pos, VineTileType.CrossJunction, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafconfig))
{
ID = id,
Health = health,
@@ -422,6 +468,7 @@ namespace Barotrauma.MapCreatures.Behavior
BlockedSides = (TileSide) blockedSides,
IsRoot = isRoot
};
if (newBranch.IsRoot) { root = newBranch; }
if (claimedId > -1)
{
@@ -437,6 +484,15 @@ namespace Barotrauma.MapCreatures.Behavior
public void Update(float deltaTime)
{
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
if (Branches.Count == 0)
{
Remove();
return;
}
}
foreach (BallastFloraBranch branch in Branches)
{
branch.UpdateScale(deltaTime);
@@ -446,38 +502,25 @@ namespace Barotrauma.MapCreatures.Behavior
#endif
}
if (damageIndicatorTimer <= 0)
{
foreach (BallastFloraBranch branch in Branches)
{
if (branch.AccumulatedDamage > 0)
{
#if CLIENT
CreateDamageParticle(branch, branch.AccumulatedDamage);
if (GameMain.DebugDraw)
{
var pos = (Parent?.Position ?? Vector2.Zero) + Offset + branch.Position;
GUI.AddMessage($"{(int)branch.AccumulatedDamage}", GUI.Style.Red, pos, Vector2.UnitY * 10.0f, 3f, playSound: false, subId: Parent?.Submarine?.ID ?? -1);
}
#elif SERVER
SendNetworkMessage(this, NetworkHeader.BranchDamage, branch, branch.AccumulatedDamage);
#endif
}
branch.AccumulatedDamage = 0f;
}
damageIndicatorTimer = 1f;
}
damageIndicatorTimer -= deltaTime;
UpdateDamage(deltaTime);
UpdatePowerDrain(deltaTime);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (root != null && HealthRegenPerBranch > 0.0f)
{
float healAmount = Branches.Count(b => !b.IsRoot && !b.IsRootGrowth && !b.DisconnectedFromRoot) * HealthRegenPerBranch;
foreach (BallastFloraBranch branch in Branches)
{
if (branch.Health > branch.MaxHealth * 0.9f || branch.DisconnectedFromRoot) { continue; }
float branchHealAmount = (float)(MaxBranchHealthRegenDistance - branch.BranchDepth) / MaxBranchHealthRegenDistance * healAmount;
if (branchHealAmount <= 0.0f) { continue; }
branch.Health += branchHealAmount;
branch.AccumulatedDamage -= branchHealAmount;
}
}
StateMachine.Update(deltaTime);
if (HasBrokenThrough)
@@ -512,12 +555,12 @@ namespace Barotrauma.MapCreatures.Behavior
// This entire scope is probably very heavy for GC, need to experiment
if (toxinsTimer > 0.1f)
{
if (!string.IsNullOrWhiteSpace(AttackItemPrefab))
if (!AttackItemPrefab.IsEmpty)
{
Dictionary<Hull, List<BallastFloraBranch>> branches = new Dictionary<Hull, List<BallastFloraBranch>>();
foreach (BallastFloraBranch branch in Branches)
{
if (branch.CurrentHull == null || branch.FlowerConfig.Variant < 0) { continue; }
if (branch.CurrentHull == null || branch.FlowerConfig.Variant < 0 || branch.DisconnectedFromRoot) { continue; }
if (branches.TryGetValue(branch.CurrentHull, out List<BallastFloraBranch>? list))
{
@@ -534,11 +577,12 @@ namespace Barotrauma.MapCreatures.Behavior
List<BallastFloraBranch> list = branches[hull];
if (!list.Any(HasAcidEmitter))
{
BallastFloraBranch randomBranch = branches[hull].GetRandom();
BallastFloraBranch randomBranch = branches[hull].GetRandomUnsynced();
randomBranch.SpawningItem = true;
ItemPrefab prefab = ItemPrefab.Find(null, AttackItemPrefab);
Entity.Spawner?.AddToSpawnQueue(prefab, Parent.Position + Offset + randomBranch.Position, Parent.Submarine, onSpawned: item =>
#warning TODO: Parent needs a nullability sanity check
Entity.Spawner?.AddItemToSpawnQueue(prefab, Parent!.Position + Offset + randomBranch.Position, Parent.Submarine, onSpawned: item =>
{
randomBranch.AttackItem = item;
randomBranch.SpawningItem = false;
@@ -563,38 +607,49 @@ namespace Barotrauma.MapCreatures.Behavior
}
}
partial void UpdateDamage(float deltaTime);
private readonly List<BallastFloraBranch> toBeRemoved = new List<BallastFloraBranch>();
private void UpdateSelfDamage(float deltaTime)
{
if (selfDamageTimer <= 0)
{
bool hasRoot = false;
foreach (BallastFloraBranch branch in Branches)
{
if (branch.IsRoot)
{
hasRoot = true;
break;
}
}
if (!hasRoot)
{
Kill();
return;
}
if (!HasBrokenThrough && !CanGrowMore())
{
Branches.ForEachMod(branch =>
{
float maxHealth = branch.IsRoot ? StemHealth : BranchHealth;
float maxHealth = branch.IsRoot ? RootHealth : BranchHealth;
DamageBranch(branch, Rand.Range(1f, maxHealth), AttackType.Other);
});
}
selfDamageTimer = 1f;
}
toBeRemoved.Clear();
foreach (BallastFloraBranch branch in Branches)
{
if (branch.ParentBranch != null && (branch.ParentBranch.DisconnectedFromRoot || branch.ParentBranch.Health <= 0.0f))
{
DamageBranch(branch, deltaTime * MathHelper.Lerp(10.0f, 0.01f, branch.ParentBranch.Health / branch.ParentBranch.MaxHealth), AttackType.CutFromRoot);
}
if (branch.Health <= 0.0f)
{
if (branch.ClaimedItem != null)
{
RemoveClaim(branch.ClaimedItem);
branch.ClaimedItem = null;
}
branch.RemoveTimer -= deltaTime;
if (branch.RemoveTimer <= 0.0f)
{
toBeRemoved.Add(branch);
}
}
}
foreach (BallastFloraBranch branch in toBeRemoved)
{
RemoveBranch(branch);
}
selfDamageTimer -= deltaTime;
}
@@ -675,20 +730,25 @@ namespace Barotrauma.MapCreatures.Behavior
branch.CurrentHull = Hull.FindHull(GetWorldPosition() + branch.Position, Parent, true);
}
private void GenerateStem()
private void GenerateRoot()
{
BallastFloraBranch stem = new BallastFloraBranch(this, Vector2.Zero, VineTileType.Stem, FoliageConfig.EmptyConfig, FoliageConfig.EmptyConfig)
if (root != null)
{
DebugConsole.ThrowError("Error in ballast flora: tried to grow a root even though root has already been created.\n" + Environment.StackTrace);
}
root = new BallastFloraBranch(this, null, Vector2.Zero, VineTileType.Stem, FoliageConfig.EmptyConfig, FoliageConfig.EmptyConfig)
{
BlockedSides = TileSide.Bottom | TileSide.Left | TileSide.Right,
GrowthStep = 1f,
Health = StemHealth,
MaxHealth = StemHealth,
MaxHealth = RootHealth,
Health = RootHealth,
IsRoot = true,
CurrentHull = Parent
};
Branches.Add(stem);
CreateBody(stem);
Branches.Add(root);
CreateBody(root);
}
public float GetGrowthSpeed(float deltaTime)
@@ -704,19 +764,19 @@ namespace Barotrauma.MapCreatures.Behavior
return deltaTime;
}
public bool TryGrowBranch(BallastFloraBranch parent, TileSide side, out List<BallastFloraBranch> result)
public bool TryGrowBranch(BallastFloraBranch parent, TileSide side, out List<BallastFloraBranch> result, bool isRootGrowth = false, Vector2? forcePosition = null)
{
result = new List<BallastFloraBranch>();
if (parent.IsSideBlocked(side)) { return false; }
if (!isRootGrowth && parent.IsSideBlocked(side)) { return false; }
Vector2 pos = parent.AdjacentPositions[side];
Vector2 pos = forcePosition ?? parent.AdjacentPositions[side];
Rectangle rect = VineTile.CreatePlantRect(pos);
if (CollidesWithWorld(rect))
if (CollidesWithWorld(rect, checkOtherBranches: !isRootGrowth))
{
parent.BlockedSides |= side;
parent.FailedGrowthAttempts++;
return false;
return false;
}
FoliageConfig flowerConfig = FoliageConfig.EmptyConfig;
@@ -732,21 +792,22 @@ namespace Barotrauma.MapCreatures.Behavior
leafConfig = FoliageConfig.CreateRandomConfig(leafVariants, 0.5f, 1.0f);
}
BallastFloraBranch newBranch = new BallastFloraBranch(this, pos, VineTileType.CrossJunction, flowerConfig, leafConfig, rect)
BallastFloraBranch newBranch = new BallastFloraBranch(this, parent, pos, VineTileType.CrossJunction, flowerConfig, leafConfig, rect)
{
ID = CreateID(),
MaxHealth = BranchHealth,
Health = BranchHealth,
MaxHealth = BranchHealth
IsRootGrowth = isRootGrowth
};
SetHull(newBranch);
if (newBranch.CurrentHull == null || newBranch.CurrentHull.Submarine != Parent.Submarine)
{
parent.BlockedSides |= side;
if (!isRootGrowth) { parent.BlockedSides |= side; }
parent.FailedGrowthAttempts++;
return false;
}
}
UpdateConnections(newBranch, parent);
@@ -760,12 +821,28 @@ namespace Barotrauma.MapCreatures.Behavior
GrowthWarps--;
}
int rootGrowthCount = Branches.Count(b => b.IsRootGrowth);
if (rootGrowthCount < GetDesiredRootGrowthAmount())
{
if (root != null)
{
Vector2 rootGrowthPos = Rand.Vector(rootGrowthCount * Rand.Range(3.0f, 5.0f));
TryGrowBranch(root, TileSide.None, out List<BallastFloraBranch> newRootGrowth, isRootGrowth: true, forcePosition: rootGrowthPos);
}
}
#if SERVER
SendNetworkMessage(this, NetworkHeader.BranchCreate, newBranch, parent.ID);
#endif
return true;
}
private int GetDesiredRootGrowthAmount()
{
if (root == null) { return 0; }
return MathHelper.Clamp(Branches.Count(b => !b.IsRootGrowth && b.Health > 0) / 20, 3, 30);
}
public bool BranchContainsTarget(BallastFloraBranch branch, Item target)
{
Rectangle worldRect = branch.Rect;
@@ -894,6 +971,14 @@ namespace Barotrauma.MapCreatures.Behavior
public void DamageBranch(BallastFloraBranch branch, float amount, AttackType type, Character? attacker = null)
{
float damage = amount;
if (type != AttackType.Other && type != AttackType.CutFromRoot)
{
branch.DamageVisualizationTimer = 1.0f;
}
if (branch.IsRootGrowth && root != null && root.Health > 0.0f) { return; }
// damage is handled server side currently
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
@@ -922,12 +1007,10 @@ namespace Barotrauma.MapCreatures.Behavior
}
}
branch.AccumulatedDamage += damage;
branch.Health -= damage;
if (type != AttackType.Other)
if (type != AttackType.Other && type != AttackType.CutFromRoot)
{
branch.AccumulatedDamage += damage;
Anger += damage * 0.001f;
}
@@ -935,30 +1018,13 @@ namespace Barotrauma.MapCreatures.Behavior
GameMain.Server?.KarmaManager?.OnBallastFloraDamaged(attacker, damage);
#endif
if (branch.Health < 0)
if (branch.Health <= 0 && type != AttackType.CutFromRoot)
{
RemoveBranch(branch);
if (branch.IsRoot) { Kill(); }
}
}
public void Remove()
{
foreach (Body body in bodies)
{
GameMain.World.Remove(body);
}
Parent.BallastFlora = null;
Branches.Clear();
foreach (Item target in ClaimedTargets)
{
target.Infector = null;
}
_entityList.Remove(this);
}
public void RemoveBranch(BallastFloraBranch branch)
{
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
@@ -969,6 +1035,21 @@ namespace Barotrauma.MapCreatures.Behavior
Branches.Remove(branch);
branch.Removed = true;
bool foundDisconnected = false;
do
{
foundDisconnected = false;
foreach (BallastFloraBranch otherBranch in Branches)
{
if (otherBranch.ParentBranch == null || otherBranch.DisconnectedFromRoot) { continue; }
if (otherBranch.ParentBranch.Removed || otherBranch.ParentBranch.DisconnectedFromRoot)
{
otherBranch.DisconnectedFromRoot = true;
foundDisconnected = true;
}
}
} while (foundDisconnected);
bodies.ForEachMod(body =>
{
if (body.UserData == branch)
@@ -995,11 +1076,21 @@ namespace Barotrauma.MapCreatures.Behavior
});
#if CLIENT
CreateDeathParticle(branch);
CreateDeathParticle(branch, 1.0f);
#endif
if (isClient) { return; }
int rootGrowthCount = Branches.Count(b => b.IsRootGrowth);
if (rootGrowthCount > GetDesiredRootGrowthAmount())
{
var rootGrowth = Branches.LastOrDefault(b => b.IsRootGrowth);
if (rootGrowth != null)
{
RemoveBranch(rootGrowth);
}
}
if (branch.ClaimedItem != null)
{
RemoveClaim(branch.ClaimedItem);
@@ -1050,8 +1141,10 @@ namespace Barotrauma.MapCreatures.Behavior
public void Kill()
{
Branches.ForEachMod(RemoveBranch);
Parent.BallastFlora = null;
foreach (var branch in Branches)
{
branch.DisconnectedFromRoot = true;
}
foreach (Item target in ClaimedTargets)
{
@@ -1059,6 +1152,19 @@ namespace Barotrauma.MapCreatures.Behavior
}
StateMachine?.State?.Exit();
#if SERVER
SendNetworkMessage(this, NetworkHeader.Kill);
#endif
}
public void Remove()
{
Kill();
Branches.ForEachMod(RemoveBranch);
Branches.Clear();
toBeRemoved.Clear();
Parent.BallastFlora = null;
// clean up leftover (can probably be removed)
foreach (Body body in bodies)
@@ -1067,8 +1173,9 @@ namespace Barotrauma.MapCreatures.Behavior
GameMain.World.Remove(body);
}
_entityList.Remove(this);
#if SERVER
SendNetworkMessage(this, NetworkHeader.Kill);
SendNetworkMessage(this, NetworkHeader.Remove);
#endif
}
@@ -1093,9 +1200,9 @@ namespace Barotrauma.MapCreatures.Behavior
private bool CanGrowMore() => Branches.Any(b => b.CanGrowMore());
private bool CollidesWithWorld(Rectangle rect)
private bool CollidesWithWorld(Rectangle rect, bool checkOtherBranches = true)
{
if (Branches.Any(g => g.Rect.Contains(rect))) { return true; }
if (checkOtherBranches && Branches.Any(g => g.Rect.Contains(rect))) { return true; }
Rectangle worldRect = rect;
worldRect.Location = (Parent.Position + Offset).ToPoint() + worldRect.Location;
@@ -4,125 +4,28 @@ using System.Xml.Linq;
namespace Barotrauma
{
class BallastFloraPrefab : IPrefab, IDisposable
class BallastFloraPrefab : Prefab
{
public string OriginalName { get; }
public string Identifier { get; }
public string FilePath { get; }
public XElement Element { get; }
public ContentPackage ContentPackage { get; private set; }
public LocalizedString DisplayName { get; }
public ContentXElement Element { get; }
public bool Disposed;
public static readonly PrefabCollection<BallastFloraPrefab> Prefabs = new PrefabCollection<BallastFloraPrefab>();
private BallastFloraPrefab(XElement element, string filePath, bool isOverride)
public BallastFloraPrefab(ContentXElement element, BallastFloraFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
{
Identifier = element.GetAttributeString("identifier", "");
OriginalName = element.GetAttributeString("name", "");
DisplayName = TextManager.Get(Identifier).Fallback(OriginalName);
Element = element;
FilePath = filePath;
Prefabs.Add(this, isOverride);
}
public static BallastFloraPrefab Find(string idenfitier)
public static BallastFloraPrefab Find(Identifier identifier)
{
return !string.IsNullOrWhiteSpace(idenfitier) ? Prefabs.Find(prefab => prefab.Identifier == idenfitier) : null;
return Prefabs.ContainsKey(identifier) ? Prefabs[identifier] : null;
}
public static void LoadAll(IEnumerable files)
{
DebugConsole.Log("Loading map creature prefabs: ");
foreach (ContentFile file in files) { LoadFromFile(file); }
}
public static void LoadFromFile(ContentFile file)
{
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
var rootElement = doc?.Root;
if (rootElement == null) { return; }
switch (rootElement.Name.ToString().ToLowerInvariant())
{
case "ballastflorabehavior":
{
new BallastFloraPrefab(rootElement, file.Path, false) { ContentPackage = file.ContentPackage };
break;
}
case "ballastflorabehaviors":
{
foreach (var element in rootElement.Elements())
{
if (element.IsOverride())
{
XElement upgradeElement = element.GetChildElement("mapcreature");
if (upgradeElement != null)
{
new BallastFloraPrefab(upgradeElement, file.Path, true) { ContentPackage = file.ContentPackage };
}
else
{
DebugConsole.ThrowError($"Cannot find a map creature element from the children of the override element defined in {file.Path}");
}
}
else
{
if (element.Name.ToString().Equals("mapcreature", StringComparison.OrdinalIgnoreCase))
{
new BallastFloraPrefab(element, file.Path, false) { ContentPackage = file.ContentPackage };
}
}
}
break;
}
case "override":
{
XElement mapCreatures = rootElement.GetChildElement("ballastflorabehaviors");
if (mapCreatures != null)
{
foreach (XElement element in mapCreatures.Elements())
{
new BallastFloraPrefab(element, file.Path, true) { ContentPackage = file.ContentPackage };
}
}
foreach (XElement element in rootElement.GetChildElements("ballastflorabehavior"))
{
new BallastFloraPrefab(element, file.Path, true) { ContentPackage = file.ContentPackage };
}
break;
}
default:
{
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}\n " +
"Valid elements are: \"MapCreature\", \"MapCreatures\" and \"Override\".");
break;
}
}
}
private void Dispose(bool disposing)
{
if (!Disposed)
{
if (disposing)
{
Prefabs.Remove(this);
}
}
Disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public override void Dispose() { }
}
}
@@ -18,7 +18,7 @@ namespace Barotrauma.MapCreatures.Behavior
private bool tryDrown;
private readonly Character attacker;
public DefendWithPumpState(BallastFloraBranch branch, List<Item> items, Character attacker)
public DefendWithPumpState(BallastFloraBranch branch, IEnumerable<Item> items, Character attacker)
{
targetBranch = branch;
this.attacker = attacker;
@@ -59,11 +59,14 @@ namespace Barotrauma.MapCreatures.Behavior
protected virtual void Grow()
{
List<BallastFloraBranch> newTiles = GrowRandomly();
List<BallastFloraBranch> newBranches = GrowRandomly();
#if DEBUG
Behavior.debugSearchLines.Clear();
#endif
if (newTiles.Any(TryScanTargets)) { return; }
foreach (var branch in newBranches)
{
TryScanTargets(branch);
}
}
public void UpdateIgnoredTargets()
@@ -85,23 +88,20 @@ namespace Barotrauma.MapCreatures.Behavior
private List<BallastFloraBranch> GrowRandomly()
{
List<BallastFloraBranch> newBranches = new List<BallastFloraBranch>();
List<BallastFloraBranch> newList = new List<BallastFloraBranch>(Behavior.Branches);
foreach (BallastFloraBranch branch in newList)
{
if (branch.FailedGrowthAttempts > 8 || !branch.CanGrowMore()) { continue; }
List<BallastFloraBranch> availableBranches = Behavior.Branches.Where(b => !b.DisconnectedFromRoot && b.FailedGrowthAttempts <= 8 && b.CanGrowMore()).ToList();
if (availableBranches.Count == 0) { return availableBranches; }
if (Rand.Range(0, Behavior.Branches.Count(tile => tile.CanGrowMore())) != 0) { continue; }
//prefer growing from the branches furthest from the root (ones with the largest branch depth)
var branch = ToolBox.SelectWeightedRandom(availableBranches, b => (float)b.BranchDepth, Rand.RandSync.Unsynced);
TileSide side = branch.GetRandomFreeSide();
TileSide side = branch.GetRandomFreeSide();
if (side == TileSide.None) { return availableBranches; }
if (side == TileSide.None) { continue; }
Behavior.TryGrowBranch(branch, side, out List<BallastFloraBranch> result);
availableBranches.Clear();
availableBranches.Add(branch);
Behavior.TryGrowBranch(branch, side, out List<BallastFloraBranch> result);
newBranches.AddRange(result);
}
return newBranches;
return availableBranches;
}
private Item? ScanForTargets(VineTile branch)
@@ -117,22 +117,22 @@ namespace Barotrauma.MapCreatures.Behavior
int highestPriority = 0;
Item? currentItem = null;
foreach (Item item in Item.ItemList.Where(it => !Behavior.ClaimedTargets.Contains(it)))
foreach (Item item in Item.ItemList)
{
if (item.Submarine != parent.Submarine || Vector2.DistanceSquared(worldPos, item.WorldPosition) > Behavior.Sight * Behavior.Sight) { continue; }
if (Behavior.ClaimedTargets.Contains(item)) { continue; }
if (Behavior.IgnoredTargets.ContainsKey(item)) { continue; }
int priority = 0;
foreach (BallastFloraBehavior.AITarget target in Behavior.Targets)
{
if (!target.Matches(item) || target.Priority <= highestPriority) { continue; }
if (target.Priority <= highestPriority || !target.Matches(item)) { continue; }
priority = target.Priority;
break;
}
if (priority == 0) { continue; }
if (item.Submarine != parent.Submarine || Vector2.Distance(worldPos, item.WorldPosition) > Behavior.Sight) { continue; }
Vector2 itemSimPos = ConvertUnits.ToSimUnits(item.Position);
#if DEBUG
@@ -156,6 +156,7 @@ namespace Barotrauma.MapCreatures.Behavior
{
foreach (BallastFloraBranch existingBranch in Behavior.Branches)
{
if (existingBranch.Health <= 0 || existingBranch.IsRootGrowth) { continue; }
if (Behavior.BranchContainsTarget(existingBranch, currentItem))
{
Behavior.ClaimTarget(currentItem, existingBranch);
@@ -53,7 +53,7 @@ namespace Barotrauma.MapCreatures.Behavior
List<BallastFloraBranch> newList = new List<BallastFloraBranch>(TargetBranches);
foreach (BallastFloraBranch branch in newList)
{
if (branch.FailedGrowthAttempts > 8 || !branch.CanGrowMore()) { continue; }
if (branch.FailedGrowthAttempts > 8 || branch.DisconnectedFromRoot || !branch.CanGrowMore()) { continue; }
// Get what side gets us closest to the target
TileSide side = GetClosestSide(branch, Target.WorldPosition);