#nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Barotrauma.Networking; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Barotrauma.Items.Components { internal class VineSprite { [Serialize("0,0,0,0", IsPropertySaveable.No)] public Rectangle SourceRect { get; private set; } [Serialize("0.5,0.5", IsPropertySaveable.No)] public Vector2 Origin { get; private set; } public Vector2 AbsoluteOrigin; public VineSprite(ContentXElement element) { SerializableProperty.DeserializeProperties(this, element); AbsoluteOrigin = new Vector2(SourceRect.Width * Origin.X, SourceRect.Height * Origin.Y); } } internal partial class Growable { public readonly Dictionary VineSprites = new Dictionary(); public readonly List FlowerSprites = new List(); public readonly List LeafSprites = new List(); public Sprite? VineAtlas, DecayAtlas; protected override void RemoveComponentSpecific() { VineAtlas?.Remove(); DecayAtlas?.Remove(); foreach (Sprite sprite in FlowerSprites) { sprite.Remove(); } foreach (Sprite sprite in LeafSprites) { sprite.Remove(); } } public void Draw(SpriteBatch spriteBatch, Planter planter, Vector2 offset, float depth) { const float zStep = 0.0001f; float leafDepth = 0f; foreach (VineTile vine in Vines) { leafDepth += zStep; DrawBranch(vine, spriteBatch, planter.Item.DrawPosition + offset, depth, leafDepth); } if (GameMain.DebugDraw) { foreach (Rectangle rect in FailedRectangles) { Rectangle wRect = rect; wRect.Y = -wRect.Y; wRect.Y -= wRect.Height; GUI.DrawRectangle(spriteBatch, wRect, Color.Red); } } } private void DrawBranch(VineTile vine, SpriteBatch spriteBatch, Vector2 position, float depth, float leafDepth) { Vector2 pos = position + vine.Position; pos.Y = -pos.Y; VineSprite vineSprite = VineSprites[vine.Type]; Color color = Decayed ? DeadTint : VineTint; float layer1 = depth + 0.01f, // flowers layer2 = depth + 0.02f, // decay atlas layer3 = depth + 0.03f; // branches and leaves float scale = VineScale * vine.VineStep; if (VineAtlas != null && VineAtlas.Loaded) { spriteBatch.Draw(VineAtlas.Texture, pos + vine.offset, vineSprite.SourceRect, color, 0f, vineSprite.AbsoluteOrigin, scale, SpriteEffects.None, layer3); } if (DecayAtlas != null && DecayAtlas.Loaded) { spriteBatch.Draw(DecayAtlas.Texture, pos, vineSprite.SourceRect, vine.HealthColor, 0f, vineSprite.AbsoluteOrigin, scale, SpriteEffects.None, layer2); } if (vine.FlowerConfig.Variant >= 0 && !Decayed) { Sprite flowerSprite = FlowerSprites[vine.FlowerConfig.Variant]; flowerSprite.Draw(spriteBatch, pos, FlowerTint, flowerSprite.Origin, scale: BaseFlowerScale * vine.FlowerConfig.Scale * vine.FlowerStep, rotate: vine.FlowerConfig.Rotation, depth: layer1); } if (vine.LeafConfig.Variant >= 0) { Sprite leafSprite = LeafSprites[vine.LeafConfig.Variant]; leafSprite.Draw(spriteBatch, pos, Decayed ? DeadTint : LeafTint, leafSprite.Origin, scale: BaseLeafScale * vine.LeafConfig.Scale * vine.FlowerStep, rotate: vine.LeafConfig.Rotation, depth: layer3 + leafDepth); } } partial void LoadVines(ContentXElement element) { ContentPath vineAtlasPath = element.GetAttributeContentPath("vineatlas") ?? ContentPath.Empty; ContentPath decayAtlasPath = element.GetAttributeContentPath("decayatlas") ?? ContentPath.Empty; if (!vineAtlasPath.IsNullOrEmpty()) { VineAtlas = new Sprite(vineAtlasPath.Value, Rectangle.Empty); } if (!decayAtlasPath.IsNullOrEmpty()) { DecayAtlas = new Sprite(decayAtlasPath.Value, Rectangle.Empty); } foreach (var subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "vinesprite": VineTileType type = subElement.GetAttributeEnum("type", VineTileType.Stem); VineSprites.Add(type, new VineSprite(subElement)); break; case "flowersprite": FlowerSprites.Add(new Sprite(subElement)); break; case "leafsprite": LeafSprites.Add(new Sprite(subElement)); break; } flowerVariants = FlowerSprites.Count; leafVariants = LeafSprites.Count; } foreach (VineTileType type in Enum.GetValues(typeof(VineTileType)).Cast()) { if (!VineSprites.ContainsKey(type)) { DebugConsole.ThrowError($"Vine sprite missing from {item.Prefab.Identifier}: {type}"); } } } private readonly object mutex = new object(); public void ClientEventRead(IReadMessage msg, float sendingTime) { Health = msg.ReadRangedSingle(0, MaxWater, 8); int startOffset = msg.ReadRangedInteger(-1, MaximumVines); if (startOffset > -1) { int vineCount = msg.ReadRangedInteger(0, VineChunkSize); List tiles = new List(); for (int i = 0; i < vineCount; i++) { VineTileType vineType = (VineTileType)msg.ReadRangedInteger(0b0000, 0b1111); int flowerConfig = msg.ReadRangedInteger(0, 0xFFF); int leafConfig = msg.ReadRangedInteger(0, 0xFFF); sbyte posX = (sbyte)msg.ReadByte(), posY = (sbyte)msg.ReadByte(); Vector2 pos = new Vector2(posX * VineTile.Size, posY * VineTile.Size); tiles.Add(new VineTile(this, pos, vineType, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafConfig))); } // is this even needed?? lock (mutex) { for (var i = 0; i < vineCount; i++) { int index = i + startOffset; if (index >= Vines.Count) { Vines.Add(tiles[i]); continue; } VineTile oldVine = Vines[index]; VineTile newVine = tiles[i]; newVine.GrowthStep = oldVine.GrowthStep; Vines[index] = newVine; } } } UpdateBranchHealth(); ResetPlanterSize(); } private void ResetPlanterSize() { if (item.ParentInventory is ItemInventory itemInventory && itemInventory.Owner is Item parentItem) { if (parentItem.GetComponent() is { } planter) { planter.Item.ResetCachedVisibleSize(); } } } #if DEBUG private int seed; // Huge bowl of spaghetti public void CreateDebugHUD(Planter planter, PlantSlot slot) { Vector2 relativeSize = new Vector2(0.3f, 0.6f); GUIMessageBox msgBox = new GUIMessageBox(item.Name, "", new[] { TextManager.Get("applysettingsbutton") }, relativeSize); GUILayoutGroup content = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.85f), msgBox.Content.RectTransform)) { Stretch = true }; GUINumberInput seedInput = CreateIntEntry("Random Seed", seed, content.RectTransform); GUINumberInput vineTileSizeInput = CreateIntEntry("Vine Tile Size (Global)", VineTile.Size, content.RectTransform); GUINumberInput[] leafScaleRangeInput = CreateMinMaxEntry("Leaf Scale Range (Global)", new []{ MinLeafScale, MaxLeafScale }, 1.5f, content.RectTransform); GUINumberInput[] flowerScaleRangeInput = CreateMinMaxEntry("Flower Scale Range (Global)", new []{ MinFlowerScale, MaxFlowerScale }, 1.5f, content.RectTransform); GUINumberInput vineCountInput = CreateIntEntry("Vine Count", MaximumVines, content.RectTransform); GUINumberInput vineScaleInput = CreateFloatEntry("Vine Scale", VineScale, content.RectTransform); GUINumberInput flowerInput = CreateIntEntry("Flower Quantity", FlowerQuantity, content.RectTransform); GUINumberInput flowerScaleInput = CreateFloatEntry("Flower Scale", BaseFlowerScale, content.RectTransform); GUINumberInput leafScaleInput = CreateFloatEntry("Leaf Scale", BaseLeafScale, content.RectTransform); GUINumberInput leafProbabilityInput = CreateFloatEntry("Leaf Probability", LeafProbability, content.RectTransform); GUINumberInput[] leafTintInputs = CreateMinMaxEntry("Leaf Tint", new []{ LeafTint.R / 255f, LeafTint.G / 255f, LeafTint.B / 255f }, 1.0f, content.RectTransform); GUINumberInput[] flowerTintInputs = CreateMinMaxEntry("Flower Tint", new []{ FlowerTint.R / 255f, FlowerTint.G / 255f, FlowerTint.B / 255f }, 1.0f, content.RectTransform); GUINumberInput[] vineTintInputs = CreateMinMaxEntry("Branch Tint", new []{ VineTint.R / 255f, VineTint.G / 255f, VineTint.B / 255f }, 1.0f, content.RectTransform); // Apply msgBox.Buttons[0].OnClicked = (button, o) => { seed = seedInput.IntValue; MaximumVines = vineCountInput.IntValue; FlowerQuantity = flowerInput.IntValue; BaseFlowerScale = flowerScaleInput.FloatValue; VineScale = vineScaleInput.FloatValue; BaseLeafScale = leafScaleInput.FloatValue; LeafProbability = leafProbabilityInput.FloatValue; VineTile.Size = vineTileSizeInput.IntValue; MinFlowerScale = flowerScaleRangeInput[0].FloatValue; MaxFlowerScale = flowerScaleRangeInput[1].FloatValue; MinLeafScale = leafScaleRangeInput[0].FloatValue; MaxLeafScale = leafScaleRangeInput[1].FloatValue; LeafTint = new Color(leafTintInputs[0].FloatValue, leafTintInputs[1].FloatValue, leafTintInputs[2].FloatValue); FlowerTint = new Color(flowerTintInputs[0].FloatValue, flowerTintInputs[1].FloatValue, flowerTintInputs[2].FloatValue); VineTint = new Color(vineTintInputs[0].FloatValue, vineTintInputs[1].FloatValue, vineTintInputs[2].FloatValue); if (FlowerQuantity >= MaximumVines - 1) { vineCountInput.Flash(Color.Red); flowerInput.Flash(Color.Red); return false; } if (MinFlowerScale > MaxFlowerScale) { foreach (GUINumberInput input in flowerScaleRangeInput) { input.Flash(Color.Red); } return false; } if (MinLeafScale > MaxLeafScale) { foreach (GUINumberInput input in leafScaleRangeInput) { input.Flash(Color.Red); } return false; } msgBox.Close(); Random random = new Random(seed); Random flowerRandom = new Random(seed); Vines.Clear(); GenerateFlowerTiles(flowerRandom); GenerateStem(); Decayed = false; FullyGrown = false; while (MaximumVines > Vines.Count) { if (!CanGrowMore()) { Decayed = true; break; } TryGenerateBranches(planter, slot, random, flowerRandom); } if (!Decayed) { FullyGrown = true; } foreach (VineTile vineTile in Vines) { vineTile.GrowthStep = 2.0f; } return true; }; } private static GUINumberInput CreateIntEntry(string label, int defaultValue, RectTransform parent) { GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label); GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), NumberType.Int) { IntValue = defaultValue }; return input; } private static GUINumberInput CreateFloatEntry(string label, float defaultValue, RectTransform parent) { GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label); GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), NumberType.Float) { FloatValue = defaultValue, DecimalsToDisplay = 2 }; return input; } private static GUINumberInput[] CreateMinMaxEntry(string label, float[] values, float max, RectTransform parent, float min = 0f) { GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label); GUINumberInput[] inputs = new GUINumberInput[values.Length]; for (var i = 0; i < values.Length; i++) { float value = values[i]; GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f / values.Length, 1f), layout.RectTransform), NumberType.Float) { FloatValue = value, DecimalsToDisplay = 2, MinValueFloat = min, MaxValueFloat = max }; inputs[i] = input; } return inputs; } #endif } }