2 using System.Collections.Generic;
3 using System.Collections.Immutable;
4 using System.Diagnostics;
8 using Microsoft.Xna.Framework;
12 internal readonly
struct InventorySlotItem
14 public readonly
int Slot;
17 public InventorySlotItem(
int slot, Item item)
23 public void Deconstruct(out
int slot, out Item item)
30 internal abstract partial class Command
32 public abstract LocalizedString GetDescription();
40 internal class TransformCommand :
Command
42 private readonly List<MapEntity> Receivers;
43 private readonly List<Rectangle> NewData;
44 private readonly List<Rectangle> OldData;
45 private readonly
bool Resized;
57 public TransformCommand(List<MapEntity> receivers, List<Rectangle> newData, List<Rectangle> oldData,
bool resized)
59 Receivers = receivers;
65 public override void Execute() => SetRects(NewData);
66 public override void UnExecute() => SetRects(OldData);
68 public override void Cleanup()
75 private void SetRects(IReadOnlyList<Rectangle> rects)
77 if (Receivers.Count != rects.Count)
79 DebugConsole.ThrowError($
"Receivers.Count did not match Rects.Count ({Receivers.Count} vs {rects.Count}).");
83 for (
int i = 0; i < rects.Count; i++)
85 MapEntity entity = Receivers[i].GetReplacementOrThis();
87 Vector2 diff = Rect.Location.ToVector2() - entity.Rect.Location.ToVector2();
93 public override LocalizedString GetDescription()
97 return TextManager.GetWithVariable(
"Undo.ResizedItem",
"[item]", Receivers.FirstOrDefault()?.Name);
100 return Receivers.Count > 1
101 ? TextManager.GetWithVariable(
"Undo.MovedItemsMultiple",
"[count]", Receivers.Count.ToString())
102 : TextManager.GetWithVariable(
"Undo.MovedItem",
"[item]", Receivers.FirstOrDefault()?.Name);
112 internal class AddOrDeleteCommand :
Command
114 private readonly Dictionary<InventorySlotItem, Inventory> PreviousInventories =
new Dictionary<InventorySlotItem, Inventory>();
115 public readonly List<MapEntity> Receivers;
116 private readonly List<MapEntity> CloneList;
117 private readonly
bool WasDeleted;
118 private readonly List<AddOrDeleteCommand> ContainedItemsCommand =
new List<AddOrDeleteCommand>();
121 private readonly List<XElement> CircuitBoxData =
new List<XElement>();
129 public AddOrDeleteCommand(List<MapEntity> receivers,
bool wasDeleted,
bool handleInventoryBehavior =
true)
131 Debug.Assert(receivers.Count > 0,
"Command has 0 receivers");
132 WasDeleted = wasDeleted;
133 Receivers =
new List<MapEntity>(receivers);
137 foreach (MapEntity receiver
in receivers)
139 if (receiver is Item { ParentInventory: not
null } it)
141 PreviousInventories.Add(
new InventorySlotItem(it.ParentInventory.FindIndex(it), it), it.ParentInventory);
145 List<MapEntity> clonedTargets = MapEntity.Clone(receivers);
147 List<MapEntity> itemsToDelete =
new List<MapEntity>();
148 foreach (MapEntity receiver
in Receivers)
150 if (receiver is not Item it) {
continue; }
152 foreach (var cb
in it.GetComponents<CircuitBox>())
154 CircuitBoxData.Add(cb.Save(
new XElement(
"root")));
159 if (component.
Inventory ==
null) {
continue; }
160 itemsToDelete.AddRange(component.
Inventory.
AllItems.Where(
static item => !item.Removed));
164 if (itemsToDelete.Any() && handleInventoryBehavior)
166 ContainedItemsCommand.Add(
new AddOrDeleteCommand(itemsToDelete, wasDeleted));
169 foreach (MapEntity item
in itemsToDelete)
171 if (item !=
null && !item.Removed)
179 foreach (MapEntity clone
in clonedTargets)
181 clone.ShallowRemove();
182 if (clone is Item it)
191 CloneList = clonedTargets;
196 Receivers =
new List<MapEntity>();
197 CloneList =
new List<MapEntity>();
198 DebugConsole.ThrowError(
"Could not store object", e);
202 public override void Execute()
205 public override void UnExecute()
208 private void Process(
bool redo)
210 var items = DeleteUndelete(redo);
211 foreach (var cmd
in ContainedItemsCommand)
215 ApplyCircuitBoxDataIfAny(items);
228 private void ApplyCircuitBoxDataIfAny(ImmutableArray<Item> items)
231 foreach (var newItem
in items)
235 if (component is not CircuitBox cb) {
continue; }
237 if (cbIndex < 0 || cbIndex >= CircuitBoxData.Count)
239 DebugConsole.ThrowError(
"Unable to restore wiring in circuit box, index out of range.");
243 var cbData = CircuitBoxData[cbIndex];
246 cb.LoadFromXML(
new ContentXElement(
null, cbData));
251 public override void Cleanup()
253 foreach (MapEntity entity
in CloneList)
263 PreviousInventories?.Clear();
264 ContainedItemsCommand?.ForEach(
static cmd => cmd.Cleanup());
265 CircuitBoxData.Clear();
268 private ImmutableArray<Item> DeleteUndelete(
bool redo)
270 bool wasDeleted = WasDeleted;
273 if (redo) { wasDeleted = !wasDeleted; }
276 var builder = ImmutableArray.CreateBuilder<
Item>();
280 Debug.Assert(Receivers.All(
static entity => entity.GetReplacementOrThis().Removed),
"Tried to redo a deletion but some items were not deleted");
282 List<MapEntity> clones = MapEntity.Clone(CloneList);
283 int length = Math.Min(Receivers.Count, clones.Count);
284 for (
int i = 0; i < length; i++)
286 MapEntity clone = clones[i],
287 receiver = Receivers[i];
289 if (receiver.GetReplacementOrThis() is
Item item && clone is
Item cloneItem)
291 builder.Add(cloneItem);
294 int index = item.GetComponentIndex(ic);
295 ItemComponent component = cloneItem.Components.ElementAtOrDefault(index);
313 for (
int i = 0; i < length; i++)
315 MapEntity clone = clones[i],
316 receiver = Receivers[i];
318 if (clone is Item it)
320 foreach (var (slotRef, inventory) in PreviousInventories)
322 if (slotRef.Item == receiver)
324 inventory.GetReplacementOrThiS().TryPutItem(it, slotRef.Slot,
false,
false,
null, createNetworkEvent:
false);
330 foreach (MapEntity clone
in clones)
335 return builder.ToImmutable();
339 foreach (MapEntity t
in Receivers)
341 MapEntity receiver = t.GetReplacementOrThis();
342 if (!receiver.Removed)
348 return builder.ToImmutable();
352 public void MergeInto(AddOrDeleteCommand master)
354 master.Receivers.AddRange(Receivers);
355 master.CloneList.AddRange(CloneList);
356 master.ContainedItemsCommand.AddRange(ContainedItemsCommand);
357 foreach (var (slot, item) in PreviousInventories)
359 master.PreviousInventories.Add(slot, item);
363 public override LocalizedString GetDescription()
367 return Receivers.Count > 1
368 ? TextManager.GetWithVariable(
"Undo.RemovedItemsMultiple",
"[count]", Receivers.Count.ToString())
369 : TextManager.GetWithVariable(
"Undo.RemovedItem",
"[item]", Receivers.FirstOrDefault()?.Name ??
"null");
372 return Receivers.Count > 1
373 ? TextManager.GetWithVariable(
"Undo.AddedItemsMultiple",
"[count]", Receivers.Count.ToString())
374 : TextManager.GetWithVariable(
"Undo.AddedItem",
"[item]", Receivers.FirstOrDefault()?.Name ??
"null");
383 internal class InventoryPlaceCommand :
Command
385 private readonly Inventory Inventory;
386 private readonly List<InventorySlotItem> Receivers;
387 private readonly
bool wasDropped;
389 public InventoryPlaceCommand(Inventory inventory, List<Item> items,
bool dropped)
391 Inventory = inventory;
392 Receivers = items.Select(item =>
new InventorySlotItem(inventory.FindIndex(item), item)).ToList();
393 wasDropped = dropped;
396 public override void Execute() => ContainUncontain(
false);
397 public override void UnExecute() => ContainUncontain(
true);
399 public override void Cleanup()
404 private void ContainUncontain(
bool drop)
407 if (wasDropped) { drop = !drop; }
409 foreach (var (slot, receiver) in Receivers)
411 Item item = (
Item) receiver.GetReplacementOrThis();
415 item.Drop(
null, createNetworkEvent:
false);
419 Inventory.GetReplacementOrThiS().TryPutItem(item, slot,
false,
false,
null, createNetworkEvent:
false);
424 public override LocalizedString GetDescription()
428 return TextManager.GetWithVariable(
"Undo.DroppedItem",
"[item]", Receivers.FirstOrDefault().Item.Name);
431 string container =
"[ERROR]";
433 if (Inventory.Owner is Item item)
435 container = item.Name;
438 return Receivers.Count > 1
439 ? TextManager.GetWithVariables(
"Undo.ContainedItemsMultiple", (
"[count]", Receivers.Count.ToString()), (
"[container]", container))
440 : TextManager.GetWithVariables(
"Undo.ContainedItem", (
"[item]", Receivers.FirstOrDefault().Item.Name), (
"[container]", container));
447 internal class PropertyCommand :
Command
449 private Dictionary<object, List<ISerializableEntity>> OldProperties;
450 private readonly List<ISerializableEntity> Receivers;
451 private readonly Identifier PropertyName;
452 private readonly
object NewProperties;
453 private string sanitizedProperty;
455 public readonly
int PropertyCount;
464 public PropertyCommand(List<ISerializableEntity> receivers, Identifier propertyName,
object newData, Dictionary<
object, List<ISerializableEntity>> oldData)
466 Receivers = receivers;
467 PropertyName = propertyName;
468 OldProperties = oldData;
469 NewProperties = newData;
470 PropertyCount = receivers.Count;
474 public PropertyCommand(ISerializableEntity receiver, Identifier propertyName,
object newData,
object oldData)
476 Receivers =
new List<ISerializableEntity> { receiver };
477 PropertyName = propertyName;
478 OldProperties =
new Dictionary<object, List<ISerializableEntity>> { { oldData, Receivers } };
479 NewProperties = newData;
484 public bool MergeInto(PropertyCommand master)
486 if (!master.Receivers.SequenceEqual(Receivers)) {
return false; }
487 master.OldProperties = OldProperties;
491 private void SanitizeProperty()
493 sanitizedProperty = NewProperties
switch
495 float f => f.FormatSingleDecimal(),
496 Point point => XMLExtensions.PointToString(point),
497 Vector2 vector2 => vector2.FormatZeroDecimal(),
498 Vector3 vector3 => vector3.FormatSingleDecimal(),
499 Vector4 vector4 => vector4.FormatSingleDecimal(),
500 Color color => XMLExtensions.ColorToString(color),
501 Rectangle rectangle => XMLExtensions.RectToString(rectangle),
502 _ => NewProperties.ToString()
506 public override void Execute() => SetProperties(
false);
507 public override void UnExecute() => SetProperties(
true);
509 public override void Cleanup()
512 OldProperties.Clear();
515 private void SetProperties(
bool undo)
517 foreach (ISerializableEntity t
in Receivers)
519 ISerializableEntity receiver;
522 case MapEntity me when me.GetReplacementOrThis() is ISerializableEntity sEntity:
526 receiver = sItemComponent;
533 object data = NewProperties;
537 foreach (var (key, value) in OldProperties)
539 if (value.Contains(t)) { data = key; }
543 if (receiver.SerializableProperties !=
null)
547 if (props.TryGetValue(PropertyName, out SerializableProperty prop))
549 prop.TrySetValue(receiver, data);
551 if (MapEntity.EditingHUD ==
null || (MapEntity.EditingHUD.UserData != receiver && (receiver is
ItemComponent ic && MapEntity.EditingHUD.UserData != ic.
Item))) {
continue; }
553 GUIListBox list = MapEntity.EditingHUD.GetChild<GUIListBox>();
554 if (list ==
null) {
continue; }
556 IEnumerable<SerializableEntityEditor> editors = list.Content.FindChildren(comp => comp is SerializableEntityEditor).Cast<SerializableEntityEditor>();
557 SerializableEntityEditor.LockEditing =
true;
558 foreach (SerializableEntityEditor editor
in editors)
560 if (editor.UserData == receiver && editor.Fields.TryGetValue(PropertyName, out GUIComponent[] _))
562 editor.UpdateValue(prop, data);
566 SerializableEntityEditor.LockEditing =
false;
572 public override LocalizedString GetDescription()
574 return Receivers.Count > 1
575 ? TextManager.GetWithVariables(
"Undo.ChangedPropertyMultiple",
576 (
"[property]", PropertyName.Value),
577 (
"[count]", Receivers.Count.ToString()),
578 (
"[value]", sanitizedProperty))
579 : TextManager.GetWithVariables(
"Undo.ChangedProperty",
580 (
"[property]", PropertyName.Value),
581 (
"[item]", Receivers.FirstOrDefault()?.Name),
582 (
"[value]", sanitizedProperty));
591 internal class InventoryMoveCommand :
Command
593 private readonly Inventory oldInventory;
594 private readonly Inventory newInventory;
595 private readonly
int oldSlot;
596 private readonly
int newSlot;
597 private readonly
Item targetItem;
599 public InventoryMoveCommand(Inventory oldInventory, Inventory newInventory, Item item,
int oldSlot,
int newSlot)
601 this.newInventory = newInventory;
602 this.oldInventory = oldInventory;
603 this.oldSlot = oldSlot;
604 this.newSlot = newSlot;
608 public override void Execute()
610 if (targetItem.GetReplacementOrThis() is Item item)
612 newInventory?.GetReplacementOrThiS().TryPutItem(item, newSlot,
true,
false,
null, createNetworkEvent:
false);
616 public override void UnExecute()
618 if (targetItem.GetReplacementOrThis() is Item item)
620 oldInventory?.GetReplacementOrThiS().TryPutItem(item, oldSlot,
true,
false,
null, createNetworkEvent:
false);
624 public override void Cleanup() { }
626 public override LocalizedString GetDescription()
628 return TextManager.GetWithVariable(
"Undo.MovedItem",
"[item]", targetItem.Name);
Inventory(Entity owner, int capacity, int slotsPerRow=5)
Inventory GetReplacementOrThiS()
void DeleteAllItems()
Deletes all items inside the inventory (and also recursively all items inside the items)
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
The base class for components holding the different functionalities of the item
Dictionary< Identifier, SerializableProperty > SerializableProperties
ItemComponent GetReplacementOrThis()
readonly ItemInventory Inventory