3 using System.Collections.Generic;
4 using System.Diagnostics;
6 using Microsoft.Xna.Framework;
10 internal enum PlantItemType
16 internal readonly
struct SuitablePlantItem
19 public readonly PlantItemType Type;
20 public readonly
string ProgressBarMessage;
22 public SuitablePlantItem(
Item item, PlantItemType type,
string progressBarMessage)
26 ProgressBarMessage = progressBarMessage;
29 public bool IsNull() =>
Item ==
null;
32 internal struct PlantSlot
34 public Vector2 Offset;
37 public PlantSlot(ContentXElement element)
39 Offset = element.GetAttributeVector2(
"offset", Vector2.Zero);
40 Size = element.GetAttributeFloat(
"size", 0.5f);
43 public PlantSlot(Vector2 offset,
float size)
50 internal partial class Planter : Pickable, IDrawableComponent
52 public static readonly PlantSlot NullSlot =
new PlantSlot();
53 public readonly Dictionary<int, PlantSlot> PlantSlots =
new Dictionary<int, PlantSlot>();
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;
64 private float fertilizer;
67 public float Fertilizer
70 set => fertilizer = Math.Clamp(value, 0, FertilizerCapacity);
74 public float FertilizerCapacity {
get;
set; }
76 public Growable?[] GrowableSeeds =
new Growable?[0];
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;
83 private List<LightComponent>? lightComponents;
86 public override bool DontTransferInventoryBetweenSubs =>
true;
88 public Planter(
Item item, ContentXElement element) : base(item, element)
91 SerializableProperty.DeserializeProperties(
this, element);
92 foreach (var subElement
in element.Elements())
94 switch (subElement.Name.ToString().ToLowerInvariant())
97 PlantSlots.Add(subElement.GetAttributeInt(
"slot", 0),
new PlantSlot(subElement));
99 case "suitablefertilizer":
100 SuitableFertilizer.Add(RelatedItem.Load(subElement,
true, item.Name));
103 SuitableSeeds.Add(RelatedItem.Load(subElement,
true, item.Name));
109 public override void OnItemLoaded()
114 var lights = item.GetComponents<LightComponent>();
117 lightComponents = lights.ToList();
118 foreach (var light
in lightComponents)
120 light.Light.Enabled =
false;
124 container = item.GetComponent<ItemContainer>();
125 GrowableSeeds =
new Growable[container.Capacity];
128 public override bool HasRequiredItems(Character character,
bool addMessage, LocalizedString? msg =
null)
130 if (container?.Inventory ==
null) {
return false; }
132 SuitablePlantItem plantItem = GetSuitableItem(character);
134 if (!plantItem.IsNull())
136 Msg = plantItem.Type
switch
138 PlantItemType.Seed => MsgSeed,
139 PlantItemType.Fertilizer => MsgFertilizer,
140 _ =>
throw new ArgumentOutOfRangeException()
146 if (GrowableSeeds.Any(s => s !=
null))
158 public override bool Pick(Character character)
160 SuitablePlantItem plantItem = GetSuitableItem(character);
161 PickingMsg = plantItem.IsNull() ? MsgUprooting : plantItem.ProgressBarMessage;
163 return base.Pick(character);
166 public override bool OnPicked(Character character)
168 if (container?.Inventory ==
null) {
return false; }
170 SuitablePlantItem plantItem = GetSuitableItem(character);
171 if (plantItem.IsNull())
173 return TryHarvest(character);
176 switch (plantItem.Type)
178 case PlantItemType.Seed:
180 if (GameMain.NetworkMember ==
null || GameMain.NetworkMember.IsServer)
182 return container.Inventory.TryPutItem(plantItem.Item, character);
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;
196 character.UpdateHUDProgressBar(
this,
Item.DrawPosition, Fertilizer / FertilizerCapacity, Color.SaddleBrown, Color.SaddleBrown,
"entityname.fertilizer");
210 private bool TryHarvest(Character? character)
212 Debug.Assert(container !=
null,
"Tried to harvest a planter without an item container.");
214 bool anyDecayed = GrowableSeeds.Any(s => s is { } seed && (seed.Decayed || seed.FullyGrown));
215 for (var i = 0; i < GrowableSeeds.Length; i++)
217 Growable? seed = GrowableSeeds[i];
218 if (seed ==
null) {
continue; }
220 if (!anyDecayed || seed.Decayed || seed.FullyGrown)
222 container?.Inventory.RemoveItem(seed.Item);
223 Entity.Spawner?.AddItemToRemoveQueue(seed.Item);
224 GrowableSeeds[i] =
null;
233 public override void Update(
float deltaTime, Camera cam)
235 base.Update(deltaTime, cam);
238 if (lightComponents !=
null && lightComponents.Count > 0)
240 bool hasSeed =
false;
241 foreach (Growable? seed
in GrowableSeeds)
243 hasSeed |= seed !=
null;
245 foreach (var light
in lightComponents)
247 light.Light.Enabled = hasSeed;
252 if (container?.Inventory ==
null) {
return; }
254 bool recreateHudTexts =
false;
255 for (var i = 0; i < container.Inventory.Capacity; i++)
257 if (i < 0 || GrowableSeeds.Length <= i) {
continue; }
259 Item containedItem = container.Inventory.GetItemAt(i);
260 Growable? growable = containedItem?.GetComponent<Growable>();
262 if (growable !=
null)
264 recreateHudTexts |= GrowableSeeds[i] != growable;
265 GrowableSeeds[i] = growable;
266 growable.IsActive =
true;
270 if (GrowableSeeds[i] is { } oldGrowable)
273 oldGrowable.Decayed =
true;
274 oldGrowable.IsActive =
false;
275 recreateHudTexts =
true;
277 GrowableSeeds[i] =
null;
285 if (GameMain.NetworkMember !=
null && GameMain.NetworkMember.IsClient) {
return; }
287 float delay = GrowthTickDelay;
291 Fertilizer -= deltaTime / 10f;
294 if (growthTickTimer > delay)
296 for (var i = 0; i < GrowableSeeds.Length; i++)
298 PlantSlot slot = PlantSlots.ContainsKey(i) ? PlantSlots[i] : NullSlot;
299 Growable? seed = GrowableSeeds[i];
300 seed?.OnGrowthTick(
this, slot);
305 else if (
Item.ParentInventory ==
null)
307 if (item.GetComponent<Holdable>() is { } holdable)
309 if (holdable.Attachable && !holdable.Attached)
315 growthTickTimer += deltaTime;
319 private SuitablePlantItem GetSuitableItem(Character character)
321 foreach (
Item heldItem
in character.HeldItems)
323 if (container?.Inventory !=
null && container.Inventory.CanBePut(heldItem))
325 if (heldItem.GetComponent<Growable>() !=
null && SuitableSeeds.Any(ri => ri.MatchesItem(heldItem)))
327 return new SuitablePlantItem(heldItem, PlantItemType.Seed, MsgPlanting);
331 if (SuitableFertilizer.Any(ri => ri.MatchesItem(heldItem)))
333 return new SuitablePlantItem(heldItem, PlantItemType.Fertilizer, MsgFertilizing);
340 private bool HasAnyFinishedGrowing() => GrowableSeeds.Any(seed => seed !=
null && (seed.FullyGrown || seed.Decayed));
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)
ActionType
ActionTypes define when a StatusEffect is executed.