Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Components/Planter.cs
1 #nullable enable
2 using System;
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.Linq;
6 using Microsoft.Xna.Framework;
7 
9 {
10  internal enum PlantItemType
11  {
12  Seed,
13  Fertilizer
14  }
15 
16  internal readonly struct SuitablePlantItem
17  {
18  public readonly Item? Item;
19  public readonly PlantItemType Type;
20  public readonly string ProgressBarMessage;
21 
22  public SuitablePlantItem(Item item, PlantItemType type, string progressBarMessage)
23  {
24  Item = item;
25  Type = type;
26  ProgressBarMessage = progressBarMessage;
27  }
28 
29  public bool IsNull() => Item == null;
30  }
31 
32  internal struct PlantSlot
33  {
34  public Vector2 Offset;
35  public float Size;
36 
37  public PlantSlot(ContentXElement element)
38  {
39  Offset = element.GetAttributeVector2("offset", Vector2.Zero);
40  Size = element.GetAttributeFloat("size", 0.5f);
41  }
42 
43  public PlantSlot(Vector2 offset, float size)
44  {
45  Offset = offset;
46  Size = size;
47  }
48  }
49 
50  internal partial class Planter : Pickable, IDrawableComponent
51  {
52  public static readonly PlantSlot NullSlot = new PlantSlot();
53  public readonly Dictionary<int, PlantSlot> PlantSlots = new Dictionary<int, PlantSlot>();
54 
55  private static readonly SuitablePlantItem NullItem = new SuitablePlantItem();
56  private const string MsgFertilizer = "ItemMsgAddFertilizer";
57  private const string MsgSeed = "ItemMsgPlantSeed";
58  private const string MsgHarvest = "ItemMsgHarvest";
59  private const string MsgUprooting = "progressbar.uprooting";
60  private const string MsgFertilizing = "progressbar.fertilizing";
61  private const string MsgPlanting = "progressbar.planting";
62  public static float GrowthTickDelay = 1f; // 1 second
63 
64  private float fertilizer;
65 
66  [Serialize(0f, IsPropertySaveable.Yes, "How much fertilizer the planter has.")]
67  public float Fertilizer
68  {
69  get => fertilizer;
70  set => fertilizer = Math.Clamp(value, 0, FertilizerCapacity);
71  }
72 
73  [Serialize(100f, IsPropertySaveable.Yes, "How much fertilizer can the planter hold.")]
74  public float FertilizerCapacity { get; set; }
75 
76  public Growable?[] GrowableSeeds = new Growable?[0];
77 
78  private readonly List<RelatedItem> SuitableFertilizer = new List<RelatedItem>();
79  private readonly List<RelatedItem> SuitableSeeds = new List<RelatedItem>();
80  private ItemContainer? container;
81  private float growthTickTimer;
82 
83  private List<LightComponent>? lightComponents;
84 
85  // We don't want the seeds to be transferred to a new submarine as seeds are not supposed to leave the container after they have been planted.
86  public override bool DontTransferInventoryBetweenSubs => true;
87 
88  public Planter(Item item, ContentXElement element) : base(item, element)
89  {
90  canBePicked = true;
91  SerializableProperty.DeserializeProperties(this, element);
92  foreach (var subElement in element.Elements())
93  {
94  switch (subElement.Name.ToString().ToLowerInvariant())
95  {
96  case "plantslot":
97  PlantSlots.Add(subElement.GetAttributeInt("slot", 0), new PlantSlot(subElement));
98  break;
99  case "suitablefertilizer":
100  SuitableFertilizer.Add(RelatedItem.Load(subElement, true, item.Name));
101  break;
102  case "suitableseed":
103  SuitableSeeds.Add(RelatedItem.Load(subElement, true, item.Name));
104  break;
105  }
106  }
107  }
108 
109  public override void OnItemLoaded()
110  {
111  base.OnItemLoaded();
112  IsActive = true;
113 #if CLIENT
114  var lights = item.GetComponents<LightComponent>();
115  if (lights.Any())
116  {
117  lightComponents = lights.ToList();
118  foreach (var light in lightComponents)
119  {
120  light.Light.Enabled = false;
121  }
122  }
123 #endif
124  container = item.GetComponent<ItemContainer>();
125  GrowableSeeds = new Growable[container.Capacity];
126  }
127 
128  public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString? msg = null)
129  {
130  if (container?.Inventory == null) { return false; }
131 
132  SuitablePlantItem plantItem = GetSuitableItem(character);
133 
134  if (!plantItem.IsNull())
135  {
136  Msg = plantItem.Type switch
137  {
138  PlantItemType.Seed => MsgSeed,
139  PlantItemType.Fertilizer => MsgFertilizer,
140  _ => throw new ArgumentOutOfRangeException()
141  };
142  ParseMsg();
143  return true;
144  }
145 
146  if (GrowableSeeds.Any(s => s != null))
147  {
148  Msg = MsgHarvest;
149  ParseMsg();
150  return true;
151  }
152 
153  Msg = string.Empty;
154  ParseMsg();
155  return false;
156  }
157 
158  public override bool Pick(Character character)
159  {
160  SuitablePlantItem plantItem = GetSuitableItem(character);
161  PickingMsg = plantItem.IsNull() ? MsgUprooting : plantItem.ProgressBarMessage;
162 
163  return base.Pick(character);
164  }
165 
166  public override bool OnPicked(Character character)
167  {
168  if (container?.Inventory == null) { return false; }
169 
170  SuitablePlantItem plantItem = GetSuitableItem(character);
171  if (plantItem.IsNull())
172  {
173  return TryHarvest(character);
174  }
175 
176  switch (plantItem.Type)
177  {
178  case PlantItemType.Seed:
179  ApplyStatusEffects(ActionType.OnPicked, 1.0f, character);
180  if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
181  {
182  return container.Inventory.TryPutItem(plantItem.Item, character);
183  }
184  else
185  {
186  //let the server handle moving the item
187  return false;
188  }
189  case PlantItemType.Fertilizer when plantItem.Item != null:
190  float canAdd = FertilizerCapacity - Fertilizer;
191  float maxAvailable = plantItem.Item.Condition;
192  float toAdd = Math.Min(canAdd, maxAvailable);
193  plantItem.Item.Condition -= toAdd;
194  fertilizer += toAdd;
195 #if CLIENT
196  character.UpdateHUDProgressBar(this, Item.DrawPosition, Fertilizer / FertilizerCapacity, Color.SaddleBrown, Color.SaddleBrown, "entityname.fertilizer");
197 #endif
198  ApplyStatusEffects(ActionType.OnPicked, 1.0f, character);
199  return false;
200  }
201 
202  return false;
203  }
204 
210  private bool TryHarvest(Character? character)
211  {
212  Debug.Assert(container != null, "Tried to harvest a planter without an item container.");
213 
214  bool anyDecayed = GrowableSeeds.Any(s => s is { } seed && (seed.Decayed || seed.FullyGrown));
215  for (var i = 0; i < GrowableSeeds.Length; i++)
216  {
217  Growable? seed = GrowableSeeds[i];
218  if (seed == null) { continue; }
219 
220  if (!anyDecayed || seed.Decayed || seed.FullyGrown)
221  {
222  container?.Inventory.RemoveItem(seed.Item);
223  Entity.Spawner?.AddItemToRemoveQueue(seed.Item);
224  GrowableSeeds[i] = null;
225  ApplyStatusEffects(ActionType.OnPicked, 1.0f, character);
226  return true;
227  }
228  }
229 
230  return false;
231  }
232 
233  public override void Update(float deltaTime, Camera cam)
234  {
235  base.Update(deltaTime, cam);
236 
237 #if CLIENT
238  if (lightComponents != null && lightComponents.Count > 0)
239  {
240  bool hasSeed = false;
241  foreach (Growable? seed in GrowableSeeds)
242  {
243  hasSeed |= seed != null;
244  }
245  foreach (var light in lightComponents)
246  {
247  light.Light.Enabled = hasSeed;
248  }
249  }
250 #endif
251 
252  if (container?.Inventory == null) { return; }
253 
254  bool recreateHudTexts = false;
255  for (var i = 0; i < container.Inventory.Capacity; i++)
256  {
257  if (i < 0 || GrowableSeeds.Length <= i) { continue; }
258 
259  Item containedItem = container.Inventory.GetItemAt(i);
260  Growable? growable = containedItem?.GetComponent<Growable>();
261 
262  if (growable != null)
263  {
264  recreateHudTexts |= GrowableSeeds[i] != growable;
265  GrowableSeeds[i] = growable;
266  growable.IsActive = true;
267  }
268  else
269  {
270  if (GrowableSeeds[i] is { } oldGrowable)
271  {
272  // Kill the plant if it's somehow removed
273  oldGrowable.Decayed = true;
274  oldGrowable.IsActive = false;
275  recreateHudTexts = true;
276  }
277  GrowableSeeds[i] = null;
278  }
279  }
280 #if CLIENT
281  CharacterHUD.RecreateHudTexts |= recreateHudTexts;
282 #endif
283 
284  // server handles this
285  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
286 
287  float delay = GrowthTickDelay;
288  if (Fertilizer > 0)
289  {
290  delay /= 2f;
291  Fertilizer -= deltaTime / 10f;
292  }
293 
294  if (growthTickTimer > delay)
295  {
296  for (var i = 0; i < GrowableSeeds.Length; i++)
297  {
298  PlantSlot slot = PlantSlots.ContainsKey(i) ? PlantSlots[i] : NullSlot;
299  Growable? seed = GrowableSeeds[i];
300  seed?.OnGrowthTick(this, slot);
301  }
302 
303  growthTickTimer = 0;
304  }
305  else if (Item.ParentInventory == null)
306  {
307  if (item.GetComponent<Holdable>() is { } holdable)
308  {
309  if (holdable.Attachable && !holdable.Attached)
310  {
311  return;
312  }
313  }
314 
315  growthTickTimer += deltaTime;
316  }
317  }
318 
319  private SuitablePlantItem GetSuitableItem(Character character)
320  {
321  foreach (Item heldItem in character.HeldItems)
322  {
323  if (container?.Inventory != null && container.Inventory.CanBePut(heldItem))
324  {
325  if (heldItem.GetComponent<Growable>() != null && SuitableSeeds.Any(ri => ri.MatchesItem(heldItem)))
326  {
327  return new SuitablePlantItem(heldItem, PlantItemType.Seed, MsgPlanting);
328  }
329  }
330 
331  if (SuitableFertilizer.Any(ri => ri.MatchesItem(heldItem)))
332  {
333  return new SuitablePlantItem(heldItem, PlantItemType.Fertilizer, MsgFertilizing);
334  }
335  }
336 
337  return NullItem;
338  }
339 
340  private bool HasAnyFinishedGrowing() => GrowableSeeds.Any(seed => seed != null && (seed.FullyGrown || seed.Decayed));
341  }
342 }
virtual Vector2 DrawPosition
Definition: Entity.cs:51
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb targetLimb=null, Entity useTarget=null, Character user=null, Vector2? worldPosition=null, float afflictionMultiplier=1.0f)
virtual bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg=null)
virtual bool OnPicked(Character picker, bool pickDroppedStack)
Definition: Pickable.cs:122
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19