408 lines
18 KiB
C#
408 lines
18 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
using Barotrauma.Items.Components;
|
|
using Barotrauma.Networking;
|
|
using Barotrauma.Particles;
|
|
using Barotrauma.Sounds;
|
|
using FarseerPhysics;
|
|
using FarseerPhysics.Dynamics;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
|
|
namespace Barotrauma.MapCreatures.Behavior
|
|
{
|
|
partial class BallastFloraBehavior
|
|
{
|
|
public Sprite? branchAtlas, decayAtlas;
|
|
public readonly Dictionary<VineTileType, VineSprite> BranchSprites = new Dictionary<VineTileType, VineSprite>();
|
|
public readonly List<Sprite> FlowerSprites = new List<Sprite>(), DamagedFlowerSprites = new List<Sprite>();
|
|
public readonly List<Sprite> HiddenFlowerSprites = new List<Sprite>();
|
|
public readonly List<Sprite> LeafSprites = new List<Sprite>(), DamagedLeafSprites = new List<Sprite>();
|
|
|
|
public readonly List<ParticleEmitter> DamageParticles = new List<ParticleEmitter>();
|
|
public readonly List<ParticleEmitter> DeathParticles = new List<ParticleEmitter>();
|
|
|
|
public static bool AlwaysShowBallastFloraSprite = false;
|
|
|
|
partial void LoadPrefab(ContentXElement element)
|
|
{
|
|
if (element.GetAttributeContentPath("branchatlas") is { } branchAtlasPath)
|
|
{
|
|
branchAtlas = new Sprite(branchAtlasPath.Value, Rectangle.Empty);
|
|
}
|
|
|
|
if (element.GetAttributeContentPath("decayatlas") is { } decayAtlasPath)
|
|
{
|
|
decayAtlas = new Sprite(decayAtlasPath.Value, Rectangle.Empty);
|
|
}
|
|
|
|
foreach (var subElement in element.Elements())
|
|
{
|
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "branchsprite":
|
|
var type = subElement.GetAttributeEnum("type", VineTileType.Stem);
|
|
BranchSprites.Add(type, new VineSprite(subElement));
|
|
break;
|
|
case "flowersprite":
|
|
FlowerSprites.Add(new Sprite(subElement));
|
|
break;
|
|
case "damagedflowersprite":
|
|
DamagedFlowerSprites.Add(new Sprite(subElement));
|
|
break;
|
|
case "hiddenflowersprite":
|
|
HiddenFlowerSprites.Add(new Sprite(subElement));
|
|
break;
|
|
case "leafsprite":
|
|
LeafSprites.Add(new Sprite(subElement));
|
|
break;
|
|
case "damagedleafsprite":
|
|
DamagedLeafSprites.Add(new Sprite(subElement));
|
|
break;
|
|
case "damageparticle":
|
|
DamageParticles.Add(new ParticleEmitter(subElement));
|
|
break;
|
|
case "deathparticle":
|
|
DeathParticles.Add(new ParticleEmitter(subElement));
|
|
break;
|
|
case "targets":
|
|
LoadTargets(subElement);
|
|
break;
|
|
}
|
|
|
|
flowerVariants = FlowerSprites.Count;
|
|
leafVariants = LeafSprites.Count;
|
|
}
|
|
}
|
|
|
|
private void CreateShapnel(Vector2 pos)
|
|
{
|
|
float particleAmount = Rand.Range(16, 32);
|
|
for (int i = 0; i < particleAmount; i++)
|
|
{
|
|
GameMain.ParticleManager.CreateParticle("shrapnel", pos, Rand.Vector(Rand.Range(0f, 250.0f)), Rand.Range(0f, 360.0f));
|
|
}
|
|
}
|
|
|
|
partial void UpdateDamage(float deltaTime)
|
|
{
|
|
foreach (BallastFloraBranch branch in Branches)
|
|
{
|
|
if (branch.IsRoot && isDead) { continue; }
|
|
|
|
if (branch.AccumulatedDamage > 0)
|
|
{
|
|
CreateDamageParticle(branch, branch.AccumulatedDamage);
|
|
|
|
if (GameMain.DebugDraw)
|
|
{
|
|
var pos = (Parent?.Position ?? Vector2.Zero) + Offset + branch.Position;
|
|
GUI.AddMessage($"{(int)branch.AccumulatedDamage}", GUIStyle.Red, pos, Vector2.UnitY * 10.0f, 3f, playSound: false, subId: Parent?.Submarine?.ID ?? -1);
|
|
}
|
|
}
|
|
if (Character.Controlled != null && Character.Controlled.CurrentHull == branch.CurrentHull && branch.IsRoot &&
|
|
(branch.AccumulatedDamage > 0.0f || branch.AccumulatedDamage < -0.1f))
|
|
{
|
|
Character.Controlled.UpdateHUDProgressBar(this,
|
|
GetWorldPosition() + branch.Position,
|
|
branch.Health / branch.MaxHealth,
|
|
emptyColor: GUIStyle.HealthBarColorLow,
|
|
fullColor: GUIStyle.HealthBarColorHigh,
|
|
textTag: Prefab.DisplayName.Value);
|
|
}
|
|
branch.AccumulatedDamage = 0f;
|
|
if (branch.DamageVisualizationTimer > 0.0f)
|
|
{
|
|
branch.DamageVisualizationTimer -= deltaTime;
|
|
float t1 = (float)Timing.TotalTime * 0.2f + branch.Position.X / 100.0f;
|
|
float t2 = (float)Timing.TotalTime * 0.5f + branch.Position.Y / 100.0f;
|
|
branch.ShakeAmount = new Vector2(
|
|
PerlinNoise.GetPerlin(t1, t2) - 0.5f,
|
|
PerlinNoise.GetPerlin(t2, t1) - 0.5f) * 10.0f * branch.DamageVisualizationTimer;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CreateDamageParticle(BallastFloraBranch branch, float deltaTime)
|
|
{
|
|
Vector2 pos = GetWorldPosition() + branch.Position;
|
|
foreach (var particleEmitter in DamageParticles)
|
|
{
|
|
particleEmitter.Emit(deltaTime, pos, branch.CurrentHull);
|
|
}
|
|
}
|
|
|
|
private void CreateDeathParticle(BallastFloraBranch branch, float deltaTime)
|
|
{
|
|
Vector2 pos = GetWorldPosition() + branch.Position;
|
|
foreach (var particleEmitter in DeathParticles)
|
|
{
|
|
particleEmitter.Emit(deltaTime, pos, branch.CurrentHull);
|
|
}
|
|
}
|
|
|
|
public void Draw(SpriteBatch spriteBatch)
|
|
{
|
|
const float zStep = 0.000001f;
|
|
float leafDepth = zStep;
|
|
float flowerDepth = zStep;
|
|
|
|
if (GameMain.DebugDraw)
|
|
{
|
|
foreach (Body body in bodies)
|
|
{
|
|
Vector2 pos = Parent.Submarine.DrawPosition + ConvertUnits.ToDisplayUnits(body.Position);
|
|
pos.Y = -pos.Y;
|
|
GUI.DrawRectangle(spriteBatch, pos, 32f, 32f, 0f, body.UserData is BallastFloraBranch { IsRoot: true } ? Color.Magenta : Color.Cyan, 0.1f, thickness: 1);
|
|
}
|
|
|
|
foreach (var (key, steps) in IgnoredTargets)
|
|
{
|
|
string label = $"Ignored \"{key.Name}\" for {steps} steps";
|
|
var (sizeX, sizeY) = GUIStyle.SubHeadingFont.MeasureString(label);
|
|
Vector2 targetPos = key.WorldPosition;
|
|
targetPos.Y = -targetPos.Y;
|
|
GUI.DrawString(spriteBatch, targetPos - new Vector2(sizeX / 2f, sizeY), label, GUIStyle.Red, font: GUIStyle.SubHeadingFont);
|
|
}
|
|
}
|
|
|
|
foreach (BallastFloraBranch branch in Branches)
|
|
{
|
|
Vector2 pos = Parent.DrawPosition + Offset + branch.Position + branch.ShakeAmount;
|
|
pos.Y = -pos.Y;
|
|
|
|
float depth = branch.IsRootGrowth ? 0.2f : BranchDepth;
|
|
|
|
float layer1 = depth + 0.01f,
|
|
layer2 = depth + 0.02f,
|
|
layer3 = depth + 0.03f;
|
|
|
|
VineSprite branchSprite = BranchSprites[branch.Type];
|
|
|
|
Color branchColor = (branch.IsRoot || branch.IsRootGrowth) ? RootColor : Color.White;
|
|
|
|
|
|
if (GameMain.DebugDraw)
|
|
{
|
|
if (branch.DisconnectedFromRoot && branch.ParentBranch == null)
|
|
{
|
|
branchColor = Color.Yellow;
|
|
}
|
|
else if (branch.DisconnectedFromRoot)
|
|
{
|
|
branchColor = Color.Cyan;
|
|
}
|
|
else if (branch.ParentBranch == null)
|
|
{
|
|
branchColor = Color.Magenta;
|
|
}
|
|
#if DEBUG
|
|
Vector2 basePos = Parent.WorldPosition;
|
|
foreach (var (from, to) in debugSearchLines)
|
|
{
|
|
Vector2 pos1 = basePos - from;
|
|
pos1.Y = -pos1.Y;
|
|
Vector2 pos2 = basePos - to;
|
|
pos2.Y = -pos2.Y;
|
|
GUI.DrawLine(spriteBatch, pos1, pos2, GUIStyle.Yellow * 0.8f, width: 4);
|
|
}
|
|
if (branch.ParentBranch != null)
|
|
{
|
|
Vector2 pos2 = Parent.DrawPosition + Offset + branch.ParentBranch.Position;
|
|
pos2.Y = -pos2.Y;
|
|
GUI.DrawLine(spriteBatch, pos, pos2, GUIStyle.Green * 0.8f, width: 3);
|
|
}
|
|
#endif
|
|
|
|
string label = "";
|
|
|
|
if (branch == Branches[^1])
|
|
{
|
|
label += $"Current State: {StateMachine.State?.GetType().Name ?? "null!"}\n";
|
|
}
|
|
|
|
if (StateMachine.State is GrowToTargetState targetState)
|
|
{
|
|
if (targetState.TargetBranches.Contains(branch))
|
|
{
|
|
GUI.DrawRectangle(spriteBatch, pos, branch.Rect.Width, branch.Rect.Height, 0f, Color.Red, thickness: 4);
|
|
}
|
|
|
|
if (targetState.TargetBranches[^1] == branch)
|
|
{
|
|
label += $"Target: {targetState.Target.Name}\n";
|
|
|
|
Vector2 targetPos = targetState.Target.WorldPosition;
|
|
targetPos.Y = -targetPos.Y;
|
|
GUI.DrawLine(spriteBatch, pos, targetPos, Color.Red, width: 4);
|
|
}
|
|
}
|
|
|
|
var (sizeX, sizeY) = GUIStyle.SubHeadingFont.MeasureString(label);
|
|
GUI.DrawString(spriteBatch, pos - new Vector2(sizeX / 2f, branch.Rect.Height + sizeY), label, Color.White, font: GUIStyle.SubHeadingFont);
|
|
}
|
|
|
|
bool isDamaged = branch.Health < branch.MaxHealth;
|
|
|
|
if (HasBrokenThrough)
|
|
{
|
|
if (branchAtlas != null && branchAtlas.Loaded)
|
|
{
|
|
spriteBatch.Draw(branchAtlas.Texture, pos + branch.offset, branchSprite.SourceRect, branchColor, 0f, branchSprite.AbsoluteOrigin, BaseBranchScale * branch.VineStep, SpriteEffects.None, layer2);
|
|
}
|
|
|
|
if (decayAtlas != null && isDamaged && decayAtlas.Loaded)
|
|
{
|
|
spriteBatch.Draw(decayAtlas.Texture, pos + branch.offset, branchSprite.SourceRect, branch.HealthColor, 0f, branchSprite.AbsoluteOrigin, BaseBranchScale * branch.VineStep, SpriteEffects.None, layer2 - zStep);
|
|
}
|
|
}
|
|
|
|
if (branch.FlowerConfig.Variant >= 0)
|
|
{
|
|
int variant = branch.FlowerConfig.Variant;
|
|
Sprite flowerSprite = HasBrokenThrough ? FlowerSprites[variant] : HiddenFlowerSprites[variant];
|
|
float flowerScale = BaseFlowerScale * branch.FlowerConfig.Scale * branch.FlowerStep;
|
|
|
|
if (HasBrokenThrough) { flowerScale *= branch.Pulse; }
|
|
|
|
flowerSprite.Draw(spriteBatch, pos, branchColor, flowerSprite.Origin, scale: flowerScale, rotate: branch.FlowerConfig.Rotation, depth: layer1 - flowerDepth);
|
|
if (isDamaged && HasBrokenThrough && DamagedFlowerSprites.Count > variant)
|
|
{
|
|
DamagedFlowerSprites[variant].Draw(spriteBatch, pos, branch.HealthColor, flowerSprite.Origin, scale: flowerScale, rotate: branch.FlowerConfig.Rotation, depth: layer1 - flowerDepth - zStep);
|
|
}
|
|
flowerDepth -= zStep;
|
|
if (flowerDepth > 0.01f)
|
|
{
|
|
flowerDepth = zStep;
|
|
}
|
|
}
|
|
|
|
if (branch.LeafConfig.Variant >= 0 && HasBrokenThrough)
|
|
{
|
|
int variant = branch.LeafConfig.Variant;
|
|
Sprite leafSprite = LeafSprites[variant];
|
|
leafSprite.Draw(spriteBatch, pos, branchColor, leafSprite.Origin, scale: BaseLeafScale * branch.LeafConfig.Scale * branch.FlowerStep, rotate: branch.LeafConfig.Rotation, depth: layer3 + leafDepth);
|
|
if (isDamaged && DamagedLeafSprites.Count > variant)
|
|
{
|
|
DamagedLeafSprites[variant].Draw(spriteBatch, pos, branch.HealthColor, leafSprite.Origin, scale: BaseLeafScale * branch.LeafConfig.Scale * branch.FlowerStep, rotate: branch.LeafConfig.Rotation, depth: layer3 + leafDepth - zStep);
|
|
}
|
|
leafDepth += zStep;
|
|
if (leafDepth > 0.01f)
|
|
{
|
|
flowerDepth = zStep;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void ClientRead(IReadMessage msg, NetworkHeader header)
|
|
{
|
|
switch (header)
|
|
{
|
|
case NetworkHeader.Infect:
|
|
int infectBranch = -1;
|
|
ushort itemId = msg.ReadUInt16();
|
|
bool infect = msg.ReadBoolean();
|
|
if (infect)
|
|
{
|
|
infectBranch = msg.ReadInt32();
|
|
}
|
|
|
|
Entity? entity = Entity.FindEntityByID(itemId);
|
|
if (entity is Item item)
|
|
{
|
|
if (infect)
|
|
{
|
|
ClaimTarget(item, Branches.FirstOrDefault(b => b.ID == infectBranch));
|
|
}
|
|
else
|
|
{
|
|
RemoveClaim(item);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.AddWarning($"Received Infect.{infect} Network Header with invalid item ID: {itemId}, which belongs to {entity?.ToString() ?? "null!"}");
|
|
}
|
|
break;
|
|
case NetworkHeader.BranchCreate:
|
|
int parentId = msg.ReadInt32();
|
|
BallastFloraBranch branch = ReadBranch(msg);
|
|
BallastFloraBranch? parent = Branches.FirstOrDefault(b => b.ID == parentId);
|
|
|
|
if (parent == null)
|
|
{
|
|
DebugConsole.AddWarning($"Received BranchCreate with an invalid parent ID: {parentId}, Maximum ID is {Branches.Max(b => b.ID)}");
|
|
}
|
|
|
|
UpdateConnections(branch, parent);
|
|
Branches.Add(branch);
|
|
OnBranchGrowthSuccess(branch);
|
|
break;
|
|
case NetworkHeader.BranchRemove:
|
|
|
|
int removedBranchId = msg.ReadInt32();
|
|
BallastFloraBranch? removedBranch = Branches.FirstOrDefault(b => b.ID == removedBranchId);
|
|
if (removedBranch != null)
|
|
{
|
|
RemoveBranch(removedBranch);
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.AddWarning($"Received BranchRemove for a branch that doesn't exist. ID: {removedBranchId}, Maximum ID is {Branches.Max(b => b.ID)}");
|
|
}
|
|
|
|
break;
|
|
case NetworkHeader.BranchDamage:
|
|
int damageBranchId = msg.ReadInt32();
|
|
float health = msg.ReadSingle();
|
|
BallastFloraBranch? damagedBranch = Branches.FirstOrDefault(b => b.ID == damageBranchId);
|
|
if (damagedBranch != null)
|
|
{
|
|
damagedBranch.Health = health;
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.AddWarning($"Received BranchDamage for a branch that doesn't exist. ID: {damageBranchId}, Maximum ID is {Branches.Max(b => b.ID)}");
|
|
}
|
|
break;
|
|
case NetworkHeader.Kill:
|
|
Kill();
|
|
break;
|
|
case NetworkHeader.Remove:
|
|
Remove();
|
|
break;
|
|
}
|
|
|
|
PowerConsumptionTimer = msg.ReadSingle();
|
|
}
|
|
|
|
private BallastFloraBranch ReadBranch(IReadMessage msg)
|
|
{
|
|
int id = msg.ReadInt32();
|
|
bool isRootGrowth = msg.ReadBoolean();
|
|
byte type = (byte)msg.ReadRangedInteger(0b0000, 0b1111);
|
|
byte sides = (byte)msg.ReadRangedInteger(0b0000, 0b1111);
|
|
int flowerConfig = msg.ReadRangedInteger(0, 0xFFF);
|
|
int leafConfig = msg.ReadRangedInteger(0, 0xFFF);
|
|
int maxHealth = msg.ReadUInt16();
|
|
int posX = msg.ReadInt32(), posY = msg.ReadInt32();
|
|
int parentBranchIndex = msg.ReadInt32();
|
|
Vector2 pos = new Vector2(posX * VineTile.Size, posY * VineTile.Size);
|
|
|
|
BallastFloraBranch? parentBranch = parentBranchIndex < 0 || parentBranchIndex >= Branches.Count ? null : Branches[parentBranchIndex];
|
|
|
|
return new BallastFloraBranch(this, parentBranch, pos, (VineTileType)type, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafConfig))
|
|
{
|
|
ID = id,
|
|
MaxHealth = maxHealth,
|
|
Sides = (TileSide) sides,
|
|
IsRootGrowth = isRootGrowth
|
|
};
|
|
}
|
|
}
|
|
} |