Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/CharacterInventory.cs
4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
6 using Microsoft.Xna.Framework.Input;
7 using System;
8 using System.Collections.Generic;
9 using System.Linq;
10 using System.Xml.Linq;
11 
12 namespace Barotrauma
13 {
14  partial class CharacterInventory : Inventory
15  {
16  public enum Layout
17  {
18  Default,
19  Left,
20  Right,
21  Center
22  }
23 
24  private enum QuickUseAction
25  {
26  None,
27  Equip,
28  Unequip,
29  Drop,
30  TakeFromContainer,
31  TakeFromCharacter,
32  PutToContainer,
33  PutToCharacter,
34  PutToEquippedItem,
35  UseTreatment,
36  }
37 
38  private static Dictionary<InvSlotType, Sprite> limbSlotIcons;
39  public static Dictionary<InvSlotType, Sprite> LimbSlotIcons
40  {
41  get
42  {
43  if (limbSlotIcons == null)
44  {
45  limbSlotIcons = new Dictionary<InvSlotType, Sprite>();
46  foreach (InvSlotType invSlotType in Enum.GetValues(typeof(InvSlotType)))
47  {
48  var sprite = GUIStyle.GetComponentStyle($"InventorySlot.{invSlotType}")?.GetDefaultSprite();
49  if (sprite != null)
50  {
51  limbSlotIcons.Add(invSlotType, sprite);
52  }
53  }
54 
55  int margin = 2;
56  AddIfMissing(InvSlotType.Headset, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
57  AddIfMissing(InvSlotType.InnerClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
58  AddIfMissing(InvSlotType.Card, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(640 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
59  AddIfMissing(InvSlotType.Head, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(896 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
60  AddIfMissing(InvSlotType.LeftHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(634, 0, 128, 128)));
61  AddIfMissing(InvSlotType.RightHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(762, 0, 128, 128)));
62  AddIfMissing(InvSlotType.OuterClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
63  AddIfMissing(InvSlotType.Bag, new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(639, 926, 128,80)));
64 
65  static void AddIfMissing(InvSlotType slotType, Sprite sprite)
66  {
67  limbSlotIcons.TryAdd(slotType, sprite);
68  }
69  }
70  return limbSlotIcons;
71  }
72  }
73 
74  public const InvSlotType PersonalSlots = InvSlotType.Card | InvSlotType.Bag | InvSlotType.Headset | InvSlotType.InnerClothes | InvSlotType.OuterClothes | InvSlotType.Head;
75 
76  private Point screenResolution;
77 
78  public Vector2[] SlotPositions;
79  public static Point SlotSize;
80 
81  private Layout layout;
83  {
84  get { return layout; }
85  set
86  {
87  if (layout == value) return;
88  layout = value;
89  SetSlotPositions(layout);
90  }
91  }
92 
93  private Rectangle personalSlotArea;
94 
95  partial void InitProjSpecific(XElement element)
96  {
97  SlotPositions = new Vector2[SlotTypes.Length];
98  CurrentLayout = Layout.Default;
99  SetSlotPositions(layout);
100  }
101 
102  protected override ItemInventory GetActiveEquippedSubInventory(int slotIndex)
103  {
104  Item item = slots[slotIndex].FirstOrDefault();
105  if (item == null) { return null; }
106 
107  var container = item.GetComponent<ItemContainer>();
108  if (container == null || !container.KeepOpenWhenEquippedBy(character))
109  {
110  return null;
111  }
112  return container.Inventory;
113  }
114 
115  public override void CreateSlots()
116  {
118 
119  float multiplier = UIScale * GUI.AspectRatioAdjustment;
120 
121  for (int i = 0; i < capacity; i++)
122  {
123  VisualSlot prevSlot = visualSlots[i];
124 
125  Sprite slotSprite = SlotSpriteSmall;
126  Rectangle slotRect = new Rectangle(
127  (int)SlotPositions[i].X,
128  (int)SlotPositions[i].Y,
129  (int)(slotSprite.size.X * multiplier), (int)(slotSprite.size.Y * multiplier));
130 
131  if (SlotTypes[i] == InvSlotType.HealthInterface &&
132  character.CharacterHealth?.InventorySlotContainer != null)
133  {
134  slotRect.Width = slotRect.Height = (int)(character.CharacterHealth.InventorySlotContainer.Rect.Width * 1.2f);
135  }
136 
137  ItemContainer itemContainer = slots[i].FirstOrDefault()?.GetComponent<ItemContainer>();
138  if (itemContainer != null)
139  {
140  if (itemContainer.InventoryTopSprite != null) slotRect.Width = Math.Max(slotRect.Width, (int)(itemContainer.InventoryTopSprite.size.X * UIScale));
141  if (itemContainer.InventoryBottomSprite != null) slotRect.Width = Math.Max(slotRect.Width, (int)(itemContainer.InventoryBottomSprite.size.X * UIScale));
142  }
143 
144  visualSlots[i] = new VisualSlot(slotRect)
145  {
146  SubInventoryDir = Math.Sign(GameMain.GraphicsHeight / 2 - slotRect.Center.Y),
147  Disabled = false,
148  SlotSprite = slotSprite,
149  Color = SlotTypes[i] == InvSlotType.Any ? Color.White * 0.2f : Color.White * 0.4f
150  };
151  if (prevSlot != null)
152  {
153  visualSlots[i].DrawOffset = prevSlot.DrawOffset;
154  visualSlots[i].Color = prevSlot.Color;
155  prevSlot.MoveBorderHighlight(visualSlots[i]);
156  }
157  if (selectedSlot?.ParentInventory == this && selectedSlot.SlotIndex == i)
158  {
160  }
161  }
162 
164 
165  highlightedSubInventorySlots.RemoveWhere(s => s.Inventory.OpenState <= 0.0f);
166  foreach (var subSlot in highlightedSubInventorySlots)
167  {
168  if (subSlot.ParentInventory == this && subSlot.SlotIndex > 0 && subSlot.SlotIndex < visualSlots.Length)
169  {
170  subSlot.Slot = visualSlots[subSlot.SlotIndex];
171  }
172  }
173 
174  screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
176  }
177 
178  protected override void CalculateBackgroundFrame()
179  {
180  Rectangle frame = Rectangle.Empty;
181  for (int i = 0; i < capacity; i++)
182  {
183  if (HideSlot(i)) continue;
184  if (frame == Rectangle.Empty)
185  {
186  frame = visualSlots[i].Rect;
187  continue;
188  }
189  frame = Rectangle.Union(frame, visualSlots[i].Rect);
190  }
191  frame.Inflate(10, 30);
192  frame.Location -= new Point(0, 25);
193  BackgroundFrame = frame;
194  }
195 
196  public override bool HideSlot(int i)
197  {
198  if (visualSlots[i].Disabled || (slots[i].HideIfEmpty && slots[i].Empty())) { return true; }
199 
200  if (SlotTypes[i] == InvSlotType.HealthInterface)
201  {
202  //hide health interface slot unless this character's health window is open
203  if (CharacterHealth.OpenHealthWindow == null || Character.Controlled == null)
204  {
205  return true;
206  }
207  if (character == Character.Controlled)
208  {
210  if (!ownHealthWindowOpen) { return true; }
211  }
212  else if (character == Character.Controlled.SelectedCharacter)
213  {
214  bool otherCharacterHealthWindowOpen = CharacterHealth.OpenHealthWindow == Character.Controlled?.SelectedCharacter?.CharacterHealth;
215  if (!otherCharacterHealthWindowOpen) { return true; }
216  //can't access the health interface slot of a non-incapacitated player (bots are fine though)
217  if (character.IsPlayer && !character.IsIncapacitated) { return true; }
218  }
219  }
220 
221  if (layout == Layout.Default)
222  {
223  if (PersonalSlots.HasFlag(SlotTypes[i]) && !personalSlotArea.Contains(visualSlots[i].Rect.Center + visualSlots[i].DrawOffset.ToPoint())) { return true; }
224  }
225 
226  Item item = slots[i].FirstOrDefault();
227 
228  //no need to draw the right hand slot if the item is in both hands
229  if (item != null && SlotTypes[i] == InvSlotType.RightHand && IsInLimbSlot(item, InvSlotType.LeftHand))
230  {
231  return true;
232  }
233 
234  //don't show the limb-specific slot if the item is also in an Any slot
235  if (item != null && SlotTypes[i] != InvSlotType.Any)
236  {
237  if (IsInLimbSlot(item, InvSlotType.Any)) { return true; }
238  }
239 
240  //don't draw equipment slots in wiring mode
242  {
243  if (SlotTypes[i] != InvSlotType.Any && SlotTypes[i] != InvSlotType.LeftHand && SlotTypes[i] != InvSlotType.RightHand)
244  {
245  return true;
246  }
247  }
248 
249  return false;
250  }
251 
252  public void RefreshSlotPositions()
253  {
254  SetSlotPositions(CurrentLayout);
255  }
256 
257  private void SetSlotPositions(Layout layout)
258  {
259  int spacing = GUI.IntScale(5);
260 
261  SlotSize = (SlotSpriteSmall.size * UIScale * GUI.AspectRatioAdjustment).ToPoint();
262  int bottomOffset = SlotSize.Y + spacing * 2 + ContainedIndicatorHeight;
263  int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - spacing * 2 - (int)(UnequippedIndicator.size.Y * UIScale);
264 
265  if (visualSlots == null) { CreateSlots(); }
266  if (visualSlots.None()) { return; }
267 
268  switch (layout)
269  {
270  case Layout.Default:
271  {
272  int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s));
273  int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface);
274 
275  int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + spacing) / 2;
276  int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - spacing;
277 
278  //make sure the rightmost normal slot doesn't overlap with the personal slots
279  x -= Math.Max((x + normalSlotCount * (SlotSize.X + spacing)) - (upperX - personalSlotCount * (SlotSize.X + spacing)), 0);
280 
281  int hideButtonSlotIndex = -1;
282  for (int i = 0; i < SlotPositions.Length; i++)
283  {
284  if (PersonalSlots.HasFlag(SlotTypes[i]))
285  {
286  SlotPositions[i] = new Vector2(upperX, GameMain.GraphicsHeight - bottomOffset);
287  upperX -= SlotSize.X + spacing;
288  personalSlotArea = (hideButtonSlotIndex == -1) ?
289  new Rectangle(SlotPositions[i].ToPoint(), SlotSize) :
290  Rectangle.Union(personalSlotArea, new Rectangle(SlotPositions[i].ToPoint(), SlotSize));
291  hideButtonSlotIndex = i;
292  }
293  else
294  {
295  SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
296  x += SlotSize.X + spacing;
297  }
298  }
299  }
300  break;
301  case Layout.Right:
302  {
303  int x = HUDLayoutSettings.InventoryAreaLower.Right;
304  int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - SlotSize.X - spacing;
305  for (int i = 0; i < visualSlots.Length; i++)
306  {
307  if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
308  if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
309  if (PersonalSlots.HasFlag(SlotTypes[i]))
310  {
311  //upperX -= slotSize.X + spacing;
312  }
313  else
314  {
315  x -= SlotSize.X + spacing;
316  }
317  }
318 
319  int lowerX = x;
320  int handSlotX = x;
321  for (int i = 0; i < SlotPositions.Length; i++)
322  {
323  if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand)
324  {
325  SlotPositions[i] = new Vector2(handSlotX, personalSlotY);
326  handSlotX += visualSlots[i].Rect.Width + spacing;
327  continue;
328  }
329 
330  if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
331  if (PersonalSlots.HasFlag(SlotTypes[i]))
332  {
333  SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
334  personalSlotX -= visualSlots[i].Rect.Width + spacing;
335  }
336  else
337  {
338  SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
339  x += visualSlots[i].Rect.Width + spacing;
340  }
341  }
342 
343  x = lowerX;
344  for (int i = 0; i < SlotPositions.Length; i++)
345  {
346  if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
347  if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
348  x -= visualSlots[i].Rect.Width + spacing;
349  SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
350  }
351  }
352  break;
353  case Layout.Left:
354  {
355  int x = HUDLayoutSettings.InventoryAreaLower.X;
356  int personalSlotX = x;
357 
358  for (int i = 0; i < SlotPositions.Length; i++)
359  {
360  if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
361  if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
362  if (PersonalSlots.HasFlag(SlotTypes[i]))
363  {
364  SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
365  personalSlotX += visualSlots[i].Rect.Width + spacing;
366  }
367  else
368  {
369  SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
370  x += visualSlots[i].Rect.Width + spacing;
371  }
372  }
373  int handSlotX = x - visualSlots[0].Rect.Width - spacing;
374  for (int i = 0; i < SlotPositions.Length; i++)
375  {
376  if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand)
377  {
378  bool rightSlot = SlotTypes[i] == InvSlotType.RightHand;
379  SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - spacing, personalSlotY);
380  continue;
381  }
382  if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
383  SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
384  x += visualSlots[i].Rect.Width + spacing;
385  }
386  }
387  break;
388  case Layout.Center:
389  {
390  int columns = 5;
391  int startX = (GameMain.GraphicsWidth / 2) - (SlotSize.X * columns + spacing * (columns - 1)) / 2;
392  int startY = GameMain.GraphicsHeight / 2 - (SlotSize.Y * 2);
393  int x = startX, y = startY;
394  for (int i = 0; i < SlotPositions.Length; i++)
395  {
396  if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
397  if (SlotTypes[i] == InvSlotType.Card || SlotTypes[i] == InvSlotType.Headset || SlotTypes[i] == InvSlotType.InnerClothes)
398  {
399  SlotPositions[i] = new Vector2(x, y);
400  x += visualSlots[i].Rect.Width + spacing;
401  }
402  }
403  y += visualSlots[0].Rect.Height + spacing + ContainedIndicatorHeight + visualSlots[0].EquipButtonRect.Height;
404  x = startX;
405  int n = 0;
406  for (int i = 0; i < SlotPositions.Length; i++)
407  {
408  if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
409  if (SlotTypes[i] != InvSlotType.Card && SlotTypes[i] != InvSlotType.Headset && SlotTypes[i] != InvSlotType.InnerClothes)
410  {
411  SlotPositions[i] = new Vector2(x, y);
412  x += visualSlots[i].Rect.Width + spacing;
413  n++;
414  if (n >= columns)
415  {
416  x = startX;
417  y += visualSlots[i].Rect.Height + spacing + ContainedIndicatorHeight + visualSlots[i].EquipButtonRect.Height;
418  n = 0;
419  }
420  }
421  }
422  }
423  break;
424  }
425 
426  if (character.CharacterHealth?.UseHealthWindow ?? false)
427  {
428  Vector2 pos = character.CharacterHealth.InventorySlotContainer.Rect.Location.ToVector2();
429  for (int i = 0; i < capacity; i++)
430  {
431  if (SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
432  SlotPositions[i] = pos;
433  pos.Y += visualSlots[i].Rect.Height + spacing;
434  }
435  }
436 
437  CreateSlots();
438  if (layout == Layout.Default)
439  {
440  HUDLayoutSettings.InventoryTopY = visualSlots[0].EquipButtonRect.Y - (int)(15 * GUI.Scale);
441  }
442  else
443  {
444  for (int i = 0; i < capacity; i++)
445  {
446  visualSlots[i].DrawOffset = Vector2.Zero;
447  }
448  }
449  }
450 
451  protected override void ControlInput(Camera cam)
452  {
453  base.ControlInput(cam);
454  // Ignore the background frame of this object in purpose, because it encompasses half of the screen.
455  if (highlightedSubInventorySlots.Any(i => i.Inventory != null && i.Inventory.BackgroundFrame.Contains(PlayerInput.MousePosition)))
456  {
457  cam.Freeze = true;
458  }
459  }
460 
461  private readonly static List<SlotReference> hideSubInventories = new List<SlotReference>();
462  private readonly static List<SlotReference> tempHighlightedSubInventorySlots = new List<SlotReference>();
463 
464  public override void Update(float deltaTime, Camera cam, bool isSubInventory = false)
465  {
466  if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner)
467  {
468  syncItemsDelay = Math.Max(syncItemsDelay - deltaTime, 0.0f);
469  doubleClickedItems.Clear();
470  return;
471  }
472 
473  base.Update(deltaTime, cam);
474 
475  bool hoverOnInventory = GUI.MouseOn == null &&
476  ((selectedSlot != null && selectedSlot.IsSubSlot) || (DraggingItems.Any() && (DraggingSlot == null || !DraggingSlot.MouseOn())));
477  if (CharacterHealth.OpenHealthWindow != null) { hoverOnInventory = true; }
478 
479  if (hoverOnInventory) { HideTimer = 0.5f; }
480  if (HideTimer > 0.0f) { HideTimer -= deltaTime; }
481 
482  UpdateSlotInput();
483 
484  hideSubInventories.Clear();
485  //remove highlighted subinventory slots that can no longer be accessed
486  highlightedSubInventorySlots.RemoveWhere(s =>
487  s.ParentInventory == this &&
488  ((s.SlotIndex < 0 || s.SlotIndex >= slots.Length || slots[s.SlotIndex] == null) || (Character.Controlled != null && !Character.Controlled.CanAccessInventory(s.Inventory))));
489  //remove highlighted subinventory slots that refer to items no longer in this inventory
490  highlightedSubInventorySlots.RemoveWhere(s => s.Item != null && s.ParentInventory == this && s.Item.ParentInventory != this);
491  //remove highlighted subinventory slots if we're dragging that item out of the inventory
492  highlightedSubInventorySlots.RemoveWhere(s => s.Item != null && s.ParentInventory == this && DraggingItems.Contains(s.Item));
493 
494  tempHighlightedSubInventorySlots.Clear();
495  tempHighlightedSubInventorySlots.AddRange(highlightedSubInventorySlots);
496  foreach (var highlightedSubInventorySlot in tempHighlightedSubInventorySlots)
497  {
498  if (highlightedSubInventorySlot.ParentInventory == this)
499  {
500  UpdateSubInventory(deltaTime, highlightedSubInventorySlot.SlotIndex, cam);
501  }
502 
503  if (!highlightedSubInventorySlot.Inventory.IsInventoryHoverAvailable(character, null)) continue;
504 
505  Rectangle hoverArea = GetSubInventoryHoverArea(highlightedSubInventorySlot);
506  if (highlightedSubInventorySlot.Inventory?.visualSlots == null || (!hoverArea.Contains(PlayerInput.MousePosition)))
507  {
508  hideSubInventories.Add(highlightedSubInventorySlot);
509  }
510  else
511  {
512  highlightedSubInventorySlot.Inventory.HideTimer = 1.0f;
513  }
514  }
515 
516  //activate the subinventory of the currently selected slot
517  if (selectedSlot?.ParentInventory == this)
518  {
519  var subInventory = GetSubInventory(selectedSlot.SlotIndex);
520  if (subInventory != null && subInventory.IsInventoryHoverAvailable(character, null))
521  {
522  selectedSlot.Inventory = subInventory;
523  if (!highlightedSubInventorySlots.Any(s => s.Inventory == subInventory))
524  {
525  ShowSubInventory(selectedSlot, deltaTime, cam, hideSubInventories, false);
526  }
527  }
528  }
529 
530  // In sub editor we cannot hover over the slot because they are not rendered so we override it here
531  if (Screen.Selected is SubEditorScreen subEditor && !subEditor.WiringMode)
532  {
533  for (int i = 0; i < visualSlots.Length; i++)
534  {
535  var subInventory = GetSubInventory(i);
536  if (subInventory != null)
537  {
538  ShowSubInventory(new SlotReference(this, visualSlots[i], i, false, subInventory), deltaTime, cam, hideSubInventories, true);
539  }
540  }
541  }
542 
543  foreach (var subInventorySlot in hideSubInventories)
544  {
545  if (subInventorySlot.Inventory == null) { continue; }
546  subInventorySlot.Inventory.HideTimer -= deltaTime;
547  if (subInventorySlot.Inventory.HideTimer < 0.25f)
548  {
549  highlightedSubInventorySlots.Remove(subInventorySlot);
550  }
551  }
552 
553  if (character == Character.Controlled && character.SelectedCharacter == null) // Permanently open subinventories only available when the default UI layout is in use -> not when grabbing characters
554  {
555  //remove the highlighted slots of other characters' inventories when not grabbing anyone
556  highlightedSubInventorySlots.RemoveWhere(s => s.ParentInventory != this && s.ParentInventory?.Owner is Character);
557 
558  for (int i = 0; i < capacity; i++)
559  {
560  var item = slots[i].FirstOrDefault();
561  if (item != null)
562  {
563  if (HideSlot(i)) { continue; }
564  if (character.HasEquippedItem(item)) // Keep a subinventory display open permanently when the container is equipped
565  {
566  var itemContainer = item.GetComponent<ItemContainer>();
567  if (itemContainer != null &&
568  itemContainer.KeepOpenWhenEquippedBy(character) &&
569  !DraggingItems.Contains(item) &&
570  character.CanAccessInventory(itemContainer.Inventory) &&
571  !highlightedSubInventorySlots.Any(s => s.Inventory == itemContainer.Inventory && s.SlotIndex == i))
572  {
573  ShowSubInventory(new SlotReference(this, visualSlots[i], i, false, itemContainer.Inventory), deltaTime, cam, hideSubInventories, true);
574  }
575  }
576  }
577  }
578  }
579 
580  if (doubleClickedItems.Any())
581  {
582  var quickUseAction = GetQuickUseAction(doubleClickedItems.First(), true, true, true);
583  int itemCount = 0;
584  foreach (Item doubleClickedItem in doubleClickedItems)
585  {
586  QuickUseItem(doubleClickedItem, true, true, true, quickUseAction, playSound: doubleClickedItem == doubleClickedItems.First());
587  itemCount++;
588  //only use one item if we're equipping or using it as a treatment
589  if (quickUseAction == QuickUseAction.Equip || quickUseAction == QuickUseAction.UseTreatment)
590  {
591  break;
592  }
593  //if the item was put in a limb slot, only put one item from the stack
594  if (doubleClickedItem.ParentInventory == this && !IsInLimbSlot(doubleClickedItem, InvSlotType.Any))
595  {
596  break;
597  }
598  //if putting an item to a container with a max stack size of 1, only put one item from the stack
599  if (quickUseAction == QuickUseAction.PutToContainer && (character.SelectedItem?.GetComponent<ItemContainer>()?.MaxStackSize ?? 0) <= 1)
600  {
601  break;
602  }
603  if ((quickUseAction == QuickUseAction.TakeFromContainer || quickUseAction == QuickUseAction.PutToEquippedItem) &&
604  doubleClickedItem.ParentInventory != null &&
605  itemCount >= doubleClickedItem.Prefab.GetMaxStackSize(doubleClickedItem.ParentInventory))
606  {
607  break;
608  }
609  }
610  }
611 
612  for (int i = 0; i < capacity; i++)
613  {
614  var item = slots[i].FirstOrDefault();
615  if (item != null)
616  {
617  var slot = visualSlots[i];
618  if (item.AllowedSlots.Any(a => a != InvSlotType.Any && a != InvSlotType.HealthInterface))
619  {
620  HandleButtonEquipStates(item, slot, deltaTime);
621  }
622  }
623  }
624 
625  //cancel dragging if too far away from the container of the dragged item
626  if (DraggingItems.Any())
627  {
628  var rootContainer = DraggingItems.First().RootContainer;
629  var rootInventory = DraggingItems.First().ParentInventory;
630 
631  if (rootContainer != null)
632  {
633  rootInventory = rootContainer.ParentInventory ?? rootContainer.GetComponent<ItemContainer>().Inventory;
634  }
635 
636  if (rootInventory != null &&
637  rootInventory.Owner != Character.Controlled &&
638  rootInventory.Owner != Character.Controlled.SelectedItem &&
639  rootInventory.Owner != Character.Controlled.SelectedCharacter)
640  {
641  //allow interacting if the container is linked to the item the character is interacting with
642  if (!(rootContainer != null &&
643  rootContainer.DisplaySideBySideWhenLinked &&
645  rootContainer.linkedTo.Contains(Character.Controlled.SelectedItem)))
646  {
647  DraggingItems.Clear();
648  }
649  }
650  }
651  doubleClickedItems.Clear();
652  }
653 
654  public void UpdateSlotInput()
655  {
656  for (int i = 0; i < capacity; i++)
657  {
658  var firstItem = slots[i].FirstOrDefault();
659  if (firstItem != null && !DraggingItems.Contains(firstItem) && Character.Controlled?.Inventory == this &&
660  GUI.KeyboardDispatcher.Subscriber == null && !CrewManager.IsCommandInterfaceOpen && PlayerInput.InventoryKeyHit(visualSlots[i].InventoryKeyIndex))
661  {
663 #if LINUX
664  // some window managers on Linux use windows key + number to change workspaces or perform other actions
665  if (PlayerInput.KeyDown(Keys.RightWindows) || PlayerInput.KeyDown(Keys.LeftWindows)) { continue; }
666 #endif
667  var quickUseAction = GetQuickUseAction(firstItem, true, false, true);
668  foreach (Item itemToUse in slots[i].Items.ToList())
669  {
670  QuickUseItem(itemToUse, true, true, true, quickUseAction, playSound: itemToUse == firstItem);
671  if (quickUseAction == QuickUseAction.Equip || quickUseAction == QuickUseAction.UseTreatment)
672  {
673  break;
674  }
675  }
676  }
677  }
678  }
679 
680  private void HandleButtonEquipStates(Item item, VisualSlot slot, float deltaTime)
681  {
685  {
687  }
688 
689  if (slot.EquipButtonState != GUIComponent.ComponentState.Hover)
690  {
691  slot.QuickUseTimer = Math.Max(0.0f, slot.QuickUseTimer - deltaTime * 5.0f);
692  return;
693  }
694 
695  var quickUseAction = GetQuickUseAction(item, allowEquip: true, allowInventorySwap: false, allowApplyTreatment: false);
696 
697  if (quickUseAction != QuickUseAction.Drop)
698  {
699  slot.QuickUseButtonToolTip = quickUseAction == QuickUseAction.None ?
700  "" : TextManager.GetWithVariable("QuickUseAction." + quickUseAction.ToString(), "[equippeditem]", character.HeldItems.FirstOrDefault()?.Name ?? item?.Name);
701  if (PlayerInput.PrimaryMouseButtonDown()) { slot.EquipButtonState = GUIComponent.ComponentState.Pressed; }
702  if (PlayerInput.PrimaryMouseButtonClicked())
703  {
704  QuickUseItem(item, allowEquip: true, allowInventorySwap: false, allowApplyTreatment: false);
705  }
706  }
707  }
708 
709  private void ShowSubInventory(SlotReference slotRef, float deltaTime, Camera cam, List<SlotReference> hideSubInventories, bool isEquippedSubInventory)
710  {
711  Rectangle hoverArea = GetSubInventoryHoverArea(slotRef);
712  if (isEquippedSubInventory && slotRef.Inventory is not ItemInventory { Container.MovableFrame: true, Container.KeepOpenWhenEquipped: true })
713  {
714  foreach (SlotReference highlightedSubInventorySlot in highlightedSubInventorySlots)
715  {
716  if (highlightedSubInventorySlot == slotRef) { continue; }
717  if (hoverArea.Intersects(GetSubInventoryHoverArea(highlightedSubInventorySlot)))
718  {
719  return; // If an equipped one intersects with a currently active hover one, do not open
720  }
721  }
722  }
723 
724  if (isEquippedSubInventory)
725  {
726  slotRef.Inventory.OpenState = 1.0f; // Reset animation when initially equipped
727  }
728 
729  highlightedSubInventorySlots.Add(slotRef);
730  slotRef.Inventory.HideTimer = 1f;
731  UpdateSubInventory(deltaTime, slotRef.SlotIndex, cam);
732 
733  //hide previously opened subinventories if this one overlaps with them
734  foreach (SlotReference highlightedSubInventorySlot in highlightedSubInventorySlots)
735  {
736  if (highlightedSubInventorySlot == slotRef) continue;
737  if (hoverArea.Intersects(GetSubInventoryHoverArea(highlightedSubInventorySlot)))
738  {
739  hideSubInventories.Add(highlightedSubInventorySlot);
740  highlightedSubInventorySlot.Inventory.HideTimer = 0.0f;
741  }
742  }
743 
744  HintManager.OnShowSubInventory(slotRef?.Item);
745  }
746 
747  public void AssignQuickUseNumKeys()
748  {
749  int keyBindIndex = 0;
750  for (int i = 0; i < visualSlots.Length; i++)
751  {
752  if (HideSlot(i)) continue;
753  if (SlotTypes[i] == InvSlotType.Any)
754  {
755  visualSlots[i].InventoryKeyIndex = keyBindIndex;
756  keyBindIndex++;
757  }
758  }
759  }
760 
761  private QuickUseAction GetQuickUseAction(Item item, bool allowEquip, bool allowInventorySwap, bool allowApplyTreatment)
762  {
763  if (allowApplyTreatment && CharacterHealth.OpenHealthWindow != null &&
764  //if the item can be equipped in the health interface slot, don't use it as a treatment but try to equip it
765  !item.AllowedSlots.Contains(InvSlotType.HealthInterface))
766  {
767  return QuickUseAction.UseTreatment;
768  }
769 
770  if (item.ParentInventory != this)
771  {
772  if (Screen.Selected == GameMain.GameScreen)
773  {
774  if (!item.IsInteractable(Character.Controlled))
775  {
776  return QuickUseAction.None;
777  }
778  }
779  if (item.ParentInventory == null || item.ParentInventory.Locked)
780  {
781  return QuickUseAction.None;
782  }
783  //in another inventory -> attempt to place in the character's inventory
784  else if (allowInventorySwap)
785  {
786  if (item.Container == null || character.Inventory.FindIndex(item.Container) == -1) // Not a subinventory in the character's inventory
787  {
788  if (character.HeldItems.Any(i => i.OwnInventory != null && i.OwnInventory.CanBePut(item) && character.CanAccessInventory(i.OwnInventory)))
789  {
790  return QuickUseAction.PutToEquippedItem;
791  }
792  else
793  {
794  return item.ParentInventory is CharacterInventory ? QuickUseAction.TakeFromCharacter : QuickUseAction.TakeFromContainer;
795  }
796  }
797  else
798  {
799  var selectedContainer = character.SelectedItem?.GetComponent<ItemContainer>();
800  if (selectedContainer != null &&
801  selectedContainer.Inventory != null &&
802  !selectedContainer.Inventory.Locked)
803  {
804  // Move the item from the subinventory to the selected container
805  return QuickUseAction.PutToContainer;
806  }
807  else if (character.Inventory.AccessibleWhenAlive || character.Inventory.AccessibleByOwner)
808  {
809  // Take from the subinventory and place it in the character's main inventory if no target container is selected
810  return QuickUseAction.TakeFromContainer;
811  }
812  }
813  }
814  }
815  else
816  {
817  var selectedContainer = character.SelectedItem?.GetComponent<ItemContainer>();
818 
819  if (selectedContainer != null &&
820  selectedContainer.Inventory != null &&
821  !selectedContainer.Inventory.Locked &&
822  selectedContainer.DrawInventory &&
823  allowInventorySwap)
824  {
825  //player has selected the inventory of another item -> attempt to move the item there
826  return QuickUseAction.PutToContainer;
827  }
828  else if (character.SelectedCharacter?.Inventory != null &&
829  !character.SelectedCharacter.Inventory.Locked &&
830  allowInventorySwap)
831  {
832  //player has selected the inventory of another character -> attempt to move the item there
833  return QuickUseAction.PutToCharacter;
834  }
835  else if (character.SelectedBy?.Inventory != null &&
836  Character.Controlled == character.SelectedBy &&
837  !character.SelectedBy.Inventory.Locked &&
839  allowInventorySwap)
840  {
841  return QuickUseAction.TakeFromCharacter;
842  }
843  else if (character.HeldItems.FirstOrDefault(i =>
844  i.OwnInventory != null &&
845  i.OwnInventory.Container.DrawInventory &&
846  character.CanAccessInventory(i.OwnInventory) &&
847  (i.OwnInventory.CanBePut(item) || ((i.OwnInventory.Capacity == 1 || i.OwnInventory.Container.HasSubContainers) && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))) is { } equippedContainer)
848  {
849  if (allowEquip)
850  {
851  if (!character.HasEquippedItem(item))
852  {
853  if (equippedContainer.GetComponent<ItemContainer>() is { QuickUseMovesItemsInside: false })
854  {
855  //put the item in a hand slot if that hand is free
856  if ((item.AllowedSlots.Contains(InvSlotType.RightHand) && character.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) == null) ||
857  (item.AllowedSlots.Contains(InvSlotType.LeftHand) && character.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand) == null))
858  {
859  return QuickUseAction.Equip;
860  }
861  }
862  }
863  //equipped -> attempt to unequip
864  else if (item.AllowedSlots.Contains(InvSlotType.Any))
865  {
866  return QuickUseAction.Unequip;
867  }
868  }
869  return QuickUseAction.PutToEquippedItem;
870  }
871  else if (allowEquip) //doubleclicked and no other inventory is selected
872  {
873  //not equipped -> attempt to equip
874  if (!character.HasEquippedItem(item) || item.GetComponents<Pickable>().Count() > 1)
875  {
876  return QuickUseAction.Equip;
877  }
878  //equipped -> attempt to unequip
879  else if (item.AllowedSlots.Contains(InvSlotType.Any))
880  {
881  return QuickUseAction.Unequip;
882  }
883  else
884  {
885  return QuickUseAction.Drop;
886  }
887  }
888  }
889 
890  return QuickUseAction.None;
891  }
892 
893  private void QuickUseItem(Item item, bool allowEquip, bool allowInventorySwap, bool allowApplyTreatment, QuickUseAction? action = null, bool playSound = true)
894  {
895  if (Screen.Selected is SubEditorScreen editor && !editor.WiringMode && !Submarine.Unloading)
896  {
897  // Find the slot the item was contained in and flash it
898  if (item.ParentInventory?.visualSlots != null)
899  {
900  var invSlots = item.ParentInventory.visualSlots;
901  for (int i = 0; i < invSlots.Length; i++)
902  {
903  if (i < 0 || invSlots.Length <= i || i < 0 || item.ParentInventory.Capacity <= i) { break; }
904 
905  var slot = invSlots[i];
906  if (item.ParentInventory.GetItemAt(i) == item)
907  {
908  slot.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.4f);
909  SoundPlayer.PlayUISound(GUISoundType.PickItem);
910  break;
911  }
912  }
913  }
914 
915  SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> { item }, true));
916 
917  item.Remove();
918  return;
919  }
920 
921  QuickUseAction quickUseAction = action ?? GetQuickUseAction(item, allowEquip, allowInventorySwap, allowApplyTreatment);
922  bool success = false;
923  switch (quickUseAction)
924  {
925  case QuickUseAction.Equip:
926  if (string.IsNullOrEmpty(item.Prefab.EquipConfirmationText) || character != Character.Controlled)
927  {
928  Equip();
929  }
930  else
931  {
932  if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "equipconfirmation")) { return; }
933  var equipConfirmation = new GUIMessageBox(string.Empty, TextManager.Get(item.Prefab.EquipConfirmationText),
934  new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") })
935  {
936  UserData = "equipconfirmation"
937  };
938  equipConfirmation.Buttons[0].OnClicked = (btn, userdata) =>
939  {
940  Equip();
941  equipConfirmation.Close();
942  return true;
943  };
944  equipConfirmation.Buttons[1].OnClicked = equipConfirmation.Close;
945  }
946 
947  void Equip()
948  {
949  //attempt to put in a free slot first
950  for (int i = capacity - 1; i >= 0; i--)
951  {
952  if (!slots[i].Empty()) { continue; }
953  if (SlotTypes[i] == InvSlotType.Any || !item.AllowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) { continue; }
954  success = TryPutItem(item, i, true, false, Character.Controlled, true);
955  if (success) { break; }
956  }
957 
958  if (!success)
959  {
960  for (int i = capacity - 1; i >= 0; i--)
961  {
962  if (SlotTypes[i] == InvSlotType.Any || !item.AllowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) { continue; }
963  // something else already equipped in a hand slot, attempt to unequip it so items aren't unnecessarily swapped to it
964  if (!slots[i].Empty() && slots[i].First().AllowedSlots.Contains(InvSlotType.Any) &&
965  (SlotTypes[i] == InvSlotType.LeftHand || SlotTypes[i] == InvSlotType.RightHand))
966  {
967  TryPutItem(slots[i].First(), Character.Controlled, new List<InvSlotType>() { InvSlotType.Any }, true);
968  }
969  success = TryPutItem(item, i, true, false, Character.Controlled, true);
970  if (success) { break; }
971  }
972  }
973  }
974  break;
975  case QuickUseAction.Unequip:
976  if (item.AllowedSlots.Contains(InvSlotType.Any))
977  {
978  success = TryPutItem(item, Character.Controlled, new List<InvSlotType>() { InvSlotType.Any }, true);
979  }
980  break;
981  case QuickUseAction.UseTreatment:
982  CharacterHealth.OpenHealthWindow?.OnItemDropped(item, ignoreMousePos: true);
983  return;
984  case QuickUseAction.Drop:
985  //do nothing, the item is dropped after a delay
986  return;
987  case QuickUseAction.PutToCharacter:
988  if (character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
989  {
990  //player has selected the inventory of another character -> attempt to move the item there
991  success = character.SelectedCharacter.Inventory.TryPutItem(item, Character.Controlled, item.AllowedSlots, true);
992  }
993  break;
994  case QuickUseAction.PutToContainer:
995  var selectedContainer = character.SelectedItem?.GetComponent<ItemContainer>();
996  if (selectedContainer != null && selectedContainer.Inventory != null)
997  {
998  //player has selected the inventory of another item -> attempt to move the item there
999  success = selectedContainer.Inventory.TryPutItem(item, Character.Controlled, item.AllowedSlots, true);
1000  }
1001  break;
1002  case QuickUseAction.TakeFromCharacter:
1003  if (character.SelectedBy != null && Character.Controlled == character.SelectedBy &&
1004  character.SelectedBy.Inventory != null)
1005  {
1006  //item is in the inventory of another character -> attempt to get the item from there
1007  success = character.SelectedBy.Inventory.TryPutItemWithAutoEquipCheck(item, Character.Controlled, item.AllowedSlots, true);
1008  }
1009  break;
1010  case QuickUseAction.TakeFromContainer:
1011  // Check open subinventories and put the item in it if equipped
1012  ItemInventory activeSubInventory = null;
1013  for (int i = 0; i < capacity; i++)
1014  {
1015  activeSubInventory = GetActiveEquippedSubInventory(i);
1016  if (activeSubInventory != null)
1017  {
1018  success = activeSubInventory.TryPutItem(item, Character.Controlled, item.AllowedSlots, true);
1019  break;
1020  }
1021  }
1022 
1023  // No subinventory found or placing unsuccessful -> attempt to put in the character's inventory
1024  if (!success)
1025  {
1026  success = TryPutItemWithAutoEquipCheck(item, Character.Controlled, item.AllowedSlots, true);
1027  }
1028  break;
1029  case QuickUseAction.PutToEquippedItem:
1030  //order by the condition of the contained item to prefer putting into the item with the emptiest ammo/battery/tank
1031  foreach (Item heldItem in character.HeldItems.OrderByDescending(heldItem => GetContainPriority(item, heldItem)))
1032  {
1033  if (heldItem.OwnInventory == null || !heldItem.OwnInventory.Container.DrawInventory) { continue; }
1034  //don't allow swapping if we're moving items into an item with 1 slot holding a stack of items
1035  //(in that case, the quick action should just fill up the stack)
1036  bool disallowSwapping =
1037  (heldItem.OwnInventory.Capacity == 1 || heldItem.OwnInventory.Container.HasSubContainers) &&
1038  heldItem.OwnInventory.GetItemAt(0)?.Prefab == item.Prefab &&
1039  heldItem.OwnInventory.GetItemsAt(0).Count() > 1;
1040  if (heldItem.OwnInventory.TryPutItem(item, Character.Controlled) ||
1041  ((heldItem.OwnInventory.Capacity == 1 || heldItem.OwnInventory.Container.HasSubContainers) && heldItem.OwnInventory.TryPutItem(item, 0, allowSwapping: !disallowSwapping, allowCombine: false, user: Character.Controlled)))
1042  {
1043  success = true;
1044  for (int j = 0; j < capacity; j++)
1045  {
1046  if (slots[j].Contains(heldItem)) { visualSlots[j].ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.4f); }
1047  }
1048  break;
1049  }
1050  }
1051  break;
1052 
1053  static float GetContainPriority(Item item, Item containerItem)
1054  {
1055  var container = containerItem.GetComponent<ItemContainer>();
1056  if (container == null) { return 0.0f; }
1057  for (int i = 0; i < container.Inventory.Capacity; i++)
1058  {
1059  var containedItems = container.Inventory.GetItemsAt(i);
1060  if (containedItems.Any() && container.Inventory.CanBePutInSlot(item, i))
1061  {
1062  //if there's a stack in the contained item that we can add the item to, prefer that
1063  return 10.0f;
1064  }
1065  }
1066  return -container.GetContainedIndicatorState();
1067  }
1068  }
1069 
1070  if (success)
1071  {
1072  for (int i = 0; i < capacity; i++)
1073  {
1074  if (slots[i].Contains(item)) { visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.4f); }
1075  }
1076  }
1077 
1078  DraggingItems.Clear();
1079  if (playSound)
1080  {
1081  SoundPlayer.PlayUISound(success ? GUISoundType.PickItem : GUISoundType.PickItemFail);
1082  }
1083  }
1084 
1086  {
1087  if (item == null) { return false; }
1088  foreach (var allowedSlot in item.AllowedSlots)
1089  {
1090  InvSlotType slotsFree = InvSlotType.None;
1091  for (int i = 0; i < slots.Length; i++)
1092  {
1093  if (allowedSlot.HasFlag(SlotTypes[i]) && slots[i].Empty()) { slotsFree |= SlotTypes[i]; }
1094  }
1095  if (allowedSlot == slotsFree) { return true; }
1096  }
1097  return false;
1098  }
1099 
1103  public void FlashAllowedSlots(Item item, Color color)
1104  {
1105  if (item == null || visualSlots == null) { return; }
1106  bool flashed = false;
1107  foreach (var allowedSlot in item.AllowedSlots)
1108  {
1109  for (int i = 0; i < slots.Length; i++)
1110  {
1111  if (allowedSlot.HasFlag(SlotTypes[i]))
1112  {
1113  visualSlots[i].ShowBorderHighlight(color, 0.1f, 0.9f);
1114  flashed = true;
1115  }
1116  }
1117  }
1118  if (flashed)
1119  {
1120  SoundPlayer.PlayUISound(GUISoundType.PickItemFail);
1121  }
1122  }
1123 
1124 
1125  public void DrawOwn(SpriteBatch spriteBatch)
1126  {
1127  if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner) { return; }
1128  if (capacity == 0) { return; }
1129  if (visualSlots == null) { CreateSlots(); }
1130  if (GameMain.GraphicsWidth != screenResolution.X ||
1131  GameMain.GraphicsHeight != screenResolution.Y ||
1132  prevUIScale != UIScale ||
1133  prevHUDScale != GUI.Scale)
1134  {
1135  CreateSlots();
1136  SetSlotPositions(layout);
1137  prevUIScale = UIScale;
1138  prevHUDScale = GUI.Scale;
1139  }
1140 
1141  if (layout == Layout.Center)
1142  {
1144  GUI.DrawRectangle(spriteBatch, BackgroundFrame, Color.Black * 0.8f, true);
1145  GUI.DrawString(spriteBatch,
1146  new Vector2((int)(BackgroundFrame.Center.X - GUIStyle.Font.MeasureString(character.Name).X / 2), (int)BackgroundFrame.Y + 5),
1147  character.Name, Color.White * 0.9f);
1148  }
1149 
1150  for (int i = 0; i < capacity; i++)
1151  {
1152  if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
1153 
1154  //don't draw the item if it's being dragged out of the slot
1155  bool drawItem = !DraggingItems.Any() || !slots[i].Items.All(it => DraggingItems.Contains(it)) || visualSlots[i].MouseOn();
1156 
1157  DrawSlot(spriteBatch, this, visualSlots[i], slots[i].FirstOrDefault(), i, drawItem, SlotTypes[i]);
1158  }
1159 
1160  VisualSlot highlightedQuickUseSlot = null;
1161  Rectangle inventoryArea = Rectangle.Empty;
1162 
1163  for (int i = 0; i < capacity; i++)
1164  {
1165  if (HideSlot(i)) { continue; }
1166 
1167  inventoryArea = inventoryArea == Rectangle.Empty ? visualSlots[i].InteractRect : Rectangle.Union(inventoryArea, visualSlots[i].InteractRect);
1168 
1169  if (slots[i].Empty() ||
1170  (DraggingItems.Any(it => slots[i].Contains(it)) && !visualSlots[i].InteractRect.Contains(PlayerInput.MousePosition)) ||
1171  !slots[i].First().AllowedSlots.Any(a => a != InvSlotType.Any))
1172  {
1173  //draw limb icons on empty slots
1174  if (LimbSlotIcons.ContainsKey(SlotTypes[i]))
1175  {
1176  var icon = LimbSlotIcons[SlotTypes[i]];
1177  icon.Draw(spriteBatch, visualSlots[i].Rect.Center.ToVector2() + visualSlots[i].DrawOffset, GUIStyle.EquipmentSlotIconColor, origin: icon.size / 2, scale: visualSlots[i].Rect.Width / icon.size.X);
1178  }
1179  continue;
1180  }
1181  if (DraggingItems.Any(it => slots[i].Contains(it)) && !visualSlots[i].IsHighlighted) { continue; }
1182 
1183  //draw hand icons if the item is equipped in a hand slot
1184  if (IsInLimbSlot(slots[i].First(), InvSlotType.LeftHand))
1185  {
1186  var icon = LimbSlotIcons[InvSlotType.LeftHand];
1187  icon.Draw(spriteBatch, new Vector2(visualSlots[i].Rect.X, visualSlots[i].Rect.Bottom) + visualSlots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.35f, icon.size.Y * 0.75f), scale: visualSlots[i].Rect.Width / icon.size.X * 0.7f);
1188  }
1189  if (IsInLimbSlot(slots[i].First(), InvSlotType.RightHand))
1190  {
1191  var icon = LimbSlotIcons[InvSlotType.RightHand];
1192  icon.Draw(spriteBatch, new Vector2(visualSlots[i].Rect.Right, visualSlots[i].Rect.Bottom) + visualSlots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.65f, icon.size.Y * 0.75f), scale: visualSlots[i].Rect.Width / icon.size.X * 0.7f);
1193  }
1194 
1196  if (state == GUIComponent.ComponentState.Hover)
1197  {
1198  highlightedQuickUseSlot = visualSlots[i];
1199  }
1200 
1201  if (slots[i].First().AllowedSlots.Count() == 1 || SlotTypes[i] == InvSlotType.HealthInterface)
1202  {
1203  continue;
1204  }
1205 
1206  Color color = Color.White;
1207  if (Locked)
1208  {
1209  color *= 0.5f;
1210  }
1211 
1212  Vector2 indicatorScale = new Vector2(
1213  visualSlots[i].EquipButtonRect.Size.X / EquippedIndicator.size.X,
1214  visualSlots[i].EquipButtonRect.Size.Y / EquippedIndicator.size.Y);
1215 
1216  bool isEquipped = character.HasEquippedItem(slots[i].First());
1217  var sprite = state switch
1218  {
1220  => isEquipped ? EquippedIndicator : UnequippedIndicator,
1222  => isEquipped ? EquippedHoverIndicator : UnequippedHoverIndicator,
1224  or GUIComponent.ComponentState.Selected
1225  or GUIComponent.ComponentState.HoverSelected
1226  => isEquipped ? EquippedClickedIndicator : UnequippedClickedIndicator,
1227  _ => throw new NotImplementedException()
1228  };
1229  sprite.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, indicatorScale);
1230  }
1231 
1232  if (Locked)
1233  {
1234  GUI.DrawRectangle(spriteBatch, inventoryArea, new Color(30,30,30,100), isFilled: true);
1235  var lockIcon = GUIStyle.GetComponentStyle("LockIcon")?.GetDefaultSprite();
1236  lockIcon?.Draw(spriteBatch, inventoryArea.Center.ToVector2(), scale: Math.Min(inventoryArea.Height / lockIcon.size.Y * 0.7f, 1.0f));
1237  if (inventoryArea.Contains(PlayerInput.MousePosition) && character.LockHands)
1238  {
1239  GUIComponent.DrawToolTip(spriteBatch, TextManager.Get("handcuffed"), new Rectangle(inventoryArea.Center - new Point(inventoryArea.Height / 2), new Point(inventoryArea.Height)));
1240  }
1241  }
1242  else if (highlightedQuickUseSlot != null && !highlightedQuickUseSlot.QuickUseButtonToolTip.IsNullOrEmpty())
1243  {
1244  GUIComponent.DrawToolTip(spriteBatch, highlightedQuickUseSlot.QuickUseButtonToolTip, highlightedQuickUseSlot.EquipButtonRect);
1245  }
1246  }
1247 
1249  {
1250  SharedWrite(msg, extraData.SlotRange);
1251  syncItemsDelay = 1.0f;
1252  }
1253  }
1254 }
bool Freeze
Resets to false each time the MoveCamera method is called.
Definition: Camera.cs:253
bool HasEquippedItem(Item item, InvSlotType? slotType=null, Func< InvSlotType, bool > predicate=null)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
bool CanAccessInventory(Inventory inventory, CharacterInventory.AccessLevel accessLevel=CharacterInventory.AccessLevel.Limited)
IEnumerable< Item >?? HeldItems
Items the character has in their hand slots. Doesn't return nulls and only returns items held in both...
void FlashAllowedSlots(Item item, Color color)
Flash the slots the item is allowed to go in (not taking into account whether there's already somethi...
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
bool AccessibleWhenAlive
Can the inventory be accessed when the character is still alive
bool AccessibleByOwner
Can the inventory be accessed by the character itself when the character is still alive (only has an ...
void ClientEventWrite(IWriteMessage msg, Character.InventoryStateEventData extraData)
bool TryPutItemWithAutoEquipCheck(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true)
If there is no room in the generic inventory (InvSlotType.Any), check if the item can be auto-equippe...
CharacterInventory(ContentXElement element, Character character, bool spawnInitialItems)
override void Update(float deltaTime, Camera cam, bool isSubInventory=false)
Responsible for keeping track of the characters in the player crew, saving and loading their orders,...
virtual Rectangle Rect
void DrawToolTip(SpriteBatch spriteBatch)
Creates and draws a tooltip.
static int GraphicsWidth
Definition: GameMain.cs:162
static SubEditorScreen SubEditorScreen
Definition: GameMain.cs:68
static int GraphicsHeight
Definition: GameMain.cs:168
readonly int capacity
Capacity, or the number of slots in the inventory.
Item FirstOrDefault()
Return the first item in the inventory, or null if the inventory is empty.
static void DrawSlot(SpriteBatch spriteBatch, Inventory inventory, VisualSlot slot, Item item, int slotIndex, bool drawItem=true, InvSlotType type=InvSlotType.Any)
bool Contains(Item item)
Is the item contained in this inventory. Does not recursively check items inside items.
void UpdateSubInventory(float deltaTime, int slotIndex, Camera cam)
void SharedWrite(IWriteMessage msg, Range slotRange)
static HashSet< SlotReference > highlightedSubInventorySlots
int FindIndex(Item item)
Find the index of the first slot the item is contained in.
static Rectangle GetSubInventoryHoverArea(SlotReference subSlot)
IEnumerable< Item > GetItemsAt(int index)
Get all the item stored in the specified inventory slot. Can return more than one item if the slot co...
IEnumerable< InvSlotType > AllowedSlots
bool IsInteractable(Character character)
Returns interactibility based on whether the character is on a player team
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
static bool KeyDown(InputType inputType)
void MoveBorderHighlight(VisualSlot newSlot)
Moves the current border highlight animation (if one is running) to the new slot
void ShowBorderHighlight(Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount=0.5f)
GUISoundType
Definition: GUI.cs:21