Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/Components/Growable.cs
1 #nullable enable
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Xml.Linq;
7 using Microsoft.Xna.Framework;
8 using Microsoft.Xna.Framework.Graphics;
9 
11 {
12  internal class VineSprite
13  {
14  [Serialize("0,0,0,0", IsPropertySaveable.No)]
15  public Rectangle SourceRect { get; private set; }
16 
17  [Serialize("0.5,0.5", IsPropertySaveable.No)]
18  public Vector2 Origin { get; private set; }
19 
20  public Vector2 AbsoluteOrigin;
21 
22  public VineSprite(ContentXElement element)
23  {
24  SerializableProperty.DeserializeProperties(this, element);
25  AbsoluteOrigin = new Vector2(SourceRect.Width * Origin.X, SourceRect.Height * Origin.Y);
26  }
27  }
28 
29  internal partial class Growable
30  {
31  public readonly Dictionary<VineTileType, VineSprite> VineSprites = new Dictionary<VineTileType, VineSprite>();
32  public readonly List<Sprite> FlowerSprites = new List<Sprite>();
33  public readonly List<Sprite> LeafSprites = new List<Sprite>();
34 
35  public Sprite? VineAtlas, DecayAtlas;
36 
37  protected override void RemoveComponentSpecific()
38  {
39  VineAtlas?.Remove();
40  DecayAtlas?.Remove();
41 
42  foreach (Sprite sprite in FlowerSprites)
43  {
44  sprite.Remove();
45  }
46 
47  foreach (Sprite sprite in LeafSprites)
48  {
49  sprite.Remove();
50  }
51  }
52 
53  public void Draw(SpriteBatch spriteBatch, Planter planter, Vector2 offset, float depth)
54  {
55  const float zStep = 0.0001f;
56  float leafDepth = 0f;
57 
58  foreach (VineTile vine in Vines)
59  {
60  leafDepth += zStep;
61  DrawBranch(vine, spriteBatch, planter.Item.DrawPosition + offset, depth, leafDepth);
62  }
63 
64  if (GameMain.DebugDraw)
65  {
66  foreach (Rectangle rect in FailedRectangles)
67  {
68  Rectangle wRect = rect;
69  wRect.Y = -wRect.Y;
70  wRect.Y -= wRect.Height;
71  GUI.DrawRectangle(spriteBatch, wRect, Color.Red);
72  }
73  }
74  }
75 
76  private void DrawBranch(VineTile vine, SpriteBatch spriteBatch, Vector2 position, float depth, float leafDepth)
77  {
78  Vector2 pos = position + vine.Position;
79  pos.Y = -pos.Y;
80 
81  VineSprite vineSprite = VineSprites[vine.Type];
82  Color color = Decayed ? DeadTint : VineTint;
83 
84  float layer1 = depth + 0.01f, // flowers
85  layer2 = depth + 0.02f, // decay atlas
86  layer3 = depth + 0.03f; // branches and leaves
87 
88  float scale = VineScale * vine.VineStep;
89 
90  if (VineAtlas != null && VineAtlas.Loaded)
91  {
92  spriteBatch.Draw(VineAtlas.Texture, pos + vine.offset, vineSprite.SourceRect, color, 0f, vineSprite.AbsoluteOrigin, scale, SpriteEffects.None, layer3);
93  }
94 
95  if (DecayAtlas != null && DecayAtlas.Loaded)
96  {
97  spriteBatch.Draw(DecayAtlas.Texture, pos, vineSprite.SourceRect, vine.HealthColor, 0f, vineSprite.AbsoluteOrigin, scale, SpriteEffects.None, layer2);
98  }
99 
100  if (vine.FlowerConfig.Variant >= 0 && !Decayed)
101  {
102  Sprite flowerSprite = FlowerSprites[vine.FlowerConfig.Variant];
103  flowerSprite.Draw(spriteBatch, pos, FlowerTint, flowerSprite.Origin, scale: BaseFlowerScale * vine.FlowerConfig.Scale * vine.FlowerStep, rotate: vine.FlowerConfig.Rotation, depth: layer1);
104  }
105 
106  if (vine.LeafConfig.Variant >= 0)
107  {
108  Sprite leafSprite = LeafSprites[vine.LeafConfig.Variant];
109  leafSprite.Draw(spriteBatch, pos, Decayed ? DeadTint : LeafTint, leafSprite.Origin, scale: BaseLeafScale * vine.LeafConfig.Scale * vine.FlowerStep, rotate: vine.LeafConfig.Rotation, depth: layer3 + leafDepth);
110  }
111  }
112 
113  partial void LoadVines(ContentXElement element)
114  {
115  ContentPath vineAtlasPath = element.GetAttributeContentPath("vineatlas") ?? ContentPath.Empty;
116  ContentPath decayAtlasPath = element.GetAttributeContentPath("decayatlas") ?? ContentPath.Empty;
117 
118  if (!vineAtlasPath.IsNullOrEmpty())
119  {
120  VineAtlas = new Sprite(vineAtlasPath.Value, Rectangle.Empty);
121  }
122 
123  if (!decayAtlasPath.IsNullOrEmpty())
124  {
125  DecayAtlas = new Sprite(decayAtlasPath.Value, Rectangle.Empty);
126  }
127 
128  foreach (var subElement in element.Elements())
129  {
130  switch (subElement.Name.ToString().ToLowerInvariant())
131  {
132  case "vinesprite":
133  VineTileType type = subElement.GetAttributeEnum("type", VineTileType.Stem);
134  VineSprites.Add(type, new VineSprite(subElement));
135  break;
136  case "flowersprite":
137  FlowerSprites.Add(new Sprite(subElement));
138  break;
139  case "leafsprite":
140  LeafSprites.Add(new Sprite(subElement));
141  break;
142  }
143 
144  flowerVariants = FlowerSprites.Count;
145  leafVariants = LeafSprites.Count;
146  }
147 
148  foreach (VineTileType type in Enum.GetValues(typeof(VineTileType)).Cast<VineTileType>())
149  {
150  if (!VineSprites.ContainsKey(type))
151  {
152  DebugConsole.ThrowError($"Vine sprite missing from {item.Prefab.Identifier}: {type}");
153  }
154  }
155  }
156 
157  private readonly object mutex = new object();
158 
159  public void ClientEventRead(IReadMessage msg, float sendingTime)
160  {
161  Health = msg.ReadRangedSingle(0, MaxHealth, 8);
162  int startOffset = msg.ReadRangedInteger(-1, MaximumVines);
163  if (startOffset > -1)
164  {
165  int vineCount = msg.ReadRangedInteger(0, VineChunkSize);
166  List<VineTile> tiles = new List<VineTile>();
167  for (int i = 0; i < vineCount; i++)
168  {
169  VineTileType vineType = (VineTileType)msg.ReadRangedInteger(0b0000, 0b1111);
170  int flowerConfig = msg.ReadRangedInteger(0, 0xFFF);
171  int leafConfig = msg.ReadRangedInteger(0, 0xFFF);
172  sbyte posX = (sbyte)msg.ReadByte(), posY = (sbyte)msg.ReadByte();
173  Vector2 pos = new Vector2(posX * VineTile.Size, posY * VineTile.Size);
174 
175  tiles.Add(new VineTile(this, pos, vineType, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafConfig)));
176  }
177 
178  // is this even needed??
179  lock (mutex)
180  {
181  for (var i = 0; i < vineCount; i++)
182  {
183  int index = i + startOffset;
184  if (index >= Vines.Count)
185  {
186  Vines.Add(tiles[i]);
187  continue;
188  }
189 
190  VineTile oldVine = Vines[index];
191  VineTile newVine = tiles[i];
192  newVine.GrowthStep = oldVine.GrowthStep;
193  Vines[index] = newVine;
194  }
195  }
196  }
197 
198  UpdateBranchHealth();
199  ResetPlanterSize();
200  }
201 
202  private void ResetPlanterSize()
203  {
204  if (item.ParentInventory is ItemInventory itemInventory && itemInventory.Owner is Item parentItem)
205  {
206  if (parentItem.GetComponent<Planter>() is { } planter)
207  {
208  planter.Item.ResetCachedVisibleSize();
209  }
210  }
211  }
212 
213 #if DEBUG
214  private int seed;
215 
216  // Huge bowl of spaghetti
217  public void CreateDebugHUD(Planter planter, PlantSlot slot)
218  {
219  Vector2 relativeSize = new Vector2(0.3f, 0.6f);
220  GUIMessageBox msgBox = new GUIMessageBox(item.Name, "", new[] { TextManager.Get("applysettingsbutton") }, relativeSize);
221 
222  GUILayoutGroup content = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.85f), msgBox.Content.RectTransform)) { Stretch = true };
223  GUINumberInput seedInput = CreateIntEntry("Random Seed", seed, content.RectTransform);
224  GUINumberInput vineTileSizeInput = CreateIntEntry("Vine Tile Size (Global)", VineTile.Size, content.RectTransform);
225  GUINumberInput[] leafScaleRangeInput = CreateMinMaxEntry("Leaf Scale Range (Global)", new []{ MinLeafScale, MaxLeafScale }, 1.5f, content.RectTransform);
226  GUINumberInput[] flowerScaleRangeInput = CreateMinMaxEntry("Flower Scale Range (Global)", new []{ MinFlowerScale, MaxFlowerScale }, 1.5f, content.RectTransform);
227  GUINumberInput vineCountInput = CreateIntEntry("Vine Count", MaximumVines, content.RectTransform);
228  GUINumberInput vineScaleInput = CreateFloatEntry("Vine Scale", VineScale, content.RectTransform);
229  GUINumberInput flowerInput = CreateIntEntry("Flower Quantity", FlowerQuantity, content.RectTransform);
230  GUINumberInput flowerScaleInput = CreateFloatEntry("Flower Scale", BaseFlowerScale, content.RectTransform);
231  GUINumberInput leafScaleInput = CreateFloatEntry("Leaf Scale", BaseLeafScale, content.RectTransform);
232  GUINumberInput leafProbabilityInput = CreateFloatEntry("Leaf Probability", LeafProbability, content.RectTransform);
233  GUINumberInput[] leafTintInputs = CreateMinMaxEntry("Leaf Tint", new []{ LeafTint.R / 255f, LeafTint.G / 255f, LeafTint.B / 255f }, 1.0f, content.RectTransform);
234  GUINumberInput[] flowerTintInputs = CreateMinMaxEntry("Flower Tint", new []{ FlowerTint.R / 255f, FlowerTint.G / 255f, FlowerTint.B / 255f }, 1.0f, content.RectTransform);
235  GUINumberInput[] vineTintInputs = CreateMinMaxEntry("Branch Tint", new []{ VineTint.R / 255f, VineTint.G / 255f, VineTint.B / 255f }, 1.0f, content.RectTransform);
236 
237  // Apply
238  msgBox.Buttons[0].OnClicked = (button, o) =>
239  {
240  seed = seedInput.IntValue;
241  MaximumVines = vineCountInput.IntValue;
242  FlowerQuantity = flowerInput.IntValue;
243  BaseFlowerScale = flowerScaleInput.FloatValue;
244  VineScale = vineScaleInput.FloatValue;
245  BaseLeafScale = leafScaleInput.FloatValue;
246  LeafProbability = leafProbabilityInput.FloatValue;
247  VineTile.Size = vineTileSizeInput.IntValue;
248 
249  MinFlowerScale = flowerScaleRangeInput[0].FloatValue;
250  MaxFlowerScale = flowerScaleRangeInput[1].FloatValue;
251  MinLeafScale = leafScaleRangeInput[0].FloatValue;
252  MaxLeafScale = leafScaleRangeInput[1].FloatValue;
253 
254  LeafTint = new Color(leafTintInputs[0].FloatValue, leafTintInputs[1].FloatValue, leafTintInputs[2].FloatValue);
255  FlowerTint = new Color(flowerTintInputs[0].FloatValue, flowerTintInputs[1].FloatValue, flowerTintInputs[2].FloatValue);
256  VineTint = new Color(vineTintInputs[0].FloatValue, vineTintInputs[1].FloatValue, vineTintInputs[2].FloatValue);
257 
258  if (FlowerQuantity >= MaximumVines - 1)
259  {
260  vineCountInput.Flash(Color.Red);
261  flowerInput.Flash(Color.Red);
262  return false;
263  }
264 
265  if (MinFlowerScale > MaxFlowerScale)
266  {
267  foreach (GUINumberInput input in flowerScaleRangeInput)
268  {
269  input.Flash(Color.Red);
270  }
271 
272  return false;
273  }
274 
275  if (MinLeafScale > MaxLeafScale)
276  {
277  foreach (GUINumberInput input in leafScaleRangeInput)
278  {
279  input.Flash(Color.Red);
280  }
281 
282  return false;
283  }
284 
285  msgBox.Close();
286 
287  Random random = new Random(seed);
288  Random flowerRandom = new Random(seed);
289  Vines.Clear();
290  GenerateFlowerTiles(flowerRandom);
291  GenerateStem();
292 
293  Decayed = false;
294  FullyGrown = false;
295  while (MaximumVines > Vines.Count)
296  {
297  if (!CanGrowMore())
298  {
299  Decayed = true;
300  break;
301  }
302 
303  TryGenerateBranches(planter, slot, random, flowerRandom);
304  }
305 
306  if (!Decayed)
307  {
308  FullyGrown = true;
309  }
310 
311  foreach (VineTile vineTile in Vines)
312  {
313  vineTile.GrowthStep = 2.0f;
314  }
315 
316  return true;
317  };
318  }
319 
320  private static GUINumberInput CreateIntEntry(string label, int defaultValue, RectTransform parent)
321  {
322  GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true);
323  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label);
324  GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), NumberType.Int) { IntValue = defaultValue };
325  return input;
326  }
327 
328  private static GUINumberInput CreateFloatEntry(string label, float defaultValue, RectTransform parent)
329  {
330  GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true);
331  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label);
332  GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), NumberType.Float) { FloatValue = defaultValue, DecimalsToDisplay = 2 };
333  return input;
334  }
335 
336  private static GUINumberInput[] CreateMinMaxEntry(string label, float[] values, float max, RectTransform parent, float min = 0f)
337  {
338  GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true);
339  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label);
340  GUINumberInput[] inputs = new GUINumberInput[values.Length];
341  for (var i = 0; i < values.Length; i++)
342  {
343  float value = values[i];
344  GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f / values.Length, 1f), layout.RectTransform), NumberType.Float)
345  {
346  FloatValue = value, DecimalsToDisplay = 2,
347  MinValueFloat = min,
348  MaxValueFloat = max
349  };
350  inputs[i] = input;
351  }
352 
353  return inputs;
354  }
355 #endif
356  }
357 }
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
int ReadRangedInteger(int min, int max)
Single ReadRangedSingle(Single min, Single max, int bitCount)
NumberType
Definition: Enums.cs:715