Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs
4 using System;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Linq;
8 using System.Xml.Linq;
9 
11 {
13  {
14  private float progressTimer;
15  private float progressState;
16 
17  private bool hasPower;
18 
19  private Character user;
20 
21  private float userDeconstructorSpeedMultiplier = 1.0f;
22 
23  private const float TinkeringSpeedIncrease = 2.5f;
24 
25  private ItemContainer inputContainer, outputContainer;
26 
28  {
29  get { return inputContainer; }
30  }
31 
33  {
34  get { return outputContainer; }
35  }
36 
42 
43  [Serialize(false, IsPropertySaveable.Yes)]
44  public bool DeconstructItemsSimultaneously { get; set; }
45 
46  [Editable(MinValueFloat = 0.1f, MaxValueFloat = 1000), Serialize(1.0f, IsPropertySaveable.Yes)]
47  public float DeconstructionSpeed { get; set; }
48 
50  : base(item, element)
51  {
52  InitProjSpecific(element);
53  }
54 
55  partial void InitProjSpecific(XElement element);
56 
57  public override void OnItemLoaded()
58  {
59  base.OnItemLoaded();
60  var containers = item.GetComponents<ItemContainer>().ToList();
61  if (containers.Count < 2)
62  {
63  DebugConsole.ThrowError("Error in item \"" + item.Name + "\": Deconstructors must have two ItemContainer components!");
64  return;
65  }
66 
67  inputContainer = containers[0];
68  outputContainer = containers[1];
69 
70 #if CLIENT
71  Identifier eventIdentifier = new Identifier(nameof(Deconstructor));
72  inputContainer.OnContainedItemsChanged.RegisterOverwriteExisting(eventIdentifier, OnItemSlotsChanged);
73 #endif
74 
75  OnItemLoadedProjSpecific();
76  }
77 
78  partial void OnItemLoadedProjSpecific();
79 
80  partial void OnItemSlotsChanged(ItemContainer container);
81 
82  public override void Update(float deltaTime, Camera cam)
83  {
84  MoveInputQueue();
85 
86  if (inputContainer == null || inputContainer.Inventory.IsEmpty())
87  {
88  SetActive(false);
89  return;
90  }
91 
92  hasPower = Voltage >= MinVoltage;
93  if (!hasPower) { return; }
94 
95  var repairable = item.GetComponent<Repairable>();
96  if (repairable != null)
97  {
98  repairable.LastActiveTime = (float)Timing.TotalTime + 10.0f;
99  }
100 
101  ApplyStatusEffects(ActionType.OnActive, deltaTime);
102 
103  progressTimer += deltaTime * Math.Min(powerConsumption <= 0.0f ? 1 : Voltage, MaxOverVoltageFactor);
104 
105  float tinkeringStrength = 0f;
106  if (repairable.IsTinkering)
107  {
108  tinkeringStrength = repairable.TinkeringStrength;
109  }
110  // doesn't quite work properly, remaining time changes if tinkering stops
111  float deconstructionSpeedModifier = userDeconstructorSpeedMultiplier * (1f + tinkeringStrength * TinkeringSpeedIncrease);
112 
113  float deconstructionSpeed = item.StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.DeconstructorSpeed, DeconstructionSpeed);
114 
116  {
117  float deconstructTime = 0.0f;
118  foreach (Item targetItem in inputContainer.Inventory.AllItems)
119  {
120  deconstructTime += targetItem.Prefab.DeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier);
121  }
122 
123  progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
124  if (progressTimer > deconstructTime)
125  {
126  List<Item> items = inputContainer.Inventory.AllItems.ToList();
127  foreach (Item targetItem in items)
128  {
129  if ((Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false) || !inputContainer.Inventory.AllItems.Contains(targetItem)) { continue; }
130  var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it =>
131  it.IsValidDeconstructor(item) &&
132  (it.RequiredOtherItem.Length == 0 || it.RequiredOtherItem.Any(r => items.Any(it => it != targetItem && (it.HasTag(r) || it.Prefab.Identifier == r))))).ToList();
133 
134  ProcessItem(targetItem, items, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any());
135  }
136 #if SERVER
137  item.CreateServerEvent(this);
138 #endif
139  progressTimer = 0.0f;
140  progressState = 0.0f;
141 
142  }
143  }
144  else
145  {
146  var targetItem = inputContainer.Inventory.LastOrDefault();
147  if (targetItem == null) { return; }
148 
149  var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it => it.IsValidDeconstructor(item)).ToList();
150  float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier) : 1.0f;
151 
152  progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
153  if (progressTimer > deconstructTime)
154  {
155  ProcessItem(targetItem, inputContainer.Inventory.AllItemsMod, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any());
156 
157 #if SERVER
158  item.CreateServerEvent(this);
159 #endif
160  progressTimer = 0.0f;
161  progressState = 0.0f;
162 
163  }
164  }
165  }
166 
167  private void ProcessItem(Item targetItem, IEnumerable<Item> inputItems, List<DeconstructItem> validDeconstructItems, bool allowRemove = true)
168  {
169  // In multiplayer, the server handles the deconstruction into new items
170  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
171 
172  float amountMultiplier = 1f;
173 
174  if (user != null && !user.Removed)
175  {
176  var abilityTargetItem = new AbilityDeconstructedItem(targetItem, user);
177  user.CheckTalents(AbilityEffectType.OnItemDeconstructed, abilityTargetItem);
178 
179  foreach (Character character in Character.GetFriendlyCrew(user))
180  {
181  character.CheckTalents(AbilityEffectType.OnItemDeconstructedByAlly, abilityTargetItem);
182  }
183 
184  var itemCreationMultiplier = new AbilityItemCreationMultiplier(targetItem.Prefab, amountMultiplier);
185  user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemCreationMultiplier);
186  amountMultiplier = (int)itemCreationMultiplier.Value;
187  }
188 
189  if (targetItem.Prefab.RandomDeconstructionOutput)
190  {
191  int amount = targetItem.Prefab.RandomDeconstructionOutputAmount;
192  List<int> deconstructItemIndexes = new List<int>();
193  for (int i = 0; i < validDeconstructItems.Count; i++)
194  {
195  deconstructItemIndexes.Add(i);
196  }
197  List<float> commonness = validDeconstructItems.Select(i => i.Commonness).ToList();
198  List<DeconstructItem> products = new List<DeconstructItem>();
199 
200  for (int i = 0; i < amount; i++)
201  {
202  if (deconstructItemIndexes.Count < 1) { break; }
203  var itemIndex = ToolBox.SelectWeightedRandom(deconstructItemIndexes, commonness, Rand.RandSync.Unsynced);
204  products.Add(validDeconstructItems[itemIndex]);
205  var removeIndex = deconstructItemIndexes.IndexOf(itemIndex);
206  deconstructItemIndexes.RemoveAt(removeIndex);
207  commonness.RemoveAt(removeIndex);
208  }
209 
210  foreach (DeconstructItem deconstructProduct in products)
211  {
212  CreateDeconstructProduct(deconstructProduct, inputItems, (int)(amountMultiplier * deconstructProduct.Amount));
213  }
214  }
215  else
216  {
217  foreach (DeconstructItem deconstructProduct in validDeconstructItems)
218  {
219  CreateDeconstructProduct(deconstructProduct, inputItems, (int)(amountMultiplier * deconstructProduct.Amount));
220  }
221  }
222 
223  void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable<Item> inputItems, int amount)
224  {
225  float percentageHealth = targetItem.Condition / targetItem.MaxCondition;
226 
227  if (percentageHealth < deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; }
228 
229  if (MapEntityPrefab.FindByIdentifier(deconstructProduct.ItemIdentifier) is not ItemPrefab itemPrefab)
230  {
231  DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!");
232  return;
233  }
234 
235  float condition = deconstructProduct.CopyCondition ?
236  percentageHealth * itemPrefab.Health * deconstructProduct.OutConditionMax :
237  itemPrefab.Health * Rand.Range(deconstructProduct.OutConditionMin, deconstructProduct.OutConditionMax);
238 
239  if (DeconstructItemsSimultaneously && deconstructProduct.RequiredOtherItem.Length > 0)
240  {
241  foreach (Item otherItem in inputItems)
242  {
243  if (targetItem == otherItem) { continue; }
244  if (deconstructProduct.RequiredOtherItem.Any(r => otherItem.HasTag(r) || r == otherItem.Prefab.Identifier))
245  {
246  var geneticMaterial1 = targetItem.GetComponent<GeneticMaterial>();
247  var geneticMaterial2 = otherItem.GetComponent<GeneticMaterial>();
248  if (geneticMaterial1 != null && geneticMaterial2 != null)
249  {
250  var result = geneticMaterial1.Combine(geneticMaterial2, user);
251  if (result == GeneticMaterial.CombineResult.Refined)
252  {
253  inputContainer.Inventory.RemoveItem(otherItem);
255  Entity.Spawner.AddItemToRemoveQueue(otherItem);
256  }
257  if (result != GeneticMaterial.CombineResult.None)
258  {
259  OnCombinedOrRefined();
260  }
261  allowRemove = false;
262  return;
263  }
264  else
265  {
266  inputContainer.Inventory.RemoveItem(otherItem);
268  Entity.Spawner.AddItemToRemoveQueue(otherItem);
269  OnCombinedOrRefined();
270  }
271  }
272  }
273 
274  void OnCombinedOrRefined()
275  {
276  user?.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
277  foreach (Character character in Character.GetFriendlyCrew(user))
278  {
279  character.CheckTalents(AbilityEffectType.OnCrewGeneticMaterialCombinedOrRefined);
280  }
281  }
282  }
283 
284  if (user != null && !user.Removed)
285  {
286  // used to spawn items directly into the deconstructor
287  var itemDeconstructedInventory = new AbilityItemDeconstructedInventory(targetItem.Prefab, item);
288  user.CheckTalents(AbilityEffectType.OnItemDeconstructedInventory, itemDeconstructedInventory);
289  }
290 
291  for (int i = 0; i < amount; i++)
292  {
293  Entity.Spawner.AddItemToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) =>
294  {
295  spawnedItem.StolenDuringRound = targetItem.StolenDuringRound;
296  spawnedItem.AllowStealing = targetItem.AllowStealing;
297  spawnedItem.OriginalOutpost = targetItem.OriginalOutpost;
298  spawnedItem.SpawnedInCurrentOutpost = targetItem.SpawnedInCurrentOutpost;
299  if (RelocateOutputToMainSub && user is { AIController: HumanAIController humanAi })
300  {
301  humanAi.HandleRelocation(spawnedItem);
302  }
303  for (int i = 0; i < outputContainer.Capacity; i++)
304  {
305  var containedItem = outputContainer.Inventory.GetItemAt(i);
306  bool combined = false;
307  if (containedItem?.OwnInventory != null)
308  {
309  foreach (Item subItem in containedItem.ContainedItems.ToList())
310  {
311  if (subItem.Combine(spawnedItem, null))
312  {
313  combined = true;
314  break;
315  }
316  }
317  }
318  if (!combined)
319  {
320  if (containedItem?.Combine(spawnedItem, null) ?? false)
321  {
322  break;
323  }
324  }
325  }
326  PutItemsToLinkedContainer();
327  });
328  }
329  }
330 
331  if (targetItem.Prefab.ContentPackage == ContentPackageManager.VanillaCorePackage &&
332  /* we don't need info of every item, we can get a good sample size just by logging 5% */
333  Rand.Range(0.0f, 1.0f) < 0.05f)
334  {
335  GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + targetItem.Prefab.Identifier);
336  }
337 
338  bool? result = GameMain.LuaCs.Hook.Call<bool?>("item.deconstructed", targetItem, this, user, allowRemove);
339  if (result == true) { return; }
340 
341  if (targetItem.AllowDeconstruct && allowRemove)
342  {
343  //drop all items that are inside the deconstructed item
344  foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
345  {
346  if (ic?.Inventory == null || ic.RemoveContainedItemsOnDeconstruct) { continue; }
347  foreach (Item outputItem in ic.Inventory.AllItemsMod)
348  {
349  tryPutInOutputSlots(outputItem);
350  if (RelocateOutputToMainSub && user != null && user.AIController is HumanAIController humanAi)
351  {
352  humanAi.HandleRelocation(outputItem);
353  }
354  }
355  }
356  inputContainer.Inventory.RemoveItem(targetItem);
357  Entity.Spawner.AddItemToRemoveQueue(targetItem);
358  MoveInputQueue();
359  PutItemsToLinkedContainer();
360  }
361  else
362  {
363  if (Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false)
364  {
365  targetItem.Drop(dropper: null);
366  }
367  else
368  {
369  tryPutInOutputSlots(targetItem);
370  }
371  }
372 
373  void tryPutInOutputSlots(Item item)
374  {
375  for (int i = 0; i < outputContainer.Capacity; i++)
376  {
377  var containedItem = outputContainer.Inventory.GetItemAt(i);
378  if (containedItem?.OwnInventory != null && containedItem.GetComponent<GeneticMaterial>() == null && containedItem.OwnInventory.TryPutItem(item, user: null))
379  {
380  return;
381  }
382  }
383  if (!outputContainer.Inventory.TryPutItem(item, user: null))
384  {
385  item.Drop(dropper: null);
386  }
387  }
388  }
389 
390  private void PutItemsToLinkedContainer()
391  {
392  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
393  if (outputContainer.Inventory.IsEmpty()) { return; }
394 
395  foreach (MapEntity linkedTo in item.linkedTo)
396  {
397  if (linkedTo is Item linkedItem)
398  {
399  var fabricator = linkedItem.GetComponent<Fabricator>();
400  if (fabricator != null) { continue; }
401  var itemContainer = linkedItem.GetComponent<ItemContainer>();
402  if (itemContainer == null) { continue; }
403  outputContainer.Inventory.AllItemsMod.ForEach(containedItem => itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true));
404  }
405  }
406  }
407 
411  private void MoveInputQueue()
412  {
413  for (int i = inputContainer.Inventory.Capacity - 2; i >= 0; i--)
414  {
415  while (inputContainer.Inventory.GetItemAt(i) is Item item1 && inputContainer.Inventory.CanBePutInSlot(item1, i + 1))
416  {
417  if (!inputContainer.Inventory.TryPutItem(item1, i + 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: true))
418  {
419  break;
420  }
421  }
422  }
423  }
424 
425  private IEnumerable<(Item item, DeconstructItem output)> GetAvailableOutputs(bool checkRequiredOtherItems = true)
426  {
427  var items = inputContainer.Inventory.AllItems;
428  foreach (Item inputItem in items)
429  {
430  if (!inputItem.AllowDeconstruct) { continue; }
431  foreach (var deconstructItem in inputItem.Prefab.DeconstructItems)
432  {
433  if (deconstructItem.RequiredDeconstructor.Length > 0)
434  {
435  if (!deconstructItem.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier == r)) { continue; }
436  }
437  if (deconstructItem.RequiredOtherItem.Length > 0 && checkRequiredOtherItems)
438  {
439  if (!deconstructItem.RequiredOtherItem.Any(r => items.Any(it => it.HasTag(r) || it.Prefab.Identifier == r))) { continue; }
440  bool validOtherItemFound = false;
441  foreach (Item otherInputItem in items)
442  {
443  if (otherInputItem == inputItem) { continue; }
444  if (!deconstructItem.RequiredOtherItem.Any(r => otherInputItem.HasTag(r) || otherInputItem.Prefab.Identifier == r)) { continue; }
445 
446  var geneticMaterial1 = inputItem.GetComponent<GeneticMaterial>();
447  var geneticMaterial2 = otherInputItem.GetComponent<GeneticMaterial>();
448  if (geneticMaterial1 != null && geneticMaterial2 != null)
449  {
450  if (!geneticMaterial1.CanBeCombinedWith(geneticMaterial2)) { continue; }
451  }
452  validOtherItemFound = true;
453  }
454  if (!validOtherItemFound) { continue; }
455  }
456  yield return (inputItem, deconstructItem);
457  }
458  }
459  }
460 
461  public void SetActive(bool active, Character user = null, bool createNetworkEvent = false)
462  {
463  PutItemsToLinkedContainer();
464 
465  this.user = user;
466  RelocateOutputToMainSub = false;
467 
468  if (inputContainer.Inventory.IsEmpty()) { active = false; }
469 
470  IsActive = active;
471  //currPowerConsumption = IsActive ? powerConsumption : 0.0f;
472  userDeconstructorSpeedMultiplier = user != null ? 1f + user.GetStatValue(StatTypes.DeconstructorSpeedMultiplier) : 1f;
473 
474 #if SERVER
475  if (user != null)
476  {
477  GameServer.Log(GameServer.CharacterLogName(user) + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
478  }
479  if (createNetworkEvent)
480  {
481  item.CreateServerEvent(this);
482  }
483 #endif
484  if (!IsActive)
485  {
486  progressTimer = 0.0f;
487  progressState = 0.0f;
488  }
489 #if CLIENT
490  else
491  {
492  HintManager.OnStartDeconstructing(user, this);
493  if (Item.Submarine is { Info.IsOutpost: true } && user is { IsBot: true })
494  {
495  HintManager.OnItemMarkedForRelocation();
496  }
497  }
498 #endif
499 
500  inputContainer.Inventory.Locked = IsActive;
501  }
502  }
504  {
505  public AbilityDeconstructedItem(Item item, Character character)
506  {
507  Item = item;
508  Character = character;
509  }
510  public Item Item { get; set; }
511  public Character Character { get; set; }
512  }
513 
515  {
516  public AbilityItemCreationMultiplier(ItemPrefab itemPrefab, float itemAmountMultiplier)
517  {
518  ItemPrefab = itemPrefab;
519  Value = itemAmountMultiplier;
520  }
521  public ItemPrefab ItemPrefab { get; set; }
522  public float Value { get; set; }
523  }
524 
526  {
528  {
529  ItemPrefab = itemPrefab;
530  Item = item;
531  }
532  public ItemPrefab ItemPrefab { get; set; }
533  public Item Item { get; set; }
534  }
535 
536 }
static IEnumerable< Character > GetFriendlyCrew(Character character)
void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
static EntitySpawner Spawner
Definition: Entity.cs:31
Submarine Submarine
Definition: Entity.cs:53
static NetworkMember NetworkMember
Definition: GameMain.cs:190
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
IEnumerable< Item > AllItemsMod
All items contained in the inventory. Allows modifying the contents of the inventory while being enum...
Item GetItemAt(int index)
Get the item stored in the specified inventory slot. If the slot contains a stack of items,...
Item LastOrDefault()
Return the last item in the inventory, or null if the inventory is empty.
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
bool Combine(Item item, Character user)
override bool TryPutItem(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true, bool ignoreCondition=false)
If there is room, puts the item in the inventory and returns true, otherwise returns false
override bool CanBePutInSlot(Item item, int i, bool ignoreCondition=false)
Can the item be put in the specified slot.
ImmutableArray< DeconstructItem > DeconstructItems
void SetActive(bool active, Character user=null, bool createNetworkEvent=false)
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
bool RelocateOutputToMainSub
Should the output items left in the deconstructor be automatically moved to the main sub at the end o...
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)
const float MaxOverVoltageFactor
Maximum voltage factor when the device is being overvolted. I.e. how many times more effectively the ...
float powerConsumption
The maximum amount of power the item can draw from connected items
ContentPackage? ContentPackage
Definition: Prefab.cs:37
readonly Identifier Identifier
Definition: Prefab.cs:34
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19
AbilityEffectType
Definition: Enums.cs:125
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:180