v0.12.0.2
This commit is contained in:
@@ -14,7 +14,7 @@ namespace Barotrauma
|
||||
|
||||
partial class CharacterInventory : Inventory
|
||||
{
|
||||
private Character character;
|
||||
private readonly Character character;
|
||||
|
||||
public InvSlotType[] SlotTypes
|
||||
{
|
||||
@@ -33,8 +33,14 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
private static string[] ParseSlotTypes(XElement element)
|
||||
{
|
||||
string slotString = element.GetAttributeString("slots", null);
|
||||
return slotString == null ? new string[0] : slotString.Split(',');
|
||||
}
|
||||
|
||||
public CharacterInventory(XElement element, Character character)
|
||||
: base(character, element.GetAttributeString("slots", "").Split(',').Count())
|
||||
: base(character, ParseSlotTypes(element).Length)
|
||||
{
|
||||
this.character = character;
|
||||
IsEquipped = new bool[capacity];
|
||||
@@ -42,7 +48,7 @@ namespace Barotrauma
|
||||
|
||||
AccessibleWhenAlive = element.GetAttributeBool("accessiblewhenalive", true);
|
||||
|
||||
string[] slotTypeNames = element.GetAttributeString("slots", "").Split(',');
|
||||
string[] slotTypeNames = ParseSlotTypes(element);
|
||||
System.Diagnostics.Debug.Assert(slotTypeNames.Length == capacity);
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
@@ -56,11 +62,9 @@ namespace Barotrauma
|
||||
SlotTypes[i] = parsedSlotType;
|
||||
switch (SlotTypes[i])
|
||||
{
|
||||
//case InvSlotType.Head:
|
||||
//case InvSlotType.OuterClothes:
|
||||
case InvSlotType.LeftHand:
|
||||
case InvSlotType.RightHand:
|
||||
hideEmptySlot[i] = true;
|
||||
slots[i].HideIfEmpty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -83,7 +87,7 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
Entity.Spawner?.AddToSpawnQueue(itemPrefab, this);
|
||||
Entity.Spawner?.AddToSpawnQueue(itemPrefab, this, ignoreLimbSlots: subElement.GetAttributeBool("forcetoslot", false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,18 +95,28 @@ namespace Barotrauma
|
||||
|
||||
public int FindLimbSlot(InvSlotType limbSlot)
|
||||
{
|
||||
for (int i = 0; i < Items.Length; i++)
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
if (SlotTypes[i] == limbSlot) { return i; }
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public Item GetItemInLimbSlot(InvSlotType limbSlot)
|
||||
{
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
if (SlotTypes[i] == limbSlot) { return slots[i].FirstOrDefault(); }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public bool IsInLimbSlot(Item item, InvSlotType limbSlot)
|
||||
{
|
||||
for (int i = 0; i < Items.Length; i++)
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
if (Items[i] == item && SlotTypes[i] == limbSlot) { return true; }
|
||||
if (SlotTypes[i] == limbSlot && slots[i].Contains(item)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -118,9 +132,9 @@ namespace Barotrauma
|
||||
foreach (var allowedSlot in item.AllowedSlots)
|
||||
{
|
||||
InvSlotType slotsFree = InvSlotType.None;
|
||||
for (int i = 0; i < Items.Length; i++)
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] == null) { slotsFree |= SlotTypes[i]; }
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && slots[i].Empty()) { slotsFree |= SlotTypes[i]; }
|
||||
}
|
||||
if (allowedSlot == slotsFree) { return true; }
|
||||
}
|
||||
@@ -130,7 +144,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// If there is no room in the generic inventory (InvSlotType.Any), check if the item can be auto-equipped into its respective limbslot
|
||||
/// </summary>
|
||||
public bool TryPutItemWithAutoEquipCheck(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
|
||||
public bool TryPutItemWithAutoEquipCheck(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
|
||||
{
|
||||
// Does not auto-equip the item if specified and no suitable any slot found (for example handcuffs are not auto-equipped)
|
||||
if (item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
@@ -148,7 +162,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// If there is room, puts the item in the inventory and returns true, otherwise returns false
|
||||
/// </summary>
|
||||
public override bool TryPutItem(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
|
||||
public override bool TryPutItem(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
|
||||
{
|
||||
if (allowedSlots == null || !allowedSlots.Any()) { return false; }
|
||||
if (item == null)
|
||||
@@ -174,7 +188,7 @@ namespace Barotrauma
|
||||
int currentSlot = -1;
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (Items[i] == item)
|
||||
if (slots[i].Contains(item))
|
||||
{
|
||||
currentSlot = i;
|
||||
if (allowedSlots.Any(a => a.HasFlag(SlotTypes[i])))
|
||||
@@ -208,18 +222,18 @@ namespace Barotrauma
|
||||
bool free = true;
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) && Items[i] != null && Items[i] != item)
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) && slots[i].Items.Any(it => it != item))
|
||||
{
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
if (!Items[i].AllowedSlots.Contains(InvSlotType.Any) || !TryPutItem(Items[i], character, new List<InvSlotType> { InvSlotType.Any }, true))
|
||||
if (!slots[i].First().AllowedSlots.Contains(InvSlotType.Any) || !TryPutItem(slots[i].FirstOrDefault(), character, new List<InvSlotType> { InvSlotType.Any }, true))
|
||||
{
|
||||
free = false;
|
||||
#if CLIENT
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (slots != null && Items[j] == Items[i]) slots[j].ShowBorderHighlight(GUI.Style.Red, 0.1f, 0.9f);
|
||||
if (visualSlots != null && slots[j] == slots[i]) { visualSlots[j].ShowBorderHighlight(GUI.Style.Red, 0.1f, 0.9f); }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -230,7 +244,7 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) && Items[i] == null)
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) && slots[i].Empty())
|
||||
{
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
|
||||
@@ -238,7 +252,7 @@ namespace Barotrauma
|
||||
bool removeFromOtherSlots = item.ParentInventory != this;
|
||||
if (placedInSlot == -1 && inWrongSlot)
|
||||
{
|
||||
if (!hideEmptySlot[i] || SlotTypes[currentSlot] != InvSlotType.Any) removeFromOtherSlots = true;
|
||||
if (!slots[i].HideIfEmpty || SlotTypes[currentSlot] != InvSlotType.Any) { removeFromOtherSlots = true; }
|
||||
}
|
||||
|
||||
PutItem(item, i, user, removeFromOtherSlots, createNetworkEvent);
|
||||
@@ -254,35 +268,51 @@ namespace Barotrauma
|
||||
|
||||
public int CheckIfAnySlotAvailable(Item item, bool inWrongSlot)
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
//attempt to stack first
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (SlotTypes[i] != InvSlotType.Any) { continue; }
|
||||
if (!slots[i].Empty() && CanBePut(item, i))
|
||||
{
|
||||
if (SlotTypes[i] != InvSlotType.Any) continue;
|
||||
if (Items[i] == item)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (SlotTypes[i] != InvSlotType.Any) continue;
|
||||
if (inWrongSlot)
|
||||
{
|
||||
if (Items[i] != item && Items[i] != null) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Items[i] != null) continue;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
}
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (SlotTypes[i] != InvSlotType.Any) { continue; }
|
||||
if (slots[i].Contains(item))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (SlotTypes[i] != InvSlotType.Any) { continue; }
|
||||
if (CanBePut(item, i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (SlotTypes[i] != InvSlotType.Any) { continue; }
|
||||
if (inWrongSlot)
|
||||
{
|
||||
//another item already in the slot
|
||||
if (slots[i].Any() && slots[i].Items.Any(it => it != item)) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CanBePut(item, i)) { continue; }
|
||||
}
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true)
|
||||
{
|
||||
if (index < 0 || index >= Items.Length)
|
||||
if (index < 0 || index >= slots.Length)
|
||||
{
|
||||
string errorMsg = "CharacterInventory.TryPutItem failed: index was out of range(" + index + ").\n" + Environment.StackTrace.CleanupStackTrace();
|
||||
GameAnalyticsManager.AddErrorEventOnce("CharacterInventory.TryPutItem:IndexOutOfRange", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
@@ -292,18 +322,16 @@ namespace Barotrauma
|
||||
if (PersonalSlots.HasFlag(SlotTypes[index])) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
//there's already an item in the slot
|
||||
if (Items[index] != null)
|
||||
if (slots[index].Any())
|
||||
{
|
||||
if (Items[index] == item) return false;
|
||||
|
||||
if (slots[index].Contains(item)) { return false; }
|
||||
return base.TryPutItem(item, index, allowSwapping, allowCombine, user, createNetworkEvent);
|
||||
}
|
||||
|
||||
if (SlotTypes[index] == InvSlotType.Any)
|
||||
{
|
||||
if (!item.AllowedSlots.Contains(InvSlotType.Any)) return false;
|
||||
if (Items[index] != null) return Items[index] == item;
|
||||
|
||||
if (!item.AllowedSlots.Contains(InvSlotType.Any)) { return false; }
|
||||
if (slots[index].Any()) { return slots[index].Contains(item); }
|
||||
PutItem(item, index, user, true, createNetworkEvent);
|
||||
return true;
|
||||
}
|
||||
@@ -311,28 +339,24 @@ namespace Barotrauma
|
||||
InvSlotType placeToSlots = InvSlotType.None;
|
||||
|
||||
bool slotsFree = true;
|
||||
List<InvSlotType> allowedSlots = item.AllowedSlots;
|
||||
foreach (InvSlotType allowedSlot in allowedSlots)
|
||||
foreach (InvSlotType allowedSlot in item.AllowedSlots)
|
||||
{
|
||||
if (!allowedSlot.HasFlag(SlotTypes[index])) continue;
|
||||
if (!allowedSlot.HasFlag(SlotTypes[index])) { continue; }
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(allowedSlot)) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] != null && Items[i] != item)
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && slots[i].Any() && !slots[i].Contains(item))
|
||||
{
|
||||
slotsFree = false;
|
||||
break;
|
||||
}
|
||||
|
||||
placeToSlots = allowedSlot;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!slotsFree) return false;
|
||||
if (!slotsFree) { return false; }
|
||||
|
||||
return TryPutItem(item, user, new List<InvSlotType>() { placeToSlots }, createNetworkEvent);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
private static List<DockingPort> list = new List<DockingPort>();
|
||||
public enum DirectionType
|
||||
{
|
||||
None,
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
private static readonly List<DockingPort> list = new List<DockingPort>();
|
||||
public static IEnumerable<DockingPort> List
|
||||
{
|
||||
get { return list; }
|
||||
@@ -37,7 +46,7 @@ namespace Barotrauma.Items.Components
|
||||
//force the sub to the correct position
|
||||
const float ForceLockDelay = 1.0f;
|
||||
|
||||
public int DockingDir { get; private set; }
|
||||
public int DockingDir { get; set; }
|
||||
|
||||
[Serialize("32.0,32.0", false, description: "How close the docking port has to be to another port to dock.")]
|
||||
public Vector2 DistanceTolerance { get; set; }
|
||||
@@ -63,6 +72,10 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(DirectionType.None, false, description: "Which direction the port is allowed to dock in. For example, \"Top\" would mean the port can dock to another port above it.\n"+
|
||||
"Normally there's no need to touch this setting, but if you notice the docking position is incorrect (for example due to some unusual docking port configuration without hulls or doors), you can use this to enforce the direction.")]
|
||||
public DirectionType ForceDockingDirection { get; set; }
|
||||
|
||||
public DockingPort DockingTarget { get; private set; }
|
||||
|
||||
public Door Door { get; private set; }
|
||||
@@ -89,6 +102,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLocked
|
||||
{
|
||||
get { return joint is WeldJoint || DockingTarget?.joint is WeldJoint; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically cleared after docking -> no need to unregister
|
||||
/// </summary>
|
||||
@@ -311,10 +329,10 @@ namespace Barotrauma.Items.Components
|
||||
joint = null;
|
||||
}
|
||||
|
||||
Vector2 offset = (IsHorizontal ?
|
||||
Vector2 offset = IsHorizontal ?
|
||||
Vector2.UnitX * DockingDir :
|
||||
Vector2.UnitY * DockingDir);
|
||||
offset *= DockedDistance * 0.5f;
|
||||
Vector2.UnitY * DockingDir;
|
||||
offset *= DockedDistance * 0.5f * item.Scale;
|
||||
|
||||
Vector2 pos1 = item.WorldPosition + offset;
|
||||
|
||||
@@ -346,6 +364,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public int GetDir(DockingPort dockingTarget = null)
|
||||
{
|
||||
int forcedDockingDir = GetForcedDockingDir();
|
||||
if (forcedDockingDir != 0) { return forcedDockingDir; }
|
||||
if (dockingTarget != null)
|
||||
{
|
||||
forcedDockingDir = -dockingTarget.GetForcedDockingDir();
|
||||
if (forcedDockingDir != 0) { return forcedDockingDir; }
|
||||
}
|
||||
|
||||
if (DockingDir != 0) { return DockingDir; }
|
||||
|
||||
if (Door != null && Door.LinkedGap.linkedTo.Count > 0)
|
||||
@@ -390,9 +416,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (dockingTarget != null)
|
||||
{
|
||||
return IsHorizontal ?
|
||||
int dir = IsHorizontal ?
|
||||
Math.Sign(dockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
|
||||
Math.Sign(dockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
|
||||
if (dir != 0) { return dir; }
|
||||
}
|
||||
if (item.Submarine != null)
|
||||
{
|
||||
@@ -404,6 +431,22 @@ namespace Barotrauma.Items.Components
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int GetForcedDockingDir()
|
||||
{
|
||||
switch (ForceDockingDirection)
|
||||
{
|
||||
case DirectionType.Left:
|
||||
return -1;
|
||||
case DirectionType.Right:
|
||||
return 1;
|
||||
case DirectionType.Top:
|
||||
return 1;
|
||||
case DirectionType.Bottom:
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void ConnectWireBetweenPorts()
|
||||
{
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
@@ -491,8 +534,9 @@ namespace Barotrauma.Items.Components
|
||||
subs = new Submarine[] { DockingTarget.item.Submarine, item.Submarine };
|
||||
}
|
||||
|
||||
hullRects[0] = new Rectangle(hullRects[0].Center.X, hullRects[0].Y, ((int)DockedDistance / 2), hullRects[0].Height);
|
||||
hullRects[1] = new Rectangle(hullRects[1].Center.X - ((int)DockedDistance / 2), hullRects[1].Y, ((int)DockedDistance / 2), hullRects[1].Height);
|
||||
int scaledDockedDistance = (int)(DockedDistance / 2 * item.Scale);
|
||||
hullRects[0] = new Rectangle(hullRects[0].Center.X, hullRects[0].Y, scaledDockedDistance, hullRects[0].Height);
|
||||
hullRects[1] = new Rectangle(hullRects[1].Center.X - scaledDockedDistance, hullRects[1].Y, scaledDockedDistance, hullRects[1].Height);
|
||||
|
||||
//expand hulls if needed, so there's no empty space between the sub's hulls and docking port hulls
|
||||
int leftSubRightSide = int.MinValue, rightSubLeftSide = int.MaxValue;
|
||||
@@ -588,8 +632,9 @@ namespace Barotrauma.Items.Components
|
||||
subs = new Submarine[] { DockingTarget.item.Submarine, item.Submarine };
|
||||
}
|
||||
|
||||
hullRects[0] = new Rectangle(hullRects[0].X, hullRects[0].Y + (int)(-hullRects[0].Height + DockedDistance) / 2, hullRects[0].Width, ((int)DockedDistance / 2));
|
||||
hullRects[1] = new Rectangle(hullRects[1].X, hullRects[1].Y - hullRects[1].Height / 2, hullRects[1].Width, ((int)DockedDistance / 2));
|
||||
int scaledDockedDistance = (int)(DockedDistance / 2 * item.Scale);
|
||||
hullRects[0] = new Rectangle(hullRects[0].X, hullRects[0].Y - hullRects[0].Height / 2 + scaledDockedDistance, hullRects[0].Width, scaledDockedDistance);
|
||||
hullRects[1] = new Rectangle(hullRects[1].X, hullRects[1].Y - hullRects[1].Height / 2, hullRects[1].Width, scaledDockedDistance);
|
||||
|
||||
//expand hulls if needed, so there's no empty space between the sub's hulls and docking port hulls
|
||||
int upperSubBottom = int.MaxValue, lowerSubTop = int.MinValue;
|
||||
@@ -908,7 +953,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (joint is DistanceJoint)
|
||||
{
|
||||
item.SendSignal(0, "0", "state_out", null);
|
||||
dockingState = MathHelper.Lerp(dockingState, 0.5f, deltaTime * 10.0f);
|
||||
|
||||
forceLockTimer += deltaTime;
|
||||
@@ -918,9 +962,7 @@ namespace Barotrauma.Items.Components
|
||||
if (jointDiff.LengthSquared() > 0.04f * 0.04f && forceLockTimer < ForceLockDelay)
|
||||
{
|
||||
float totalMass = item.Submarine.PhysicsBody.Mass + DockingTarget.item.Submarine.PhysicsBody.Mass;
|
||||
float massRatio1 = 1.0f;
|
||||
float massRatio2 = 1.0f;
|
||||
|
||||
float massRatio1, massRatio2;
|
||||
if (item.Submarine.PhysicsBody.BodyType != BodyType.Dynamic)
|
||||
{
|
||||
massRatio1 = 0.0f;
|
||||
@@ -954,11 +996,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
doorBody.Enabled = DockingTarget.Door.Body.Enabled;
|
||||
}
|
||||
|
||||
item.SendSignal(0, "1", "state_out", null);
|
||||
|
||||
dockingState = MathHelper.Lerp(dockingState, 1.0f, deltaTime * 10.0f);
|
||||
}
|
||||
|
||||
item.SendSignal(0, IsLocked ? "1" : "0", "state_out", null);
|
||||
}
|
||||
if (!obstructedWayPointsDisabled && dockingState >= 0.99f)
|
||||
{
|
||||
@@ -985,7 +1026,8 @@ namespace Barotrauma.Items.Components
|
||||
if (initialized) { return; }
|
||||
initialized = true;
|
||||
|
||||
float closestDist = 30.0f * 30.0f;
|
||||
float maxXDist = (item.Prefab.sprite.size.X * item.Prefab.Scale) / 2;
|
||||
float closestYDist = (item.Prefab.sprite.size.Y * item.Prefab.Scale) / 2;
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != item.Submarine) { continue; }
|
||||
@@ -993,11 +1035,22 @@ namespace Barotrauma.Items.Components
|
||||
var doorComponent = it.GetComponent<Door>();
|
||||
if (doorComponent == null || doorComponent.IsHorizontal == IsHorizontal) { continue; }
|
||||
|
||||
float distSqr = Vector2.DistanceSquared(item.Position, it.Position);
|
||||
if (distSqr < closestDist)
|
||||
float yDist = Math.Abs(it.Position.Y - item.Position.Y);
|
||||
if (item.linkedTo.Contains(it))
|
||||
{
|
||||
// If there's a door linked to the docking port, always treat it close enough.
|
||||
yDist = Math.Min(closestYDist, yDist);
|
||||
}
|
||||
else if (Math.Abs(it.Position.X - item.Position.X) > maxXDist)
|
||||
{
|
||||
// Too far left/right
|
||||
continue;
|
||||
}
|
||||
|
||||
if (yDist <= closestYDist)
|
||||
{
|
||||
Door = doorComponent;
|
||||
closestDist = distSqr;
|
||||
closestYDist = yDist;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ namespace Barotrauma.Items.Components
|
||||
private Rectangle doorRect;
|
||||
|
||||
private bool isBroken;
|
||||
|
||||
public bool CanBeTraversed => (IsOpen || IsBroken) && !IsJammed && !IsStuck;
|
||||
|
||||
public bool IsBroken
|
||||
{
|
||||
@@ -283,12 +285,6 @@ namespace Barotrauma.Items.Components
|
||||
return isBroken || base.HasRequiredItems(character, addMessage, msg);
|
||||
}
|
||||
|
||||
public bool CanBeOpenedWithoutTools(Character character)
|
||||
{
|
||||
if (isBroken) { return true; }
|
||||
return HasAccess(character);
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (item.Condition < RepairThreshold) { return true; }
|
||||
@@ -642,6 +638,19 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void OnFailedToOpen();
|
||||
|
||||
public override bool HasAccess(Character character)
|
||||
{
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (HasIntegratedButtons)
|
||||
{
|
||||
return base.HasAccess(character);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Item.GetConnectedComponents<Controller>(true).Any(b => b.HasAccess(character));
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
if (IsStuck || IsJammed) { return; }
|
||||
|
||||
@@ -395,6 +395,9 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize("1,1,1,1", true, "Probability for the plant to grow in a direction.")]
|
||||
public Vector4 GrowthWeights { get; set; }
|
||||
|
||||
[Serialize(0.0f, true, "How much damage is taken from fires.")]
|
||||
public float FireVulnerability { get; set; }
|
||||
|
||||
private const float increasedDeathSpeed = 10f;
|
||||
private bool accelerateDeath;
|
||||
private float health;
|
||||
@@ -418,6 +421,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private int productDelay;
|
||||
private int vineDelay;
|
||||
private float fireCheckCooldown;
|
||||
|
||||
public readonly List<ProducedItem> ProducedItems = new List<ProducedItem>();
|
||||
public readonly List<VineTile> Vines = new List<VineTile>();
|
||||
@@ -489,11 +493,15 @@ namespace Barotrauma.Items.Components
|
||||
if (Health > 0)
|
||||
{
|
||||
GrowVines(planter, slot);
|
||||
Health -= accelerateDeath ? Hardiness * increasedDeathSpeed : Hardiness;
|
||||
|
||||
// fertilizer makes the plant tick faster, compensate by halving water requirement
|
||||
float multipler = planter.Fertilizer > 0 ? 0.5f : 1f;
|
||||
|
||||
Health -= (accelerateDeath ? Hardiness * increasedDeathSpeed : Hardiness) * multipler;
|
||||
|
||||
if (planter.Item.InWater)
|
||||
{
|
||||
Health -= FloodTolerance;
|
||||
Health -= FloodTolerance * multipler;
|
||||
}
|
||||
#if SERVER
|
||||
if (FullyGrown)
|
||||
@@ -630,6 +638,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
UpdateFires(deltaTime);
|
||||
|
||||
#if CLIENT
|
||||
foreach (VineTile vine in Vines)
|
||||
{
|
||||
@@ -640,6 +650,29 @@ namespace Barotrauma.Items.Components
|
||||
CheckPlantState();
|
||||
}
|
||||
|
||||
private void UpdateFires(float deltaTime)
|
||||
{
|
||||
if (!Decayed && item.CurrentHull?.FireSources is { } fireSources && FireVulnerability > 0f)
|
||||
{
|
||||
if (fireCheckCooldown <= 0)
|
||||
{
|
||||
foreach (FireSource source in fireSources)
|
||||
{
|
||||
if (source.IsInDamageRange(item.WorldPosition, source.DamageRange))
|
||||
{
|
||||
Health -= FireVulnerability;
|
||||
}
|
||||
}
|
||||
|
||||
fireCheckCooldown = 5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
fireCheckCooldown -= deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GrowVines(Planter planter, PlantSlot slot)
|
||||
{
|
||||
if (FullyGrown) { return; }
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Barotrauma.Items.Components
|
||||
protected Vector2[] handlePos;
|
||||
private readonly Vector2[] scaledHandlePos;
|
||||
|
||||
private InputType prevPickKey;
|
||||
private readonly InputType prevPickKey;
|
||||
private string prevMsg;
|
||||
private Dictionary<RelatedItem.RelationType, List<RelatedItem>> prevRequiredItems;
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float swingState;
|
||||
|
||||
private Character prevEquipper;
|
||||
|
||||
private bool attachable, attached, attachedByDefault;
|
||||
private Voronoi2.VoronoiCell attachTargetCell;
|
||||
private readonly PhysicsBody body;
|
||||
@@ -301,11 +303,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.SetTransform(picker.SimPosition, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
picker.DeselectItem(item);
|
||||
picker.Inventory.RemoveItem(item);
|
||||
picker = null;
|
||||
}
|
||||
@@ -340,34 +340,33 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
bool alreadyEquipped = character.HasEquippedItem(item);
|
||||
bool canSelect = picker.TrySelectItem(item);
|
||||
|
||||
if (canSelect || picker.HasEquippedItem(item))
|
||||
if (picker.HasEquippedItem(item))
|
||||
{
|
||||
if (!canSelect)
|
||||
{
|
||||
character.DeselectItem(item);
|
||||
}
|
||||
|
||||
item.body.Enabled = true;
|
||||
item.body.PhysEnabled = false;
|
||||
IsActive = true;
|
||||
|
||||
#if SERVER
|
||||
if (!alreadyEquipped) GameServer.Log(GameServer.CharacterLogName(character) + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
if (picker != prevEquipper) { GameServer.Log(GameServer.CharacterLogName(character) + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction); }
|
||||
#endif
|
||||
prevEquipper = picker;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevEquipper = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Unequip(Character character)
|
||||
{
|
||||
if (picker == null) { return; }
|
||||
|
||||
picker.DeselectItem(item);
|
||||
#if SERVER
|
||||
GameServer.Log(GameServer.CharacterLogName(character) + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
if (prevEquipper != null)
|
||||
{
|
||||
GameServer.Log(GameServer.CharacterLogName(character) + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
|
||||
prevEquipper = null;
|
||||
if (picker == null) { return; }
|
||||
item.body.PhysEnabled = true;
|
||||
item.body.Enabled = false;
|
||||
IsActive = false;
|
||||
@@ -488,13 +487,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
var containedItems = item.OwnInventory?.AllItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item contained in containedItems)
|
||||
{
|
||||
if (contained == null) { continue; }
|
||||
if (contained.body == null) { continue; }
|
||||
if (contained?.body == null) { continue; }
|
||||
contained.SetTransform(item.SimPosition, contained.body.Rotation);
|
||||
}
|
||||
}
|
||||
@@ -673,7 +671,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
item.Submarine = picker.Submarine;
|
||||
|
||||
if (picker.HasSelectedItem(item))
|
||||
if (picker.HeldItems.Contains(item))
|
||||
{
|
||||
scaledHandlePos[0] = handlePos[0] * item.Scale;
|
||||
scaledHandlePos[1] = handlePos[1] * item.Scale;
|
||||
|
||||
@@ -42,13 +42,13 @@ namespace Barotrauma.Items.Components
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
base.Equip(character);
|
||||
character.Info.CheckDisguiseStatus(true, this);
|
||||
character.Info?.CheckDisguiseStatus(true, this);
|
||||
}
|
||||
|
||||
public override void Unequip(Character character)
|
||||
{
|
||||
base.Unequip(character);
|
||||
character.Info.CheckDisguiseStatus(true, this);
|
||||
character.Info?.CheckDisguiseStatus(true, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(1.0f, false, description: "How much the position of the item can vary from the wall the item spawns on.")]
|
||||
public float RandomOffsetFromWall
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool Attached
|
||||
{
|
||||
get { return holdable != null && holdable.Attached; }
|
||||
|
||||
@@ -50,6 +50,11 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines items that boost the weapon functionality, like battery cell for stun batons.
|
||||
/// </summary>
|
||||
public readonly string[] PreferredContainedItems;
|
||||
|
||||
public MeleeWeapon(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -61,6 +66,7 @@ namespace Barotrauma.Items.Components
|
||||
item.IsShootable = true;
|
||||
// TODO: should define this in xml if we have melee weapons that don't require aim to use
|
||||
item.RequireAimToUse = true;
|
||||
PreferredContainedItems = element.GetAttributeStringArray("preferredcontaineditems", new string[0], convertToLowerInvariant: true);
|
||||
}
|
||||
|
||||
public override void Equip(Character character)
|
||||
@@ -76,11 +82,9 @@ namespace Barotrauma.Items.Components
|
||||
if (Item.RequireAimToUse && !character.IsKeyDown(InputType.Aim) || hitting) { return false; }
|
||||
|
||||
//don't allow hitting if the character is already hitting with another weapon
|
||||
for (int i = 0; i < 2; i++ )
|
||||
foreach (Item heldItem in character.HeldItems)
|
||||
{
|
||||
if (character.SelectedItems[i] == null || character.SelectedItems[i] == Item) { continue; }
|
||||
|
||||
var otherWeapon = character.SelectedItems[i].GetComponent<MeleeWeapon>();
|
||||
var otherWeapon = heldItem.GetComponent<MeleeWeapon>();
|
||||
if (otherWeapon == null) { continue; }
|
||||
if (otherWeapon.hitting) { return false; }
|
||||
}
|
||||
@@ -143,7 +147,7 @@ namespace Barotrauma.Items.Components
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!item.body.Enabled) { impactQueue.Clear(); return; }
|
||||
if (picker == null && !picker.HasSelectedItem(item)) { impactQueue.Clear(); IsActive = false; }
|
||||
if (picker == null && !picker.HeldItems.Contains(item)) { impactQueue.Clear(); IsActive = false; }
|
||||
|
||||
while (impactQueue.Count > 0)
|
||||
{
|
||||
@@ -244,12 +248,14 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
//ignore collision if there's a wall between the user and the weapon to prevent hitting through walls
|
||||
contact.GetWorldManifold(out Vector2 normal, out var points);
|
||||
|
||||
//ignore collision if there's a wall between the user and the contact point to prevent hitting through walls
|
||||
if (Submarine.PickBody(User.AnimController.AimSourceSimPos,
|
||||
item.SimPosition,
|
||||
points[0],
|
||||
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking,
|
||||
allowInsideFixture: true,
|
||||
customPredicate: (Fixture fixture) => { return fixture.CollidesWith.HasFlag(Physics.CollisionItem); }) != null)
|
||||
customPredicate: (Fixture fixture) => { return fixture.CollidesWith.HasFlag(Physics.CollisionItem) && fixture.Body != f2.Body; }) != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -91,7 +92,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots))
|
||||
{
|
||||
if (!picker.HasSelectedItem(item) && item.body != null) item.body.Enabled = false;
|
||||
if (!picker.HeldItems.Contains(item) && item.body != null) { item.body.Enabled = false; }
|
||||
this.picker = picker;
|
||||
|
||||
for (int i = item.linkedTo.Count - 1; i >= 0; i--)
|
||||
|
||||
@@ -71,11 +71,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
character.AnimController.Collider.ApplyForce(propulsion, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
|
||||
if (character.SelectedItems[0] == item)
|
||||
if (character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand))
|
||||
{
|
||||
character.AnimController.GetLimb(LimbType.RightHand)?.body.ApplyForce(propulsion, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
if (character.SelectedItems[1] == item)
|
||||
if (character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand))
|
||||
{
|
||||
character.AnimController.GetLimb(LimbType.LeftHand)?.body.ApplyForce(propulsion, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public Projectile FindProjectile(bool triggerOnUseOnContainers = false)
|
||||
{
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
var containedItems = item.OwnInventory?.AllItemsMod;
|
||||
if (containedItems == null) { return null; }
|
||||
|
||||
foreach (Item item in containedItems)
|
||||
@@ -166,7 +166,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Item it in containedItems)
|
||||
{
|
||||
if (it == null) { continue; }
|
||||
var containedSubItems = it.OwnInventory?.Items;
|
||||
var containedSubItems = it.OwnInventory?.AllItemsMod;
|
||||
if (containedSubItems == null) { continue; }
|
||||
foreach (Item subItem in containedSubItems)
|
||||
{
|
||||
|
||||
@@ -87,6 +87,13 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(false, false, description: "Can the item repair things through holes in walls.")]
|
||||
public bool RepairThroughHoles { get; set; }
|
||||
|
||||
|
||||
[Serialize(100.0f, false, description: "How far two walls need to not be considered overlapping and to stop the ray.")]
|
||||
public float MaxOverlappingWallDist
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Serialize(true, false, description: "Can the item hit broken doors.")]
|
||||
public bool HitItems { get; set; }
|
||||
|
||||
@@ -320,9 +327,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
var bodies = Submarine.PickBodies(rayStart, rayEnd, ignoredBodies, collisionCategories,
|
||||
ignoreSensors: false,
|
||||
customPredicate: (Fixture f) =>
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
if (RepairThroughHoles && f.IsSensor && f.Body?.UserData is Structure || (f.Body?.UserData is Item it && it.GetComponent<Planter>() != null)) { return false; }
|
||||
if (f.IsSensor)
|
||||
{
|
||||
if (RepairThroughHoles && f.Body?.UserData is Structure) { return false; }
|
||||
if (f.Body?.UserData is PhysicsBody) { return false; }
|
||||
}
|
||||
if (f.Body?.UserData is Item it && it.GetComponent<Planter>() != null) { return false; }
|
||||
if (f.Body?.UserData as string == "ruinroom") { return false; }
|
||||
if (f.Body?.UserData is VineTile && !(FireDamage > 0)) { return false; }
|
||||
return true;
|
||||
@@ -361,12 +373,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
//if repairing through walls is not allowed and the next wall is more than 100 pixels away from the previous one, stop here
|
||||
//(= repairing multiple overlapping walls is allowed as long as the edges of the walls are less than 100 pixels apart)
|
||||
//(= repairing multiple overlapping walls is allowed as long as the edges of the walls are less than MaxOverlappingWallDist pixels apart)
|
||||
float thisBodyFraction = Submarine.LastPickedBodyDist(body);
|
||||
if (!RepairThroughWalls && lastHitType == typeof(Structure) && Range * (thisBodyFraction - lastPickedFraction) > 100.0f)
|
||||
if (!RepairThroughWalls && lastHitType == typeof(Structure) && Range * (thisBodyFraction - lastPickedFraction) > MaxOverlappingWallDist)
|
||||
{
|
||||
break;
|
||||
}
|
||||
pickedPosition = rayStart + (rayEnd - rayStart) * thisBodyFraction;
|
||||
if (FixBody(user, deltaTime, degreeOfSuccess, body))
|
||||
{
|
||||
lastPickedFraction = thisBodyFraction;
|
||||
@@ -376,13 +389,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
FixBody(user, deltaTime, degreeOfSuccess,
|
||||
Submarine.PickBody(rayStart, rayEnd,
|
||||
ignoredBodies, collisionCategories,
|
||||
var pickedBody = Submarine.PickBody(rayStart, rayEnd,
|
||||
ignoredBodies, collisionCategories,
|
||||
ignoreSensors: false,
|
||||
customPredicate: (Fixture f) =>
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
if (RepairThroughHoles && f.IsSensor && f.Body?.UserData is Structure) { return false; }
|
||||
if (f.IsSensor)
|
||||
{
|
||||
if (RepairThroughHoles && f.Body?.UserData is Structure) { return false; }
|
||||
if (f.Body?.UserData is PhysicsBody) { return false; }
|
||||
}
|
||||
if (f.Body?.UserData as string == "ruinroom") { return false; }
|
||||
if (f.Body?.UserData is VineTile && !(FireDamage > 0)) { return false; }
|
||||
|
||||
@@ -398,9 +414,11 @@ namespace Barotrauma.Items.Components
|
||||
if (targetItem.Condition <= 0) { return false; }
|
||||
}
|
||||
}
|
||||
return f.Body?.UserData != null;
|
||||
return f.Body?.UserData != null;
|
||||
},
|
||||
allowInsideFixture: true));
|
||||
allowInsideFixture: true);
|
||||
pickedPosition = Submarine.LastPickedPosition;
|
||||
FixBody(user, deltaTime, degreeOfSuccess, pickedBody);
|
||||
lastPickedFraction = Submarine.LastPickedFraction;
|
||||
}
|
||||
|
||||
@@ -495,8 +513,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (targetBody?.UserData == null) { return false; }
|
||||
|
||||
pickedPosition = Submarine.LastPickedPosition;
|
||||
|
||||
if (targetBody.UserData is Structure targetStructure)
|
||||
{
|
||||
if (targetStructure.IsPlatform) { return false; }
|
||||
@@ -526,8 +542,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (targetBody.UserData is Voronoi2.VoronoiCell cell && cell.IsDestructible)
|
||||
{
|
||||
var levelWall = Level.Loaded?.ExtraWalls.Find(w => w.Body == cell.Body) as DestructibleLevelWall;
|
||||
if (levelWall != null)
|
||||
if (Level.Loaded?.ExtraWalls.Find(w => w.Body == cell.Body) is DestructibleLevelWall levelWall)
|
||||
{
|
||||
levelWall.AddDamage(-LevelWallFixAmount * deltaTime, item.WorldPosition);
|
||||
}
|
||||
@@ -579,7 +594,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (targetBody.UserData is Item targetItem)
|
||||
{
|
||||
if (!HitItems || targetItem.NonInteractable) { return false; }
|
||||
if (!HitItems || !targetItem.IsInteractable(user)) { return false; }
|
||||
|
||||
var levelResource = targetItem.GetComponent<LevelResource>();
|
||||
if (levelResource != null && levelResource.Attached &&
|
||||
@@ -594,7 +609,7 @@ namespace Barotrauma.Items.Components
|
||||
levelResource.DeattachTimer / levelResource.DeattachDuration,
|
||||
GUI.Style.Red, GUI.Style.Green, "progressbar.deattaching");
|
||||
#endif
|
||||
FixItemProjSpecific(user, deltaTime, targetItem);
|
||||
FixItemProjSpecific(user, deltaTime, targetItem, showProgressBar: false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -620,7 +635,7 @@ namespace Barotrauma.Items.Components
|
||||
targetItem.body.ApplyForce(dir * TargetForce, maxVelocity: 10.0f);
|
||||
}
|
||||
|
||||
FixItemProjSpecific(user, deltaTime, targetItem);
|
||||
FixItemProjSpecific(user, deltaTime, targetItem, showProgressBar: true);
|
||||
return true;
|
||||
}
|
||||
else if (targetBody.UserData is BallastFloraBranch branch)
|
||||
@@ -635,7 +650,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex);
|
||||
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter);
|
||||
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem);
|
||||
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, bool showProgressBar);
|
||||
|
||||
private float sinTime;
|
||||
private float repairTimer;
|
||||
@@ -643,74 +658,71 @@ namespace Barotrauma.Items.Components
|
||||
private readonly float repairTimeOut = 5;
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (!(objective.OperateTarget is Gap leak)) { return true; }
|
||||
if (leak.Submarine == null) { return true; }
|
||||
if (!(objective.OperateTarget is Gap leak))
|
||||
{
|
||||
Reset();
|
||||
return true;
|
||||
}
|
||||
if (leak.Submarine == null)
|
||||
{
|
||||
Reset();
|
||||
return true;
|
||||
}
|
||||
if (leak != previousGap)
|
||||
{
|
||||
sinTime = 0;
|
||||
repairTimer = 0;
|
||||
Reset();
|
||||
previousGap = leak;
|
||||
}
|
||||
Vector2 fromCharacterToLeak = leak.WorldPosition - character.WorldPosition;
|
||||
float dist = fromCharacterToLeak.Length();
|
||||
float reach = AIObjectiveFixLeak.CalculateReach(this, character);
|
||||
|
||||
//too far away -> consider this done and hope the AI is smart enough to move closer
|
||||
if (dist > reach * 2) { return true; }
|
||||
character.AIController.SteeringManager.Reset();
|
||||
//steer closer if almost in range
|
||||
if (dist > reach)
|
||||
if (dist > reach * 3)
|
||||
{
|
||||
if (character.AnimController.InWater)
|
||||
// Too far away -> consider this done and hope the AI is smart enough to move closer
|
||||
Reset();
|
||||
return true;
|
||||
}
|
||||
character.AIController.SteeringManager.Reset();
|
||||
if (!character.AnimController.InWater)
|
||||
{
|
||||
// TODO: use the collider size?
|
||||
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
|
||||
Math.Abs(fromCharacterToLeak.X) < 100.0f && fromCharacterToLeak.Y < 0.0f && fromCharacterToLeak.Y > -150.0f)
|
||||
{
|
||||
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
|
||||
((HumanoidAnimController)character.AnimController).Crouching = true;
|
||||
}
|
||||
}
|
||||
if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.inWater))
|
||||
{
|
||||
// Steer closer
|
||||
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
|
||||
{
|
||||
// Swimming inside the sub
|
||||
if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && (indoorSteering.CurrentPath.Unreachable || indoorSteering.CurrentPath.Finished))
|
||||
{
|
||||
// Swimming inside the sub
|
||||
if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && indoorSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
Vector2 dir = Vector2.Normalize(fromCharacterToLeak);
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
|
||||
}
|
||||
Vector2 dir = Vector2.Normalize(fromCharacterToLeak);
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Swimming outside the sub
|
||||
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: use the collider size?
|
||||
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
|
||||
Math.Abs(fromCharacterToLeak.X) < 100.0f && fromCharacterToLeak.Y < 0.0f && fromCharacterToLeak.Y > -150.0f)
|
||||
{
|
||||
((HumanoidAnimController)character.AnimController).Crouching = true;
|
||||
}
|
||||
Vector2 standPos = new Vector2(Math.Sign(-fromCharacterToLeak.X), Math.Sign(-fromCharacterToLeak.Y)) / 2;
|
||||
if (leak.IsHorizontal)
|
||||
{
|
||||
standPos.X *= 2;
|
||||
standPos.Y = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
standPos.X = 0;
|
||||
}
|
||||
character.AIController.SteeringManager.SteeringSeek(standPos);
|
||||
// Swimming outside the sub
|
||||
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
|
||||
}
|
||||
}
|
||||
if (dist < reach / 2)
|
||||
else if (dist < reach * 0.25f)
|
||||
{
|
||||
// Too close -> steer away
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition));
|
||||
}
|
||||
else if (dist < reach * 2)
|
||||
if (dist <= reach)
|
||||
{
|
||||
// In or almost in range
|
||||
// In range
|
||||
character.CursorPosition = leak.WorldPosition;
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
@@ -734,46 +746,52 @@ namespace Barotrauma.Items.Components
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, moveDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.RequireAimToUse)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
sinTime += deltaTime * 5;
|
||||
}
|
||||
// Press the trigger only when the tool is approximately facing the target.
|
||||
Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
|
||||
var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak);
|
||||
if (angle < MathHelper.PiOver4)
|
||||
{
|
||||
if (Submarine.PickBody(item.SimPosition, leak.SimPosition, collisionCategory: Physics.CollisionWall, allowInsideFixture: true)?.UserData is Item i)
|
||||
if (item.RequireAimToUse)
|
||||
{
|
||||
var door = i.GetComponent<Door>();
|
||||
// Hit a door, abandon so that we don't weld it shut.
|
||||
return door != null && !door.IsOpen && !door.IsBroken;
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
sinTime += deltaTime * 5;
|
||||
}
|
||||
// Check that we don't hit any friendlies
|
||||
if (Submarine.PickBodies(item.SimPosition, leak.SimPosition, collisionCategory: Physics.CollisionCharacter).None(hit =>
|
||||
// Press the trigger only when the tool is approximately facing the target.
|
||||
Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
|
||||
var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak);
|
||||
if (angle < MathHelper.PiOver4)
|
||||
{
|
||||
if (hit.UserData is Character c)
|
||||
if (Submarine.PickBody(item.SimPosition, leak.SimPosition, collisionCategory: Physics.CollisionWall, allowInsideFixture: true)?.UserData is Item i)
|
||||
{
|
||||
if (c == character) { return false; }
|
||||
return HumanAIController.IsFriendly(character, c);
|
||||
var door = i.GetComponent<Door>();
|
||||
// Hit a door, abandon so that we don't weld it shut.
|
||||
return door != null && !door.CanBeTraversed;
|
||||
}
|
||||
return false;
|
||||
}))
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Use(deltaTime, character);
|
||||
repairTimer += deltaTime;
|
||||
if (repairTimer > repairTimeOut)
|
||||
// Check that we don't hit any friendlies
|
||||
if (Submarine.PickBodies(item.SimPosition, leak.SimPosition, collisionCategory: Physics.CollisionCharacter).None(hit =>
|
||||
{
|
||||
if (hit.UserData is Character c)
|
||||
{
|
||||
if (c == character) { return false; }
|
||||
return HumanAIController.IsFriendly(character, c);
|
||||
}
|
||||
return false;
|
||||
}))
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Use(deltaTime, character);
|
||||
repairTimer += deltaTime;
|
||||
if (repairTimer > repairTimeOut)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: timed out while welding a leak in {leak.FlowTargetHull.DisplayName}.", color: Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: timed out while welding a leak in {leak.FlowTargetHull.DisplayName}.", color: Color.Yellow);
|
||||
#endif
|
||||
return true;
|
||||
Reset();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset the timer so that we don't time out if the water forces push us away
|
||||
repairTimer = 0;
|
||||
}
|
||||
|
||||
bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
|
||||
(leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);
|
||||
@@ -791,6 +809,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
return leakFixed;
|
||||
|
||||
void Reset()
|
||||
{
|
||||
sinTime = 0;
|
||||
repairTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyStatusEffectsOnTarget(Character user, float deltaTime, ActionType actionType, IEnumerable<ISerializableEntity> targets)
|
||||
@@ -821,7 +845,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (ISerializableEntity target in targets)
|
||||
{
|
||||
if (!(target is Door door)) { continue; }
|
||||
if (!door.CanBeWelded || door.Item.NonInteractable) { continue; }
|
||||
if (!door.CanBeWelded || !door.Item.IsInteractable(user)) { continue; }
|
||||
for (int i = 0; i < effect.propertyNames.Length; i++)
|
||||
{
|
||||
string propertyName = effect.propertyNames[i];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -83,7 +84,7 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (picker == null || picker.Removed || !picker.HasSelectedItem(item))
|
||||
if (picker == null || picker.Removed || !picker.HeldItems.Contains(item))
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
|
||||
@@ -235,6 +235,12 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(0f, false, description: "How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).")]
|
||||
public float CombatPriority { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which sound should be played when manual sound selection type is selected? Not [Editable] because we don't want this visible in the editor for every component.
|
||||
/// </summary>
|
||||
[Serialize(0, true, alwaysUseInstanceValues: true)]
|
||||
public int ManuallySelectedSound { get; private set; }
|
||||
|
||||
public ItemComponent(Item item, XElement element)
|
||||
{
|
||||
this.item = item;
|
||||
@@ -453,22 +459,20 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public virtual bool Combine(Item item, Character user)
|
||||
{
|
||||
if (canBeCombined && this.item.Prefab == item.Prefab && item.Condition > 0.0f && this.item.Condition > 0.0f)
|
||||
if (canBeCombined && this.item.Prefab == item.Prefab &&
|
||||
item.Condition > 0.0f && this.item.Condition > 0.0f &&
|
||||
!item.IsFullCondition && !this.item.IsFullCondition)
|
||||
{
|
||||
float transferAmount = 0.0f;
|
||||
if (this.Item.Condition <= item.Condition)
|
||||
transferAmount = Math.Min(item.Condition, this.item.MaxCondition - this.item.Condition);
|
||||
else
|
||||
transferAmount = -Math.Min(this.item.Condition, item.MaxCondition - item.Condition);
|
||||
float transferAmount = Math.Min(item.Condition, this.item.MaxCondition - this.item.Condition);
|
||||
|
||||
if (transferAmount == 0.0f) { return false; }
|
||||
if (MathUtils.NearlyEqual(transferAmount, 0.0f)) { return false; }
|
||||
if (removeOnCombined)
|
||||
{
|
||||
if (item.Condition - transferAmount <= 0.0f)
|
||||
{
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.ParentInventory.Owner is Character owner && owner.HasSelectedItem(item))
|
||||
if (item.ParentInventory.Owner is Character owner && owner.HeldItems.Contains(item))
|
||||
{
|
||||
item.Unequip(owner);
|
||||
}
|
||||
@@ -484,7 +488,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (this.Item.ParentInventory != null)
|
||||
{
|
||||
if (this.Item.ParentInventory.Owner is Character owner && owner.HasSelectedItem(this.Item))
|
||||
if (this.Item.ParentInventory.Owner is Character owner && owner.HeldItems.Contains(this.Item))
|
||||
{
|
||||
this.Item.Unequip(owner);
|
||||
}
|
||||
@@ -654,16 +658,18 @@ namespace Barotrauma.Items.Components
|
||||
/// <summary>
|
||||
/// Only checks if any of the Picked requirements are matched (used for checking id card(s)). Much simpler and a bit different than HasRequiredItems.
|
||||
/// </summary>
|
||||
public bool HasAccess(Character character)
|
||||
public virtual bool HasAccess(Character character)
|
||||
{
|
||||
if (character.Inventory == null) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (requiredItems.None()) { return true; }
|
||||
|
||||
foreach (Item item in character.Inventory.Items)
|
||||
if (character.Inventory != null)
|
||||
{
|
||||
if (requiredItems.Any(ri => ri.Value.Any(r => r.Type == RelatedItem.RelationType.Picked && r.MatchesItem(item))))
|
||||
foreach (Item item in character.Inventory.AllItems)
|
||||
{
|
||||
return true;
|
||||
if (requiredItems.Any(ri => ri.Value.Any(r => r.Type == RelatedItem.RelationType.Picked && r.MatchesItem(item))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -672,6 +678,7 @@ namespace Barotrauma.Items.Components
|
||||
public virtual bool HasRequiredItems(Character character, bool addMessage, string msg = null)
|
||||
{
|
||||
if (requiredItems.None()) { return true; }
|
||||
if (!character.IsPlayer && character.Params.AI != null && character.Params.AI.Infiltrate) { return true; }
|
||||
if (character.Inventory == null) { return false; }
|
||||
bool hasRequiredItems = false;
|
||||
bool canContinue = true;
|
||||
@@ -679,7 +686,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Equipped])
|
||||
{
|
||||
canContinue = CheckItems(ri, character.SelectedItems);
|
||||
canContinue = CheckItems(ri, character.HeldItems);
|
||||
if (!canContinue) { break; }
|
||||
}
|
||||
}
|
||||
@@ -689,7 +696,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Picked])
|
||||
{
|
||||
if (!CheckItems(ri, character.Inventory.Items)) { break; }
|
||||
if (!CheckItems(ri, character.Inventory.AllItems)) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -957,7 +964,7 @@ namespace Barotrauma.Items.Components
|
||||
previousUser = character;
|
||||
itemIndex = 0;
|
||||
}
|
||||
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: aiController.IgnoredItems, customPriorityFunction: priority))
|
||||
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: aiController.IgnoredItems, customPriorityFunction: priority, positionalReference: Item))
|
||||
{
|
||||
suitableContainer = targetContainer;
|
||||
return true;
|
||||
@@ -1016,7 +1023,7 @@ namespace Barotrauma.Items.Components
|
||||
if (character.AIController is HumanAIController aiController)
|
||||
{
|
||||
ItemContainer sourceC = sourceContainer ?? (item.OwnInventory?.Owner is Item it ? it.GetComponent<ItemContainer>() : null);
|
||||
var containedItems = sourceContainer != null ? sourceContainer.Inventory.Items : item.OwnInventory.Items;
|
||||
var containedItems = sourceContainer != null ? sourceContainer.Inventory.AllItems : item.OwnInventory.AllItems;
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
if (containedItem != null && containedItem.Condition <= 0.0f)
|
||||
@@ -1027,20 +1034,20 @@ namespace Barotrauma.Items.Components
|
||||
if (i.IsThisOrAnyContainerIgnoredByAI()) { return 0; }
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
if (container.Inventory.IsFull()) { return 0; }
|
||||
if (!container.Inventory.CanBePut(containedItem)) { return 0; }
|
||||
// Ignore containers that are identical to the source container
|
||||
if (sourceC != null && container.Item.Prefab == sourceC.Item.Prefab) { return 0; }
|
||||
if (container.ShouldBeContained(containedItem, out bool isRestrictionsDefined))
|
||||
{
|
||||
if (isRestrictionsDefined)
|
||||
{
|
||||
return 4;
|
||||
return 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (containedItem.Prefab.IsContainerPreferred(container, out bool isPreferencesDefined, out bool isSecondary))
|
||||
if (containedItem.IsContainerPreferred(container, out bool isPreferencesDefined, out bool isSecondary))
|
||||
{
|
||||
return isPreferencesDefined ? isSecondary ? 2 : 3 : 1;
|
||||
return isPreferencesDefined ? isSecondary ? 2 : 5 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -9,11 +9,24 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ItemContainer : ItemComponent, IDrawableComponent
|
||||
{
|
||||
class ActiveContainedItem
|
||||
{
|
||||
public readonly Item Item;
|
||||
public readonly StatusEffect StatusEffect;
|
||||
public readonly bool ExcludeBroken;
|
||||
public ActiveContainedItem(Item item, StatusEffect statusEffect, bool excludeBroken)
|
||||
{
|
||||
Item = item;
|
||||
StatusEffect = statusEffect;
|
||||
ExcludeBroken = excludeBroken;
|
||||
}
|
||||
}
|
||||
|
||||
public ItemInventory Inventory;
|
||||
|
||||
private List<Pair<Item, StatusEffect>> itemsWithStatusEffects;
|
||||
private readonly List<ActiveContainedItem> activeContainedItems = new List<ActiveContainedItem>();
|
||||
|
||||
private ushort[] itemIds;
|
||||
private List<ushort>[] itemIds;
|
||||
|
||||
//how many items can be contained
|
||||
private int capacity;
|
||||
@@ -24,6 +37,15 @@ namespace Barotrauma.Items.Components
|
||||
set { capacity = Math.Max(value, 1); }
|
||||
}
|
||||
|
||||
//how many items can be contained
|
||||
private int maxStackSize;
|
||||
[Serialize(64, false, description: "How many items can be stacked in one slot. Does not increase the maximum stack size of the items themselves, e.g. a stack of bullets could have a maximum size of 8 but the number of bullets in a specific weapon could be restricted to 6.")]
|
||||
public int MaxStackSize
|
||||
{
|
||||
get { return maxStackSize; }
|
||||
set { maxStackSize = Math.Max(value, 1); }
|
||||
}
|
||||
|
||||
private bool hideItems;
|
||||
[Serialize(true, false, description: "Should the items contained inside this item be hidden."
|
||||
+ " If set to false, you should use the ItemPos and ItemInterval properties to determine where the items get rendered.")]
|
||||
@@ -141,8 +163,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
InitProjSpecific(element);
|
||||
|
||||
itemsWithStatusEffects = new List<Pair<Item, StatusEffect>>();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
@@ -154,23 +174,23 @@ namespace Barotrauma.Items.Components
|
||||
RelatedItem ri = ContainableItems.Find(x => x.MatchesItem(containedItem));
|
||||
if (ri != null)
|
||||
{
|
||||
itemsWithStatusEffects.RemoveAll(i => i.First == containedItem);
|
||||
activeContainedItems.RemoveAll(i => i.Item == containedItem);
|
||||
foreach (StatusEffect effect in ri.statusEffects)
|
||||
{
|
||||
itemsWithStatusEffects.Add(new Pair<Item, StatusEffect>(containedItem, effect));
|
||||
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, ri.ExcludeBroken));
|
||||
}
|
||||
}
|
||||
|
||||
//no need to Update() if this item has no statuseffects and no physics body
|
||||
IsActive = itemsWithStatusEffects.Count > 0 || Inventory.Items.Any(it => it?.body != null);
|
||||
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
|
||||
}
|
||||
|
||||
public void OnItemRemoved(Item containedItem)
|
||||
{
|
||||
itemsWithStatusEffects.RemoveAll(i => i.First == containedItem);
|
||||
activeContainedItems.RemoveAll(i => i.Item == containedItem);
|
||||
|
||||
//deactivate if the inventory is empty
|
||||
IsActive = itemsWithStatusEffects.Count > 0 || Inventory.Items.Any(it => it?.body != null);
|
||||
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
|
||||
}
|
||||
|
||||
public bool CanBeContained(Item item)
|
||||
@@ -196,18 +216,17 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.SetContainedItemPositions();
|
||||
}
|
||||
else if (itemsWithStatusEffects.Count == 0)
|
||||
else if (activeContainedItems.Count == 0)
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Pair<Item, StatusEffect> itemAndEffect in itemsWithStatusEffects)
|
||||
foreach (var activeContainedItem in activeContainedItems)
|
||||
{
|
||||
Item contained = itemAndEffect.First;
|
||||
if (contained.Condition <= 0.0f) continue;
|
||||
|
||||
StatusEffect effect = itemAndEffect.Second;
|
||||
Item contained = activeContainedItem.Item;
|
||||
if (activeContainedItem.ExcludeBroken && contained.Condition <= 0.0f) { continue; }
|
||||
StatusEffect effect = activeContainedItem.StatusEffect;
|
||||
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, item.AllPropertyObjects);
|
||||
@@ -240,9 +259,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (AutoInteractWithContained && character.SelectedConstruction == null)
|
||||
{
|
||||
foreach (Item contained in Inventory.Items)
|
||||
foreach (Item contained in Inventory.AllItems)
|
||||
{
|
||||
if (contained == null) continue;
|
||||
if (contained.TryInteract(character))
|
||||
{
|
||||
character.FocusedItem = contained;
|
||||
@@ -264,9 +282,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (AutoInteractWithContained)
|
||||
{
|
||||
foreach (Item contained in Inventory.Items)
|
||||
foreach (Item contained in Inventory.AllItems)
|
||||
{
|
||||
if (contained == null) continue;
|
||||
if (contained.TryInteract(picker))
|
||||
{
|
||||
picker.FocusedItem = contained;
|
||||
@@ -277,20 +294,19 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
IsActive = true;
|
||||
|
||||
return (picker != null);
|
||||
return picker != null;
|
||||
}
|
||||
|
||||
public override bool Combine(Item item, Character user)
|
||||
{
|
||||
if (!AllowDragAndDrop && user != null) { return false; }
|
||||
|
||||
if (!ContainableItems.Any(x => x.MatchesItem(item))) { return false; }
|
||||
if (!ContainableItems.Any(it => it.MatchesItem(item))) { return false; }
|
||||
if (user != null && !user.CanAccessInventory(Inventory)) { return false; }
|
||||
|
||||
if (Inventory.TryPutItem(item, null))
|
||||
if (Inventory.TryPutItem(item, user))
|
||||
{
|
||||
IsActive = true;
|
||||
if (hideItems && item.body != null) item.body.Enabled = false;
|
||||
if (hideItems && item.body != null) { item.body.Enabled = false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -318,9 +334,8 @@ namespace Barotrauma.Items.Components
|
||||
currentRotation += item.body.Rotation;
|
||||
}
|
||||
|
||||
foreach (Item contained in Inventory.Items)
|
||||
foreach (Item contained in Inventory.AllItems)
|
||||
{
|
||||
if (contained == null) { continue; }
|
||||
if (contained.body != null)
|
||||
{
|
||||
try
|
||||
@@ -362,13 +377,22 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
if (itemIds != null)
|
||||
{
|
||||
if (itemIds != null)
|
||||
{
|
||||
for (ushort i = 0; i < itemIds.Length; i++)
|
||||
{
|
||||
if (!(Entity.FindEntityByID(itemIds[i]) is Item item)) { continue; }
|
||||
if (i >= Inventory.Capacity) { continue; }
|
||||
Inventory.TryPutItem(item, i, false, false, null, false);
|
||||
if (i >= Inventory.Capacity)
|
||||
{
|
||||
//legacy support: before item stacking was implemented, revolver for example had a separate slot for each bullet
|
||||
//now there's just one, try to put the extra items where they fit (= stack them)
|
||||
Inventory.TryPutItem(item, user: null, createNetworkEvent: false);
|
||||
continue;
|
||||
}
|
||||
foreach (ushort id in itemIds[i])
|
||||
{
|
||||
if (!(Entity.FindEntityByID(id) is Item item)) { continue; }
|
||||
Inventory.TryPutItem(item, i, false, false, null, false);
|
||||
}
|
||||
}
|
||||
itemIds = null;
|
||||
}
|
||||
@@ -380,7 +404,7 @@ namespace Barotrauma.Items.Components
|
||||
if (SpawnWithId.Length > 0)
|
||||
{
|
||||
ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == SpawnWithId);
|
||||
if (prefab != null && Inventory != null && Inventory.Items.Any(it => it == null))
|
||||
if (prefab != null && Inventory != null && Inventory.CanBePut(prefab))
|
||||
{
|
||||
Entity.Spawner?.AddToSpawnQueue(prefab, Inventory, spawnIfInventoryFull: false);
|
||||
}
|
||||
@@ -407,13 +431,8 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
foreach (Item item in Inventory.Items)
|
||||
{
|
||||
if (item == null) continue;
|
||||
item.Drop(null);
|
||||
}
|
||||
}
|
||||
Inventory.AllItemsMod.ForEach(it => it.Drop(null));
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
@@ -421,26 +440,28 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
string containedString = componentElement.GetAttributeString("contained", "");
|
||||
string[] itemIdStrings = containedString.Split(',');
|
||||
itemIds = new ushort[itemIdStrings.Length];
|
||||
itemIds = new List<ushort>[itemIdStrings.Length];
|
||||
for (int i = 0; i < itemIdStrings.Length; i++)
|
||||
{
|
||||
if (!int.TryParse(itemIdStrings[i], out int id)) { continue; }
|
||||
itemIds[i] = idRemap.GetOffsetId(id);
|
||||
itemIds[i] ??= new List<ushort>();
|
||||
foreach (string idStr in itemIdStrings[i].Split(';'))
|
||||
{
|
||||
if (!int.TryParse(idStr, out int id)) { continue; }
|
||||
itemIds[i].Add(idRemap.GetOffsetId(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
XElement componentElement = base.Save(parentElement);
|
||||
|
||||
string[] itemIdStrings = new string[Inventory.Items.Length];
|
||||
for (int i = 0; i < Inventory.Items.Length; i++)
|
||||
string[] itemIdStrings = new string[Inventory.Capacity];
|
||||
for (int i = 0; i < Inventory.Capacity; i++)
|
||||
{
|
||||
itemIdStrings[i] = (Inventory.Items[i] == null) ? "0" : Inventory.Items[i].ID.ToString();
|
||||
var items = Inventory.GetItemsAt(i);
|
||||
itemIdStrings[i] = string.Join(';', items.Select(it => it.ID.ToString()));
|
||||
}
|
||||
|
||||
componentElement.Add(new XAttribute("contained", string.Join(",", itemIdStrings)));
|
||||
|
||||
componentElement.Add(new XAttribute("contained", string.Join(',', itemIdStrings)));
|
||||
return componentElement;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,13 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(UseEnvironment.Both, false, description: "Can the item be selected in air, underwater or both.")]
|
||||
public UseEnvironment UsableIn { get; set; }
|
||||
|
||||
[Serialize(false, false, description: "Should the character using the item be drawn behind the item.")]
|
||||
public bool DrawUserBehind
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool ControlCharacterPose
|
||||
{
|
||||
get { return limbPositions.Count > 0; }
|
||||
@@ -236,12 +243,12 @@ namespace Barotrauma.Items.Components
|
||||
case LimbType.RightHand:
|
||||
case LimbType.RightForearm:
|
||||
case LimbType.RightArm:
|
||||
if (user.SelectedItems[0] != null) { continue; }
|
||||
if (user.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) != null) { continue; }
|
||||
break;
|
||||
case LimbType.LeftHand:
|
||||
case LimbType.LeftForearm:
|
||||
case LimbType.LeftArm:
|
||||
if ( user.SelectedItems[1] != null) { continue; }
|
||||
if (user.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand) != null) { continue; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -388,6 +395,12 @@ namespace Barotrauma.Items.Components
|
||||
limb.PullJointEnabled = false;
|
||||
}
|
||||
|
||||
//disable flipping for 0.5 seconds, because flipping the character when it's in a weird pose (e.g. lying in bed) can mess up the ragdoll
|
||||
if (character.AnimController is HumanoidAnimController humanoidAnim)
|
||||
{
|
||||
humanoidAnim.LockFlippingUntil = (float)Timing.TotalTime + 0.5f;
|
||||
}
|
||||
|
||||
if (character.SelectedConstruction == this.item) { character.SelectedConstruction = null; }
|
||||
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
@@ -470,6 +483,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasAccess(Character character)
|
||||
{
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
return base.HasAccess(character);
|
||||
}
|
||||
|
||||
partial void HideHUDs(bool value);
|
||||
}
|
||||
}
|
||||
|
||||
+83
-57
@@ -1,5 +1,7 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -59,7 +61,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
MoveInputQueue();
|
||||
|
||||
if (inputContainer == null || inputContainer.Inventory.Items.All(i => i == null))
|
||||
if (inputContainer == null || inputContainer.Inventory.IsEmpty())
|
||||
{
|
||||
SetActive(false);
|
||||
return;
|
||||
@@ -79,7 +81,7 @@ namespace Barotrauma.Items.Components
|
||||
if (powerConsumption <= 0.0f) { Voltage = 1.0f; }
|
||||
progressTimer += deltaTime * Math.Min(Voltage, 1.0f);
|
||||
|
||||
var targetItem = inputContainer.Inventory.Items.LastOrDefault(i => i != null);
|
||||
var targetItem = inputContainer.Inventory.LastOrDefault();
|
||||
if (targetItem == null) { return; }
|
||||
|
||||
float deconstructTime = targetItem.Prefab.DeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / DeconstructionSpeed : 1.0f;
|
||||
@@ -87,78 +89,107 @@ namespace Barotrauma.Items.Components
|
||||
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
|
||||
if (progressTimer > deconstructTime)
|
||||
{
|
||||
int emptySlots = outputContainer.Inventory.Items.Where(i => i == null).Count();
|
||||
// In multiplayer, the server handles the deconstruction into new items
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
|
||||
if (targetItem.Prefab.RandomDeconstructionOutput)
|
||||
{
|
||||
int amount = targetItem.Prefab.RandomDeconstructionOutputAmount;
|
||||
List<int> deconstructItemIndexes = new List<int>();
|
||||
for (int i = 0; i < targetItem.Prefab.DeconstructItems.Count; i++)
|
||||
{
|
||||
deconstructItemIndexes.Add(i);
|
||||
}
|
||||
List<float> commonness = targetItem.Prefab.DeconstructItems.Select(i => i.Commonness).ToList();
|
||||
List<DeconstructItem> products = new List<DeconstructItem>();
|
||||
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
if (deconstructItemIndexes.Count < 1) { break; }
|
||||
var itemIndex = ToolBox.SelectWeightedRandom(deconstructItemIndexes, commonness, Rand.RandSync.Unsynced);
|
||||
products.Add(targetItem.Prefab.DeconstructItems[itemIndex]);
|
||||
var removeIndex = deconstructItemIndexes.IndexOf(itemIndex);
|
||||
deconstructItemIndexes.RemoveAt(removeIndex);
|
||||
commonness.RemoveAt(removeIndex);
|
||||
}
|
||||
foreach (DeconstructItem deconstructProduct in products)
|
||||
{
|
||||
CreateDeconstructProduct(deconstructProduct);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
|
||||
{
|
||||
CreateDeconstructProduct(deconstructProduct);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateDeconstructProduct(DeconstructItem deconstructProduct)
|
||||
{
|
||||
float percentageHealth = targetItem.Condition / targetItem.Prefab.Health;
|
||||
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) continue;
|
||||
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; }
|
||||
|
||||
if (!(MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!");
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
float condition = deconstructProduct.CopyCondition ?
|
||||
percentageHealth * itemPrefab.Health :
|
||||
itemPrefab.Health * deconstructProduct.OutCondition;
|
||||
|
||||
//container full, drop the items outside the deconstructor
|
||||
if (emptySlots <= 0)
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine, condition);
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition);
|
||||
emptySlots--;
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
if (targetItem.Prefab.AllowDeconstruct)
|
||||
{
|
||||
//drop all items that are inside the deconstructed item
|
||||
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
|
||||
for (int i = 0; i < outputContainer.Capacity; i++)
|
||||
{
|
||||
if (ic?.Inventory?.Items == null || ic.RemoveContainedItemsOnDeconstruct) { continue; }
|
||||
foreach (Item containedItem in ic.Inventory.Items)
|
||||
var containedItem = outputContainer.Inventory.GetItemAt(i);
|
||||
if (containedItem?.Combine(spawnedItem, null) ?? false)
|
||||
{
|
||||
containedItem?.Drop(dropper: null, createNetworkEvent: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inputContainer.Inventory.RemoveItem(targetItem);
|
||||
Entity.Spawner.AddToRemoveQueue(targetItem);
|
||||
MoveInputQueue();
|
||||
PutItemsToLinkedContainer();
|
||||
});
|
||||
}
|
||||
|
||||
if (targetItem.Prefab.AllowDeconstruct)
|
||||
{
|
||||
//drop all items that are inside the deconstructed item
|
||||
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
|
||||
{
|
||||
if (ic?.Inventory == null || ic.RemoveContainedItemsOnDeconstruct) { continue; }
|
||||
ic.Inventory.AllItemsMod.ForEach(containedItem => outputContainer.Inventory.TryPutItem(containedItem, user: null));
|
||||
}
|
||||
inputContainer.Inventory.RemoveItem(targetItem);
|
||||
Entity.Spawner.AddToRemoveQueue(targetItem);
|
||||
MoveInputQueue();
|
||||
PutItemsToLinkedContainer();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!outputContainer.Inventory.CanBePut(targetItem))
|
||||
{
|
||||
targetItem.Drop(dropper: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (outputContainer.Inventory.Items.All(i => i != null))
|
||||
{
|
||||
targetItem.Drop(dropper: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
|
||||
}
|
||||
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
|
||||
}
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
progressTimer = 0.0f;
|
||||
progressState = 0.0f;
|
||||
}
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
progressTimer = 0.0f;
|
||||
progressState = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void PutItemsToLinkedContainer()
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (outputContainer.Inventory.Items.All(it => it == null)) return;
|
||||
if (outputContainer.Inventory.IsEmpty()) { return; }
|
||||
|
||||
foreach (MapEntity linkedTo in item.linkedTo)
|
||||
{
|
||||
@@ -168,13 +199,7 @@ namespace Barotrauma.Items.Components
|
||||
if (fabricator != null) { continue; }
|
||||
var itemContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null) { continue; }
|
||||
|
||||
foreach (Item containedItem in outputContainer.Inventory.Items)
|
||||
{
|
||||
if (containedItem == null) { continue; }
|
||||
if (itemContainer.Inventory.Items.All(it => it != null)) { break; }
|
||||
itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true);
|
||||
}
|
||||
outputContainer.Inventory.AllItemsMod.ForEach(containedItem => itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,9 +211,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
for (int i = inputContainer.Inventory.Capacity - 2; i >= 0; i--)
|
||||
{
|
||||
if (inputContainer.Inventory.Items[i] != null && inputContainer.Inventory.Items[i + 1] == null)
|
||||
while (inputContainer.Inventory.GetItemAt(i) is Item item1 && inputContainer.Inventory.CanBePut(item1, i + 1))
|
||||
{
|
||||
inputContainer.Inventory.TryPutItem(inputContainer.Inventory.Items[i], i + 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: true);
|
||||
if (!inputContainer.Inventory.TryPutItem(item1, i + 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: true))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,18 +225,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
PutItemsToLinkedContainer();
|
||||
|
||||
if (inputContainer.Inventory.Items.All(i => i == null)) { active = false; }
|
||||
if (inputContainer.Inventory.IsEmpty()) { active = false; }
|
||||
|
||||
IsActive = active;
|
||||
currPowerConsumption = IsActive ? powerConsumption : 0.0f;
|
||||
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!IsActive)
|
||||
{
|
||||
progressTimer = 0.0f;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma.Items.Components
|
||||
public Character User;
|
||||
|
||||
[Editable(0.0f, 10000000.0f),
|
||||
Serialize(2000.0f, true, description: "The amount of force exerted on the submarine when the engine is operating at full power.")]
|
||||
Serialize(500.0f, true, description: "The amount of force exerted on the submarine when the engine is operating at full power.")]
|
||||
public float MaxForce
|
||||
{
|
||||
get { return maxForce; }
|
||||
@@ -46,6 +46,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true)]
|
||||
public bool DisablePropellerDamage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public float Force
|
||||
{
|
||||
get { return force;}
|
||||
@@ -117,6 +124,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f);
|
||||
//less effective when in a bad condition
|
||||
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition);
|
||||
if (item.Submarine.FlippedX) { currForce *= -1; }
|
||||
item.Submarine.ApplyForce(currForce);
|
||||
UpdatePropellerDamage(deltaTime);
|
||||
float maxChangeSpeed = 0.5f;
|
||||
@@ -130,7 +138,7 @@ namespace Barotrauma.Items.Components
|
||||
if (particleTimer <= 0.0f)
|
||||
{
|
||||
Vector2 particleVel = -currForce.ClampLength(5000.0f) / 5.0f;
|
||||
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos,
|
||||
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos * item.Scale,
|
||||
particleVel * Rand.Range(0.9f, 1.1f),
|
||||
0.0f, item.CurrentHull);
|
||||
particleTimer = 1.0f / particlesPerSec;
|
||||
@@ -154,19 +162,22 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void UpdatePropellerDamage(float deltaTime)
|
||||
{
|
||||
if (DisablePropellerDamage) { return; }
|
||||
|
||||
damageTimer += deltaTime;
|
||||
if (damageTimer < 0.5f) return;
|
||||
if (damageTimer < 0.5f) { return; }
|
||||
damageTimer = 0.1f;
|
||||
|
||||
if (propellerDamage == null) return;
|
||||
Vector2 propellerWorldPos = item.WorldPosition + PropellerPos;
|
||||
if (propellerDamage == null) { return; }
|
||||
|
||||
float scaledDamageRange = propellerDamage.DamageRange * item.Scale;
|
||||
|
||||
Vector2 propellerWorldPos = item.WorldPosition + PropellerPos * item.Scale;
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.Submarine != null || !character.Enabled || character.Removed) continue;
|
||||
|
||||
float dist = Vector2.DistanceSquared(character.WorldPosition, propellerWorldPos);
|
||||
if (dist > propellerDamage.DamageRange * propellerDamage.DamageRange) continue;
|
||||
|
||||
if (character.Submarine != null || !character.Enabled || character.Removed) { continue; }
|
||||
float distSqr = Vector2.DistanceSquared(character.WorldPosition, propellerWorldPos);
|
||||
if (distSqr > scaledDamageRange * scaledDamageRange) { continue; }
|
||||
character.LastDamageSource = item;
|
||||
propellerDamage.DoDamage(null, character, propellerWorldPos, 1.0f, true);
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
return (picker != null);
|
||||
return picker != null;
|
||||
}
|
||||
|
||||
public void RemoveFabricationRecipes(List<string> allowedIdentifiers)
|
||||
@@ -161,10 +161,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void CreateRecipes();
|
||||
|
||||
private void StartFabricating(FabricationRecipe selectedItem, Character user)
|
||||
private void StartFabricating(FabricationRecipe selectedItem, Character user, bool addToServerLog = true)
|
||||
{
|
||||
if (selectedItem == null) { return; }
|
||||
if (!outputContainer.Inventory.IsEmpty()) { return; }
|
||||
if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem)) { return; }
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = false;
|
||||
@@ -190,7 +190,7 @@ namespace Barotrauma.Items.Components
|
||||
State = FabricatorState.Active;
|
||||
}
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
if (user != null && addToServerLog)
|
||||
{
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
@@ -298,20 +298,24 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Character tempUser = user;
|
||||
if (outputContainer.Inventory.Items.All(i => i != null))
|
||||
int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem);
|
||||
for (int i = 0; i < fabricatedItem.Amount; i++)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
|
||||
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
|
||||
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
|
||||
if (i < amountFittingContainer)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
|
||||
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
|
||||
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
|
||||
}
|
||||
}
|
||||
|
||||
static void onItemSpawned(Item spawnedItem, Character user)
|
||||
{
|
||||
if (user != null && user.TeamID != Character.TeamType.None)
|
||||
if (user != null && user.TeamID != CharacterTeamType.None)
|
||||
{
|
||||
foreach (WifiComponent wifiComponent in spawnedItem.GetComponents<WifiComponent>())
|
||||
{
|
||||
@@ -328,10 +332,23 @@ namespace Barotrauma.Items.Components
|
||||
user.Info.IncreaseSkillLevel(
|
||||
skill.Identifier,
|
||||
skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f),
|
||||
user.WorldPosition + Vector2.UnitY * 150.0f);
|
||||
user.Position + Vector2.UnitY * 150.0f);
|
||||
}
|
||||
}
|
||||
|
||||
//disabled "continuous fabrication" for now
|
||||
//before we enable it, there should be some UI controls for fabricating a specific number of items
|
||||
|
||||
/*var prevFabricatedItem = fabricatedItem;
|
||||
var prevUser = user;
|
||||
CancelFabricating();
|
||||
if (CanBeFabricated(prevFabricatedItem))
|
||||
{
|
||||
//keep fabricating if we can fabricate more
|
||||
StartFabricating(prevFabricatedItem, prevUser, addToServerLog: false);
|
||||
}*/
|
||||
|
||||
|
||||
CancelFabricating();
|
||||
}
|
||||
}
|
||||
@@ -375,7 +392,7 @@ namespace Barotrauma.Items.Components
|
||||
float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
|
||||
float average = skillSum / skills.Count;
|
||||
|
||||
return ((average + 100.0f) / 2.0f) / 100.0f;
|
||||
return (average + 100.0f) / 2.0f / 100.0f;
|
||||
}
|
||||
|
||||
public override float GetSkillMultiplier()
|
||||
@@ -390,7 +407,7 @@ namespace Barotrauma.Items.Components
|
||||
private List<Item> GetAvailableIngredients()
|
||||
{
|
||||
List<Item> availableIngredients = new List<Item>();
|
||||
availableIngredients.AddRange(inputContainer.Inventory.Items.Where(it => it != null));
|
||||
availableIngredients.AddRange(inputContainer.Inventory.AllItems);
|
||||
foreach (MapEntity linkedTo in item.linkedTo)
|
||||
{
|
||||
if (linkedTo is Item linkedItem)
|
||||
@@ -404,18 +421,18 @@ namespace Barotrauma.Items.Components
|
||||
itemContainer = deconstructor.OutputContainer;
|
||||
}
|
||||
|
||||
availableIngredients.AddRange(itemContainer.Inventory.Items.Where(it => it != null));
|
||||
availableIngredients.AddRange(itemContainer.Inventory.AllItems);
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
if (Character.Controlled?.Inventory != null)
|
||||
{
|
||||
availableIngredients.AddRange(Character.Controlled.Inventory.Items.Distinct().Where(it => it != null));
|
||||
availableIngredients.AddRange(Character.Controlled.Inventory.AllItems);
|
||||
}
|
||||
#else
|
||||
if (user?.Inventory != null)
|
||||
{
|
||||
availableIngredients.AddRange(user.Inventory.Items.Distinct().Where(it => it != null));
|
||||
availableIngredients.AddRange(user.Inventory.AllItems);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -450,9 +467,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else //in another inventory, we need to move the item
|
||||
{
|
||||
if (inputContainer.Inventory.Items.All(it => it != null))
|
||||
if (!inputContainer.Inventory.CanBePut(matchingItem))
|
||||
{
|
||||
var unneededItem = inputContainer.Inventory.Items.FirstOrDefault(it => !usedItems.Contains(it));
|
||||
var unneededItem = inputContainer.Inventory.AllItems.FirstOrDefault(it => !usedItems.Contains(it));
|
||||
unneededItem?.Drop(null, createNetworkEvent: !isClient);
|
||||
}
|
||||
inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: !isClient);
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (item.CurrentHull == null) { return; }
|
||||
|
||||
float powerFactor = Math.Min(currPowerConsumption <= 0.0f ? 1.0f : Voltage, 1.0f);
|
||||
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, 1.0f);
|
||||
|
||||
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
|
||||
//less effective when in a bad condition
|
||||
@@ -112,13 +112,16 @@ namespace Barotrauma.Items.Components
|
||||
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
|
||||
}
|
||||
|
||||
public void InfectBallast(string identifier)
|
||||
public void InfectBallast(string identifier, bool allowMultiplePerShip = false)
|
||||
{
|
||||
Hull hull = item.CurrentHull;
|
||||
if (hull == null) { return; }
|
||||
|
||||
// if the ship is already infected then do nothing
|
||||
if (Hull.hullList.Where(h => h.Submarine == hull.Submarine).Any(h => h.BallastFlora != null)) { return; }
|
||||
if (!allowMultiplePerShip)
|
||||
{
|
||||
// if the ship is already infected then do nothing
|
||||
if (Hull.hullList.Where(h => h.Submarine == hull.Submarine).Any(h => h.BallastFlora != null)) { return; }
|
||||
}
|
||||
|
||||
if (hull.BallastFlora != null) { return; }
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Barotrauma.Items.Components
|
||||
if (LastAIUser.SelectedConstruction != item && LastAIUser.CanInteractWith(item))
|
||||
{
|
||||
AutoTemp = true;
|
||||
unsentChanges = true;
|
||||
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
|
||||
LastAIUser = null;
|
||||
}
|
||||
}
|
||||
@@ -313,12 +313,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (fissionRate > 0.0f)
|
||||
{
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
var containedItems = item.OwnInventory?.AllItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
if (!item.HasTag("reactorfuel")) { continue; }
|
||||
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
|
||||
}
|
||||
@@ -414,8 +413,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool TooMuchFuel()
|
||||
{
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
if (containedItems != null && containedItems.Count(i => i != null) <= 1) { return false; }
|
||||
var containedItems = item.OwnInventory?.AllItems;
|
||||
if (containedItems != null && containedItems.Count() <= 1) { return false; }
|
||||
|
||||
//get the amount of heat we'd generate if the fission rate was at the low end of the optimal range
|
||||
float minimumHeat = GetGeneratedHeat(optimalFissionRate.X);
|
||||
@@ -532,12 +531,11 @@ namespace Barotrauma.Items.Components
|
||||
fireTimer = 0.0f;
|
||||
meltDownTimer = 0.0f;
|
||||
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
var containedItems = item.OwnInventory?.AllItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
if (containedItem == null) { continue; }
|
||||
containedItem.Condition = 0.0f;
|
||||
}
|
||||
}
|
||||
@@ -559,55 +557,58 @@ namespace Barotrauma.Items.Components
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
bool shutDown = objective.Option.Equals("shutdown", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
IsActive = true;
|
||||
|
||||
float degreeOfSuccess = DegreeOfSuccess(character);
|
||||
float refuelLimit = 0.3f;
|
||||
//characters with insufficient skill levels don't refuel the reactor
|
||||
if (degreeOfSuccess > refuelLimit)
|
||||
if (!shutDown)
|
||||
{
|
||||
if (objective.SubObjectives.None())
|
||||
float degreeOfSuccess = DegreeOfSuccess(character);
|
||||
float refuelLimit = 0.3f;
|
||||
//characters with insufficient skill levels don't refuel the reactor
|
||||
if (degreeOfSuccess > refuelLimit)
|
||||
{
|
||||
if (!AIDecontainEmptyItems(character, objective, equip: false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (aiUpdateTimer > 0.0f)
|
||||
{
|
||||
aiUpdateTimer -= deltaTime;
|
||||
return false;
|
||||
}
|
||||
aiUpdateTimer = AIUpdateInterval;
|
||||
|
||||
// load more fuel if the current maximum output is only 50% of the current load
|
||||
// or if the fuel rod is (almost) deplenished
|
||||
float minCondition = fuelConsumptionRate * MathUtils.Pow((degreeOfSuccess - refuelLimit) * 2, 2);
|
||||
if (NeedMoreFuel(minimumOutputRatio: 0.5f, minCondition: minCondition))
|
||||
{
|
||||
var container = item.GetComponent<ItemContainer>();
|
||||
if (objective.SubObjectives.None())
|
||||
{
|
||||
int itemCount = item.ContainedItems.Count(i => i != null && container.ContainableItems.Any(ri => ri.MatchesItem(i))) + 1;
|
||||
AIContainItems<Reactor>(container, character, objective, itemCount, equip: false, removeEmpty: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC, dropItemOnDeselected: true);
|
||||
character.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (TooMuchFuel())
|
||||
{
|
||||
var container = item.GetComponent<ItemContainer>();
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item item in containedItems)
|
||||
if (!AIDecontainEmptyItems(character, objective, equip: false))
|
||||
{
|
||||
if (item != null && container.ContainableItems.Any(ri => ri.MatchesItem(item)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (aiUpdateTimer > 0.0f)
|
||||
{
|
||||
aiUpdateTimer -= deltaTime;
|
||||
return false;
|
||||
}
|
||||
aiUpdateTimer = AIUpdateInterval;
|
||||
|
||||
// load more fuel if the current maximum output is only 50% of the current load
|
||||
// or if the fuel rod is (almost) deplenished
|
||||
float minCondition = fuelConsumptionRate * MathUtils.Pow((degreeOfSuccess - refuelLimit) * 2, 2);
|
||||
if (NeedMoreFuel(minimumOutputRatio: 0.5f, minCondition: minCondition))
|
||||
{
|
||||
var container = item.GetComponent<ItemContainer>();
|
||||
if (objective.SubObjectives.None())
|
||||
{
|
||||
int itemCount = item.ContainedItems.Count(i => i != null && container.ContainableItems.Any(ri => ri.MatchesItem(i))) + 1;
|
||||
AIContainItems<Reactor>(container, character, objective, itemCount, equip: false, removeEmpty: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC, dropItemOnDeselected: true);
|
||||
character.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (TooMuchFuel())
|
||||
{
|
||||
if (item.OwnInventory?.AllItems != null)
|
||||
{
|
||||
var container = item.GetComponent<ItemContainer>();
|
||||
foreach (Item item in item.OwnInventory.AllItemsMod)
|
||||
{
|
||||
item.Drop(character);
|
||||
break;
|
||||
if (container.ContainableItems.Any(ri => ri.MatchesItem(item)))
|
||||
{
|
||||
item.Drop(character);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -624,7 +625,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (LastUserWasPlayer)
|
||||
else if (LastUserWasPlayer && lastUser != null && lastUser.TeamID == character.TeamID)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -636,48 +637,45 @@ namespace Barotrauma.Items.Components
|
||||
float prevFissionRate = targetFissionRate;
|
||||
float prevTurbineOutput = targetTurbineOutput;
|
||||
|
||||
switch (objective.Option.ToLowerInvariant())
|
||||
{
|
||||
case "powerup":
|
||||
PowerOn = true;
|
||||
if (objective.Override || !autoTemp)
|
||||
{
|
||||
//characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
|
||||
if (degreeOfSuccess < 0.5f)
|
||||
{
|
||||
AutoTemp = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoTemp = false;
|
||||
UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f);
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
FissionRateScrollBar.BarScroll = FissionRate / 100.0f;
|
||||
TurbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f;
|
||||
#endif
|
||||
break;
|
||||
case "shutdown":
|
||||
PowerOn = false;
|
||||
AutoTemp = false;
|
||||
targetFissionRate = 0.0f;
|
||||
targetTurbineOutput = 0.0f;
|
||||
unsentChanges = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (autoTemp != prevAutoTemp ||
|
||||
prevPowerOn != _powerOn ||
|
||||
Math.Abs(prevFissionRate - targetFissionRate) > 1.0f ||
|
||||
Math.Abs(prevTurbineOutput - targetTurbineOutput) > 1.0f)
|
||||
if (shutDown)
|
||||
{
|
||||
PowerOn = false;
|
||||
AutoTemp = false;
|
||||
targetFissionRate = 0.0f;
|
||||
targetTurbineOutput = 0.0f;
|
||||
unsentChanges = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerOn = true;
|
||||
if (objective.Override || !autoTemp)
|
||||
{
|
||||
//characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
|
||||
if (degreeOfSuccess < 0.5f)
|
||||
{
|
||||
AutoTemp = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoTemp = false;
|
||||
UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f);
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
FissionRateScrollBar.BarScroll = FissionRate / 100.0f;
|
||||
TurbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f;
|
||||
#endif
|
||||
if (autoTemp != prevAutoTemp ||
|
||||
prevPowerOn != _powerOn ||
|
||||
Math.Abs(prevFissionRate - targetFissionRate) > 1.0f ||
|
||||
Math.Abs(prevTurbineOutput - targetTurbineOutput) > 1.0f)
|
||||
{
|
||||
unsentChanges = true;
|
||||
}
|
||||
aiUpdateTimer = AIUpdateInterval;
|
||||
return false;
|
||||
}
|
||||
|
||||
aiUpdateTimer = AIUpdateInterval;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
@@ -696,14 +694,14 @@ namespace Barotrauma.Items.Components
|
||||
AutoTemp = false;
|
||||
targetFissionRate = 0.0f;
|
||||
targetTurbineOutput = 0.0f;
|
||||
unsentChanges = true;
|
||||
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
|
||||
}
|
||||
break;
|
||||
case "set_fissionrate":
|
||||
if (PowerOn && float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
|
||||
{
|
||||
targetFissionRate = newFissionRate;
|
||||
unsentChanges = true;
|
||||
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
|
||||
#if CLIENT
|
||||
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f;
|
||||
#endif
|
||||
@@ -713,7 +711,7 @@ namespace Barotrauma.Items.Components
|
||||
if (PowerOn && float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
|
||||
{
|
||||
targetTurbineOutput = newTurbineOutput;
|
||||
unsentChanges = true;
|
||||
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
|
||||
#if CLIENT
|
||||
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f;
|
||||
#endif
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace Barotrauma.Items.Components
|
||||
private const float AutoPilotMaxSpeed = 0.5f;
|
||||
private const float AIPilotMaxSpeed = 1.0f;
|
||||
|
||||
private Vector2 currVelocity;
|
||||
private Vector2 targetVelocity;
|
||||
|
||||
private Vector2 steeringInput;
|
||||
@@ -52,7 +51,13 @@ namespace Barotrauma.Items.Components
|
||||
private Sonar sonar;
|
||||
|
||||
private Submarine controlledSub;
|
||||
|
||||
|
||||
private bool showIceSpireWarning;
|
||||
|
||||
private List<Submarine> connectedSubs = new List<Submarine>();
|
||||
private const float ConnectedSubUpdateInterval = 1.0f;
|
||||
float connectedSubUpdateTimer;
|
||||
|
||||
public bool AutoPilot
|
||||
{
|
||||
get { return autoPilot; }
|
||||
@@ -302,6 +307,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
showIceSpireWarning = false;
|
||||
if (user != null && user.Info != null &&
|
||||
user.SelectedConstruction == item &&
|
||||
controlledSub != null && controlledSub.Velocity.LengthSquared() > 0.01f)
|
||||
@@ -326,12 +332,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.SendSignal(0, targetVelocity.X.ToString(CultureInfo.InvariantCulture), "velocity_x_out", user);
|
||||
|
||||
float targetLevel = -targetVelocity.Y;
|
||||
float targetLevel = targetVelocity.X;
|
||||
if (controlledSub != null && controlledSub.FlippedX) { targetLevel *= -1; }
|
||||
item.SendSignal(0, targetLevel.ToString(CultureInfo.InvariantCulture), "velocity_x_out", user);
|
||||
|
||||
targetLevel = -targetVelocity.Y;
|
||||
targetLevel += (neutralBallastLevel - 0.5f) * 100.0f;
|
||||
|
||||
item.SendSignal(0, targetLevel.ToString(CultureInfo.InvariantCulture), "velocity_y_out", user);
|
||||
}
|
||||
|
||||
@@ -345,7 +352,7 @@ namespace Barotrauma.Items.Components
|
||||
user.Info.IncreaseSkillLevel(
|
||||
"helm",
|
||||
SkillSettings.Current.SkillIncreasePerSecondWhenSteering / userSkill * deltaTime,
|
||||
user.WorldPosition + Vector2.UnitY * 150.0f);
|
||||
user.Position + Vector2.UnitY * 150.0f);
|
||||
}
|
||||
|
||||
private void UpdateAutoPilot(float deltaTime)
|
||||
@@ -354,7 +361,8 @@ namespace Barotrauma.Items.Components
|
||||
if (posToMaintain != null)
|
||||
{
|
||||
Vector2 steeringVel = GetSteeringVelocity((Vector2)posToMaintain, 10.0f);
|
||||
TargetVelocity = Vector2.Lerp(TargetVelocity, steeringVel, AutoPilotSteeringLerp);
|
||||
TargetVelocity = Vector2.Lerp(TargetVelocity, steeringVel, AutoPilotSteeringLerp);
|
||||
showIceSpireWarning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -368,9 +376,21 @@ namespace Barotrauma.Items.Components
|
||||
autopilotRecalculatePathTimer = RecalculatePathInterval;
|
||||
}
|
||||
|
||||
if (steeringPath == null) { return; }
|
||||
if (steeringPath == null)
|
||||
{
|
||||
showIceSpireWarning = false;
|
||||
return;
|
||||
}
|
||||
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(controlledSub.WorldPosition), 10.0f);
|
||||
|
||||
connectedSubUpdateTimer -= deltaTime;
|
||||
if (connectedSubUpdateTimer <= 0.0f)
|
||||
{
|
||||
connectedSubs.Clear();
|
||||
connectedSubs = controlledSub?.GetConnectedSubs();
|
||||
connectedSubUpdateTimer = ConnectedSubUpdateInterval;
|
||||
}
|
||||
|
||||
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
|
||||
{
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - controlledSub.WorldPosition);
|
||||
@@ -420,13 +440,14 @@ namespace Barotrauma.Items.Components
|
||||
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.Y), controlledSub.Borders.Height * 0.75f));
|
||||
|
||||
float avoidRadius = avoidDist.Length();
|
||||
float damagingWallAvoidRadius = avoidRadius * 1.5f;
|
||||
float damagingWallAvoidRadius = MathHelper.Clamp(avoidRadius * 1.5f, 5000.0f, 10000.0f);
|
||||
|
||||
Vector2 newAvoidStrength = Vector2.Zero;
|
||||
|
||||
debugDrawObstacles.Clear();
|
||||
|
||||
//steer away from nearby walls
|
||||
showIceSpireWarning = false;
|
||||
var closeCells = Level.Loaded.GetCells(controlledSub.WorldPosition, 4);
|
||||
foreach (VoronoiCell cell in closeCells)
|
||||
{
|
||||
@@ -435,12 +456,22 @@ namespace Barotrauma.Items.Components
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
Vector2 closestPoint = MathUtils.GetClosestPointOnLineSegment(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition);
|
||||
float dist = Vector2.Distance(closestPoint, controlledSub.WorldPosition);
|
||||
Vector2 diff = closestPoint - controlledSub.WorldPosition;
|
||||
float dist = diff.Length() - Math.Max(controlledSub.Borders.Width, controlledSub.Borders.Height) / 2;
|
||||
if (dist > damagingWallAvoidRadius) { continue; }
|
||||
Vector2 diff = controlledSub.WorldPosition - cell.Center;
|
||||
Vector2 avoid = Vector2.Normalize(diff) * (damagingWallAvoidRadius - dist) / damagingWallAvoidRadius;
|
||||
|
||||
Vector2 normalizedDiff = Vector2.Normalize(diff);
|
||||
float dot = Vector2.Dot(normalizedDiff, controlledSub.Velocity);
|
||||
|
||||
float avoidStrength = MathHelper.Clamp(MathHelper.Lerp(1.0f, 0.0f, dist / damagingWallAvoidRadius - dot), 0.0f, 1.0f);
|
||||
Vector2 avoid = -normalizedDiff * avoidStrength;
|
||||
newAvoidStrength += avoid;
|
||||
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, edge.Center, 1.0f, avoid, cell.Translation));
|
||||
|
||||
if (dot > 0.0f)
|
||||
{
|
||||
showIceSpireWarning = true;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -456,7 +487,7 @@ namespace Barotrauma.Items.Components
|
||||
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, 0.0f, Vector2.Zero, Vector2.Zero));
|
||||
continue;
|
||||
}
|
||||
if (diff.LengthSquared() < 1.0f) diff = Vector2.UnitY;
|
||||
if (diff.LengthSquared() < 1.0f) { diff = Vector2.UnitY; }
|
||||
|
||||
Vector2 normalizedDiff = Vector2.Normalize(diff);
|
||||
float dot = controlledSub.Velocity == Vector2.Zero ?
|
||||
@@ -483,8 +514,7 @@ namespace Barotrauma.Items.Components
|
||||
//steer away from other subs
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub == controlledSub) { continue; }
|
||||
if (controlledSub.DockedTo.Contains(sub)) { continue; }
|
||||
if (sub == controlledSub || connectedSubs.Contains(sub)) { continue; }
|
||||
Point sizeSum = controlledSub.Borders.Size + sub.Borders.Size;
|
||||
Vector2 minDist = sizeSum.ToVector2() / 2;
|
||||
Vector2 diff = controlledSub.WorldPosition - sub.WorldPosition;
|
||||
@@ -659,6 +689,10 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
}
|
||||
sonar?.AIOperate(deltaTime, character, objective);
|
||||
if (!MaintainPos && showIceSpireWarning)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogicespirespottedsonar"), null, 0.0f, "icespirespottedsonar", 60.0f);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -666,7 +700,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (connection.Name == "velocity_in")
|
||||
{
|
||||
currVelocity = XMLExtensions.ParseVector2(signal, false);
|
||||
TargetVelocity = XMLExtensions.ParseVector2(signal, errorMessages: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class NameTag : ItemComponent
|
||||
{
|
||||
[InGameEditable, Serialize("", false, description: "Name written on the tag.", alwaysUseInstanceValues: true)]
|
||||
[InGameEditable(MaxLength = 32), Serialize("", false, description: "Name written on the tag.", alwaysUseInstanceValues: true)]
|
||||
public string WrittenName { get; set; }
|
||||
|
||||
public NameTag(Item item, XElement element) : base(item, element)
|
||||
|
||||
@@ -74,6 +74,8 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(100f, true, "How much fertilizer can the planter hold.")]
|
||||
public float FertilizerCapacity { get; set; }
|
||||
|
||||
public string LastAction { get; set; } = "";
|
||||
|
||||
public Growable?[] GrowableSeeds = new Growable?[0];
|
||||
|
||||
private readonly List<RelatedItem> SuitableFertilizer = new List<RelatedItem>();
|
||||
@@ -168,6 +170,8 @@ namespace Barotrauma.Items.Components
|
||||
switch (plantItem.Type)
|
||||
{
|
||||
case PlantItemType.Seed:
|
||||
LastAction = "PlantSeed";
|
||||
ApplyStatusEffects(ActionType.OnPicked, 1.0f, character);
|
||||
return container.Inventory.TryPutItem(plantItem.Item, character, new List<InvSlotType> { InvSlotType.Any });
|
||||
case PlantItemType.Fertilizer when plantItem.Item != null:
|
||||
float canAdd = FertilizerCapacity - Fertilizer;
|
||||
@@ -178,6 +182,8 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
character.UpdateHUDProgressBar(this, Item.DrawPosition, Fertilizer / FertilizerCapacity, Color.SaddleBrown, Color.SaddleBrown, "entityname.fertilizer");
|
||||
#endif
|
||||
LastAction = "ApplyFertilizer";
|
||||
ApplyStatusEffects(ActionType.OnPicked, 1.0f, character);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -203,6 +209,8 @@ namespace Barotrauma.Items.Components
|
||||
container?.Inventory.RemoveItem(seed.Item);
|
||||
Entity.Spawner?.AddToRemoveQueue(seed.Item);
|
||||
GrowableSeeds[i] = null;
|
||||
LastAction = "Harvest";
|
||||
ApplyStatusEffects(ActionType.OnPicked, 1.0f, character);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -226,12 +234,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (container?.Inventory == null) { return; }
|
||||
|
||||
for (var i = 0; i < container.Inventory.Items.Length; i++)
|
||||
for (var i = 0; i < container.Inventory.Capacity; i++)
|
||||
{
|
||||
if (i < 0 || GrowableSeeds.Length <= i) { continue; }
|
||||
|
||||
Item containedItem = container.Inventory.Items[i];
|
||||
|
||||
Item containedItem = container.Inventory.GetItemAt(i);
|
||||
Growable? growable = containedItem?.GetComponent<Growable>();
|
||||
|
||||
if (growable != null)
|
||||
@@ -289,11 +296,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private SuitablePlantItem GetSuitableItem(Character character)
|
||||
{
|
||||
foreach (Item heldItem in character.SelectedItems)
|
||||
foreach (Item heldItem in character.HeldItems)
|
||||
{
|
||||
if (heldItem == null) { continue; }
|
||||
|
||||
if (container?.Inventory != null && !container.Inventory.IsFull())
|
||||
if (container?.Inventory != null && container.Inventory.CanBePut(heldItem))
|
||||
{
|
||||
if (heldItem.GetComponent<Growable>() != null && SuitableSeeds.Any(ri => ri.MatchesItem(heldItem)))
|
||||
{
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!powerOnSoundPlayed && powerOnSound != null)
|
||||
{
|
||||
SoundPlayer.PlaySound(powerOnSound.Sound, item.WorldPosition, powerOnSound.Volume, powerOnSound.Range, hullGuess: item.CurrentHull);
|
||||
SoundPlayer.PlaySound(powerOnSound.Sound, item.WorldPosition, powerOnSound.Volume, powerOnSound.Range, hullGuess: item.CurrentHull, ignoreMuffling: powerOnSound.IgnoreMuffling);
|
||||
powerOnSoundPlayed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,7 +559,7 @@ namespace Barotrauma.Items.Components
|
||||
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) - dir,
|
||||
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) + dir,
|
||||
collisionCategory: Physics.CollisionWall);
|
||||
if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure structure &&
|
||||
if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure &&
|
||||
//ignore the hit if it's behind the position the item was launched from, and the projectile is travelling in the opposite direction
|
||||
Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0)
|
||||
{
|
||||
@@ -656,8 +656,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (character != null) { character.LastDamageSource = item; }
|
||||
|
||||
ActionType actionType = ActionType.OnUse;
|
||||
if (_user != null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(_user))
|
||||
{
|
||||
actionType = ActionType.OnFailure;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
PlaySound(ActionType.OnUse, user: _user);
|
||||
PlaySound(actionType, user: _user);
|
||||
PlaySound(ActionType.OnImpact, user: _user);
|
||||
#endif
|
||||
|
||||
@@ -665,7 +671,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (target.Body.UserData is Limb targetLimb)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, character, targetLimb, user: _user);
|
||||
ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: _user);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: _user);
|
||||
var attack = targetLimb.attack;
|
||||
if (attack != null)
|
||||
@@ -695,19 +701,19 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, this, targetLimb.character.ID, targetLimb, (ushort)0, item.WorldPosition });
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, actionType, this, targetLimb.character.ID, targetLimb, (ushort)0, item.WorldPosition });
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact, this, targetLimb.character.ID, targetLimb, (ushort)0, item.WorldPosition });
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
|
||||
ApplyStatusEffects(actionType, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, this, (ushort)0, null, (target.Body.UserData as Entity)?.ID ?? 0, item.WorldPosition });
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, actionType, this, (ushort)0, null, (target.Body.UserData as Entity)?.ID ?? 0, item.WorldPosition });
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact, this, (ushort)0, null, (target.Body.UserData as Entity)?.ID ?? 0, item.WorldPosition });
|
||||
}
|
||||
#endif
|
||||
@@ -764,12 +770,11 @@ namespace Barotrauma.Items.Components
|
||||
item.body.LinearVelocity *= 0.5f;
|
||||
}
|
||||
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
var containedItems = item.OwnInventory?.AllItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item contained in containedItems)
|
||||
{
|
||||
if (contained == null) { continue; }
|
||||
if (contained.body != null)
|
||||
{
|
||||
contained.SetTransform(item.SimPosition, contained.body.Rotation);
|
||||
|
||||
@@ -351,7 +351,7 @@ namespace Barotrauma.Items.Components
|
||||
float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
|
||||
CurrentFixer.Info.IncreaseSkillLevel(skill.Identifier,
|
||||
SkillSettings.Current.SkillIncreasePerRepair / Math.Max(characterSkillLevel, 1.0f),
|
||||
CurrentFixer.WorldPosition + Vector2.UnitY * 100.0f);
|
||||
CurrentFixer.Position + Vector2.UnitY * 100.0f);
|
||||
}
|
||||
SteamAchievementManager.OnItemRepaired(item, CurrentFixer);
|
||||
}
|
||||
@@ -381,7 +381,7 @@ namespace Barotrauma.Items.Components
|
||||
float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
|
||||
CurrentFixer.Info.IncreaseSkillLevel(skill.Identifier,
|
||||
SkillSettings.Current.SkillIncreasePerSabotage / Math.Max(characterSkillLevel, 1.0f),
|
||||
CurrentFixer.WorldPosition + Vector2.UnitY * 100.0f);
|
||||
CurrentFixer.Position + Vector2.UnitY * 100.0f);
|
||||
}
|
||||
|
||||
deteriorationTimer = 0.0f;
|
||||
|
||||
+8
-5
@@ -52,15 +52,18 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
sealed public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
bool deactivate = true;
|
||||
bool earlyReturn = false;
|
||||
for (int i = 0; i < timeSinceReceived.Length; i++)
|
||||
{
|
||||
if (timeSinceReceived[i] > timeFrame)
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
deactivate &= timeSinceReceived[i] > timeFrame;
|
||||
earlyReturn |= timeSinceReceived[i] > timeFrame;
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
}
|
||||
// only stop Update() if both signals timed-out. if IsActive == false, then the component stops updating.
|
||||
IsActive = !deactivate;
|
||||
// early return if either of the signal timed-out
|
||||
if (earlyReturn) { return; }
|
||||
float output = Calculate(receivedSignal[0], receivedSignal[1]);
|
||||
if (MathUtils.IsValid(output))
|
||||
{
|
||||
|
||||
+16
-1
@@ -1,9 +1,23 @@
|
||||
using System.Xml.Linq;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class ConcatComponent : StringComponent
|
||||
{
|
||||
private int maxOutputLength;
|
||||
|
||||
[Editable, Serialize(256, false, description: "The maximum length of the output string. Warning: Large values can lead to large memory usage or networking load.")]
|
||||
public int MaxOutputLength
|
||||
{
|
||||
get { return maxOutputLength; }
|
||||
set
|
||||
{
|
||||
maxOutputLength = Math.Max(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ConcatComponent(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -11,7 +25,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected override string Calculate(string signal1, string signal2)
|
||||
{
|
||||
return signal1 + signal2;
|
||||
string output = signal1 + signal2;
|
||||
return output.Length <= maxOutputLength ? output : output.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Connection
|
||||
{
|
||||
//how many wires can be linked to a single connector
|
||||
public const int MaxLinked = 5;
|
||||
//how many wires can be linked to connectors by default
|
||||
private const int DefaultMaxWires = 5;
|
||||
|
||||
//how many wires can be linked to this connection
|
||||
public readonly int MaxWires = 5;
|
||||
|
||||
public readonly string Name;
|
||||
public readonly string DisplayName;
|
||||
|
||||
private Wire[] wires;
|
||||
private readonly Wire[] wires;
|
||||
public IEnumerable<Wire> Wires
|
||||
{
|
||||
get { return wires; }
|
||||
@@ -77,7 +80,8 @@ namespace Barotrauma.Items.Components
|
||||
ConnectionPanel = connectionPanel;
|
||||
item = connectionPanel.Item;
|
||||
|
||||
wires = new Wire[MaxLinked];
|
||||
MaxWires = element.GetAttributeInt("maxwires", DefaultMaxWires);
|
||||
wires = new Wire[MaxWires];
|
||||
|
||||
IsOutput = element.Name.ToString() == "output";
|
||||
Name = element.GetAttributeString("name", IsOutput ? "output" : "input");
|
||||
@@ -135,7 +139,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
Effects = new List<StatusEffect>();
|
||||
|
||||
wireId = new ushort[MaxLinked];
|
||||
wireId = new ushort[MaxWires];
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -143,7 +147,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
case "link":
|
||||
int index = -1;
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wireId[i] < 1) index = i;
|
||||
}
|
||||
@@ -173,7 +177,7 @@ namespace Barotrauma.Items.Components
|
||||
private void RefreshRecipients()
|
||||
{
|
||||
recipients.Clear();
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null) continue;
|
||||
Connection recipient = wires[i].OtherConnection(this);
|
||||
@@ -184,7 +188,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public int FindEmptyIndex()
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null) return i;
|
||||
}
|
||||
@@ -193,7 +197,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public int FindWireIndex(Wire wire)
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == wire) return i;
|
||||
}
|
||||
@@ -202,7 +206,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public int FindWireIndex(Item wireItem)
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null && wireItem == null) return i;
|
||||
if (wires[i] != null && wires[i].Item == wireItem) return i;
|
||||
@@ -212,7 +216,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool TryAddLink(Wire wire)
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null)
|
||||
{
|
||||
@@ -250,7 +254,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void SendSignal(int stepsTaken, string signal, Item source, Character sender, float power, float signalStrength = 1.0f)
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null) { continue; }
|
||||
|
||||
@@ -274,7 +278,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void SendPowerProbeSignal(Item source, float power)
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null) { continue; }
|
||||
|
||||
@@ -286,7 +290,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
public void ClearConnections()
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null) continue;
|
||||
|
||||
@@ -300,7 +304,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (wireId == null) return;
|
||||
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wireId[i] == 0) { continue; }
|
||||
|
||||
@@ -329,7 +333,7 @@ namespace Barotrauma.Items.Components
|
||||
return wire1.Item.ID.CompareTo(wire2.Item.ID);
|
||||
});
|
||||
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null) continue;
|
||||
|
||||
|
||||
+28
-8
@@ -1,7 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
@@ -111,7 +109,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
if (item.body != null)
|
||||
if (item.body != null && item.body.BodyType == FarseerPhysics.BodyType.Dynamic)
|
||||
{
|
||||
var holdable = item.GetComponent<Holdable>();
|
||||
if (holdable == null || !holdable.Attachable)
|
||||
@@ -130,12 +128,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (Wire wire in c.Wires)
|
||||
{
|
||||
if (wire == null) continue;
|
||||
if (wire == null) { continue; }
|
||||
#if CLIENT
|
||||
if (wire.Item.IsSelected) continue;
|
||||
if (wire.Item.IsSelected) { continue; }
|
||||
#endif
|
||||
var wireNodes = wire.GetNodes();
|
||||
if (wireNodes.Count == 0) continue;
|
||||
if (wireNodes.Count == 0) { continue; }
|
||||
|
||||
if (Submarine.RectContains(item.Rect, wireNodes[0] + wireNodeOffset))
|
||||
{
|
||||
@@ -184,7 +182,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//attaching wires to items with a body is not allowed
|
||||
//(signal items remove their bodies when attached to a wall)
|
||||
if (item.body != null)
|
||||
if (item.body != null && item.body.BodyType == FarseerPhysics.BodyType.Dynamic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -247,10 +245,32 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (int i = 0; i < loadedConnections.Count && i < Connections.Count; i++)
|
||||
{
|
||||
loadedConnections[i].wireId.CopyTo(Connections[i].wireId, 0);
|
||||
if (loadedConnections[i].wireId.Length == Connections[i].wireId.Length)
|
||||
{
|
||||
loadedConnections[i].wireId.CopyTo(Connections[i].wireId, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
//backwards compatibility when maximum number of wires has changed
|
||||
foreach (ushort id in loadedConnections[i].wireId)
|
||||
{
|
||||
for (int j = 0; j < Connections[i].wireId.Length; j++)
|
||||
{
|
||||
if (Connections[i].wireId[j] == 0)
|
||||
{
|
||||
Connections[i].wireId[j] = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedWireIds = element.GetAttributeUshortArray("disconnectedwires", new ushort[0]).ToList();
|
||||
for (int i = 0; i < disconnectedWireIds.Count; i++)
|
||||
{
|
||||
disconnectedWireIds[i] = idRemap.GetOffsetId(disconnectedWireIds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
|
||||
+130
-21
@@ -12,25 +12,72 @@ namespace Barotrauma.Items.Components
|
||||
public bool ContinuousSignal;
|
||||
public bool State;
|
||||
public string ConnectionName;
|
||||
public string PropertyName;
|
||||
public Connection Connection;
|
||||
|
||||
[Serialize("", false, translationTextTag: "Label.", description: "The text displayed on this button/tickbox."), Editable]
|
||||
public string Label { get; set; }
|
||||
[Serialize("1", false, description: "The signal sent out when this button is pressed or this tickbox checked."), Editable]
|
||||
public string Signal { get; set; }
|
||||
|
||||
public string PropertyName { get; }
|
||||
public bool TargetOnlyParentProperty { get; }
|
||||
public int NumberInputMin { get; }
|
||||
public int NumberInputMax { get; }
|
||||
public const int DefaultNumberInputMin = 0, DefaultNumberInputMax = 99;
|
||||
public bool IsIntegerInput { get; }
|
||||
public bool HasPropertyName { get; }
|
||||
public bool ShouldSetProperty { get; set; }
|
||||
|
||||
public string Name => "CustomInterfaceElement";
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
|
||||
|
||||
public List<StatusEffect> StatusEffects = new List<StatusEffect>();
|
||||
|
||||
public CustomInterfaceElement(XElement element)
|
||||
/// <summary>
|
||||
/// Pass the parent component to the constructor to access the serializable properties
|
||||
/// for elements which change property values.
|
||||
/// </summary>
|
||||
public CustomInterfaceElement(XElement element, CustomInterface parent)
|
||||
{
|
||||
Label = element.GetAttributeString("text", "");
|
||||
ConnectionName = element.GetAttributeString("connection", "");
|
||||
PropertyName = element.GetAttributeString("propertyname", "").ToLowerInvariant();
|
||||
Signal = element.GetAttributeString("signal", "1");
|
||||
TargetOnlyParentProperty = element.GetAttributeBool("targetonlyparentproperty", false);
|
||||
NumberInputMin = element.GetAttributeInt("min", DefaultNumberInputMin);
|
||||
NumberInputMax = element.GetAttributeInt("max", DefaultNumberInputMax);
|
||||
|
||||
HasPropertyName = !string.IsNullOrEmpty(PropertyName);
|
||||
IsIntegerInput = HasPropertyName && element.Name.ToString().ToLowerInvariant() == "integerinput";
|
||||
|
||||
if (element.Attribute("signal") is XAttribute attribute)
|
||||
{
|
||||
Signal = attribute.Value;
|
||||
ShouldSetProperty = HasPropertyName;
|
||||
}
|
||||
else if (HasPropertyName && parent != null)
|
||||
{
|
||||
if (TargetOnlyParentProperty)
|
||||
{
|
||||
if (parent.SerializableProperties.ContainsKey(PropertyName))
|
||||
{
|
||||
Signal = parent.SerializableProperties[PropertyName].GetValue(parent) as string;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (ISerializableEntity e in parent.item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(PropertyName)) { continue; }
|
||||
Signal = e.SerializableProperties[PropertyName].GetValue(e) as string;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Signal = "1";
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -50,13 +97,14 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
string[] splitValues = value == "" ? new string[0] : value.Split(',');
|
||||
if (customInterfaceElementList.Count > 0)
|
||||
{
|
||||
string[] splitValues = value == "" ? new string[0] : value.Split(',');
|
||||
UpdateLabels(splitValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string[] signals;
|
||||
[Serialize("", true, description: "The signals sent when the buttons are pressed or the tickboxes checked, separated by commas.")]
|
||||
public string Signals
|
||||
@@ -67,34 +115,29 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
string[] splitValues = value == "" ? new string[0] : value.Split(';');
|
||||
if (customInterfaceElementList.Count > 0)
|
||||
{
|
||||
signals = new string[customInterfaceElementList.Count];
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
signals[i] = i < splitValues.Length ? splitValues[i] : customInterfaceElementList[i].Signal;
|
||||
customInterfaceElementList[i].Signal = signals[i];
|
||||
}
|
||||
string[] splitValues = value == "" ? new string[0] : value.Split(';');
|
||||
UpdateSignals(splitValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
private List<CustomInterfaceElement> customInterfaceElementList = new List<CustomInterfaceElement>();
|
||||
private readonly List<CustomInterfaceElement> customInterfaceElementList = new List<CustomInterfaceElement>();
|
||||
|
||||
public CustomInterface(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "button":
|
||||
case "textbox":
|
||||
var button = new CustomInterfaceElement(subElement)
|
||||
case "integerinput":
|
||||
var button = new CustomInterfaceElement(subElement, this)
|
||||
{
|
||||
ContinuousSignal = false
|
||||
};
|
||||
@@ -105,7 +148,7 @@ namespace Barotrauma.Items.Components
|
||||
customInterfaceElementList.Add(button);
|
||||
break;
|
||||
case "tickbox":
|
||||
var tickBox = new CustomInterfaceElement(subElement)
|
||||
var tickBox = new CustomInterfaceElement(subElement, this)
|
||||
{
|
||||
ContinuousSignal = true
|
||||
};
|
||||
@@ -116,10 +159,9 @@ namespace Barotrauma.Items.Components
|
||||
customInterfaceElementList.Add(tickBox);
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
IsActive = true;
|
||||
InitProjSpecific(element);
|
||||
InitProjSpecific();
|
||||
Labels = element.GetAttributeString("labels", "");
|
||||
Signals = element.GetAttributeString("signals", "");
|
||||
}
|
||||
@@ -142,6 +184,47 @@ namespace Barotrauma.Items.Components
|
||||
UpdateLabelsProjSpecific();
|
||||
}
|
||||
|
||||
private void UpdateSignals(string[] newSignals)
|
||||
{
|
||||
signals = new string[customInterfaceElementList.Count];
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
var element = customInterfaceElementList[i];
|
||||
if (i < newSignals.Length)
|
||||
{
|
||||
var newSignal = newSignals[i];
|
||||
signals[i] = newSignal;
|
||||
element.ShouldSetProperty = element.Signal != newSignal;
|
||||
element.Signal = newSignal;
|
||||
}
|
||||
else
|
||||
{
|
||||
signals[i] = element.Signal;
|
||||
}
|
||||
|
||||
if (element.HasPropertyName && element.ShouldSetProperty)
|
||||
{
|
||||
if (element.TargetOnlyParentProperty)
|
||||
{
|
||||
if (SerializableProperties.ContainsKey(element.PropertyName))
|
||||
{
|
||||
SerializableProperties[element.PropertyName].TrySetValue(this, element.Signal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var po in item.AllPropertyObjects)
|
||||
{
|
||||
if (!po.SerializableProperties.ContainsKey(element.PropertyName)) { continue; }
|
||||
po.SerializableProperties[element.PropertyName].TrySetValue(po, element.Signal);
|
||||
}
|
||||
}
|
||||
customInterfaceElementList[i].ShouldSetProperty = false;
|
||||
}
|
||||
}
|
||||
UpdateSignalsProjSpecific();
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
|
||||
@@ -152,7 +235,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void UpdateLabelsProjSpecific();
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
partial void UpdateSignalsProjSpecific();
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
private void ButtonClicked(CustomInterfaceElement btnElement)
|
||||
{
|
||||
@@ -175,14 +260,38 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void TextChanged(CustomInterfaceElement textElement, string text)
|
||||
{
|
||||
if (textElement == null) { return; }
|
||||
textElement.Signal = text;
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
if (!textElement.TargetOnlyParentProperty)
|
||||
{
|
||||
if (e.SerializableProperties.ContainsKey(textElement.PropertyName))
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(textElement.PropertyName)) { continue; }
|
||||
e.SerializableProperties[textElement.PropertyName].TrySetValue(e, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (SerializableProperties.ContainsKey(textElement.PropertyName))
|
||||
{
|
||||
SerializableProperties[textElement.PropertyName].TrySetValue(this, text);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValueChanged(CustomInterfaceElement numberInputElement, int value)
|
||||
{
|
||||
if (numberInputElement == null) { return; }
|
||||
numberInputElement.Signal = value.ToString();
|
||||
if (!numberInputElement.TargetOnlyParentProperty)
|
||||
{
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(numberInputElement.PropertyName)) { continue; }
|
||||
e.SerializableProperties[numberInputElement.PropertyName].TrySetValue(e, value);
|
||||
}
|
||||
}
|
||||
else if (SerializableProperties.ContainsKey(numberInputElement.PropertyName))
|
||||
{
|
||||
SerializableProperties[numberInputElement.PropertyName].TrySetValue(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public PhysicsBody ParentBody;
|
||||
|
||||
private Turret turret;
|
||||
|
||||
[Serialize(100.0f, true, description: "The range of the emitted light. Higher values are more performance-intensive.", alwaysUseInstanceValues: true),
|
||||
Editable(MinValueFloat = 0.0f, MaxValueFloat = 2048.0f)]
|
||||
public float Range
|
||||
@@ -214,7 +216,14 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = IsOn;
|
||||
item.AddTag("light");
|
||||
}
|
||||
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
SetLightSourceState(IsActive, lightBrightness);
|
||||
turret = item.GetComponent<Turret>();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.AiTarget != null)
|
||||
@@ -232,9 +241,19 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
#if CLIENT
|
||||
light.Position = ParentBody != null ? ParentBody.Position : item.Position;
|
||||
if (ParentBody != null)
|
||||
{
|
||||
light.Position = ParentBody.Position;
|
||||
}
|
||||
else if (turret != null)
|
||||
{
|
||||
light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
light.Position = item.Position;
|
||||
}
|
||||
#endif
|
||||
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
if (body != null)
|
||||
{
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
break;
|
||||
case "signal_store":
|
||||
case "lock_state":
|
||||
writeable = signal == "1";
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -15,17 +15,24 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float updateTimer;
|
||||
|
||||
public enum TargetType
|
||||
{
|
||||
Any,
|
||||
Human,
|
||||
Monster
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
|
||||
public bool MotionDetected { get; set; }
|
||||
|
||||
[Editable, Serialize(false, true, description: "Should the sensor only detect the movement of humans?", alwaysUseInstanceValues: true)]
|
||||
public bool OnlyHumans
|
||||
[InGameEditable, Serialize(TargetType.Any, true, description: "Which kind of targets can trigger the sensor?", alwaysUseInstanceValues: true)]
|
||||
public TargetType Target
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true, description: "Should the sensor ignore the bodies of dead characters?", alwaysUseInstanceValues: true)]
|
||||
[InGameEditable, Serialize(false, true, description: "Should the sensor ignore the bodies of dead characters?", alwaysUseInstanceValues: true)]
|
||||
public bool IgnoreDead
|
||||
{
|
||||
get;
|
||||
@@ -55,7 +62,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize("0,0", true, description: "The position to detect the movement at relative to the item. For example, 0,100 would detect movement 100 units above the item.")]
|
||||
[InGameEditable, Serialize("0,0", true, description: "The position to detect the movement at relative to the item. For example, 0,100 would detect movement 100 units above the item.")]
|
||||
public Vector2 DetectOffset
|
||||
{
|
||||
get { return detectOffset; }
|
||||
@@ -80,7 +87,6 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public MotionSensor(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -93,6 +99,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
//backwards compatibility
|
||||
if (componentElement.GetAttributeBool("onlyhumans", false))
|
||||
{
|
||||
Target = TargetType.Human;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
string signalOut = MotionDetected ? Output : FalseOutput;
|
||||
@@ -121,7 +137,16 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (IgnoreDead && c.IsDead) { continue; }
|
||||
if (OnlyHumans && !c.IsHuman) { continue; }
|
||||
|
||||
switch (Target)
|
||||
{
|
||||
case TargetType.Human:
|
||||
if (!c.IsHuman) { continue; }
|
||||
break;
|
||||
case TargetType.Monster:
|
||||
if (c.IsHuman || c.IsPet) { continue; }
|
||||
break;
|
||||
}
|
||||
|
||||
//do a rough check based on the position of the character's collider first
|
||||
//before the more accurate limb-based check
|
||||
|
||||
@@ -4,16 +4,36 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class NotComponent : ItemComponent
|
||||
{
|
||||
private bool signalReceived;
|
||||
|
||||
private bool continuousOutput;
|
||||
[Editable, Serialize(false, true, description: "When enabled, the component continuously outputs \"1\" when it's not receiving a signal.", alwaysUseInstanceValues: true)]
|
||||
public bool ContinuousOutput
|
||||
{
|
||||
get { return continuousOutput; }
|
||||
set { continuousOutput = IsActive = value; }
|
||||
}
|
||||
|
||||
public NotComponent(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
if (!signalReceived)
|
||||
{
|
||||
item.SendSignal(0, "1", "signal_out", null, 0.0f);
|
||||
}
|
||||
signalReceived = false;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
if (connection.Name != "signal_in") return;
|
||||
|
||||
item.SendSignal(stepsTaken, signal == "0" ? "1" : "0", "signal_out", sender, 0.0f, source, signalStrength);
|
||||
if (connection.Name != "signal_in") { return; }
|
||||
item.SendSignal(stepsTaken, signal == "0" || signal == string.Empty ? "1" : "0", "signal_out", sender, 0.0f, source, signalStrength);
|
||||
signalReceived = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components
|
||||
[InGameEditable, Serialize(false, true, description: "Should the component output a value of a capture group instead of a constant signal.", alwaysUseInstanceValues: true)]
|
||||
public bool UseCaptureGroup { get; set; }
|
||||
|
||||
[Serialize("0", true, description: "The signal this item outputs when the received signal does not match the regular expression.", alwaysUseInstanceValues: true)]
|
||||
[InGameEditable, Serialize("0", true, description: "The signal this item outputs when the received signal does not match the regular expression.", alwaysUseInstanceValues: true)]
|
||||
public string FalseOutput { get; set; }
|
||||
|
||||
[InGameEditable, Serialize(true, true, description: "Should the component keep sending the output even after it stops receiving a signal, or only send an output when it receives a signal.", alwaysUseInstanceValues: true)]
|
||||
|
||||
@@ -180,6 +180,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (connection.Name == "toggle")
|
||||
{
|
||||
if (signal == "0") { return; }
|
||||
SetState(!IsOn, false);
|
||||
}
|
||||
else if (connection.Name == "set_state")
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private int[] channelMemory = new int[ChannelMemorySize];
|
||||
|
||||
[Serialize(Character.TeamType.None, true, description: "WiFi components can only communicate with components that have the same Team ID.", alwaysUseInstanceValues: true)]
|
||||
public Character.TeamType TeamID { get; set; }
|
||||
[Serialize(CharacterTeamType.None, true, description: "WiFi components can only communicate with components that have the same Team ID.", alwaysUseInstanceValues: true)]
|
||||
public CharacterTeamType TeamID { get; set; }
|
||||
|
||||
[Editable, Serialize(20000.0f, false, description: "How close the recipient has to be to receive a signal from this WiFi component.", alwaysUseInstanceValues: true)]
|
||||
public float Range
|
||||
|
||||
@@ -262,24 +262,33 @@ namespace Barotrauma.Items.Components
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
base.OnMapLoaded();
|
||||
var lightComponents = item.GetComponents<LightComponent>();
|
||||
if (lightComponents != null && lightComponents.Count() > 0)
|
||||
{
|
||||
lightComponent = lightComponents.FirstOrDefault(lc => lc.Parent == this);
|
||||
#if CLIENT
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.Parent = null;
|
||||
lightComponent.Rotation = Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
lightComponent.Light.Rotation = -rotation;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
FindLightComponent();
|
||||
if (loadedRotationLimits.HasValue) { RotationLimits = loadedRotationLimits.Value; }
|
||||
if (loadedBaseRotation.HasValue) { BaseRotation = loadedBaseRotation.Value; }
|
||||
UpdateTransformedBarrelPos();
|
||||
}
|
||||
|
||||
private void FindLightComponent()
|
||||
{
|
||||
foreach (LightComponent lc in item.GetComponents<LightComponent>())
|
||||
{
|
||||
if (lc?.Parent == this)
|
||||
{
|
||||
lightComponent = lc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.Parent = null;
|
||||
lightComponent.Rotation = Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
lightComponent.Light.Rotation = -rotation;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
this.cam = cam;
|
||||
@@ -304,7 +313,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
UpdateProjSpecific(deltaTime);
|
||||
|
||||
if (minRotation == maxRotation) { return; }
|
||||
if (MathUtils.NearlyEqual(minRotation, maxRotation))
|
||||
{
|
||||
UpdateLightComponent();
|
||||
return;
|
||||
}
|
||||
|
||||
float targetMidDiff = MathHelper.WrapAngle(targetRotation - (minRotation + maxRotation) / 2.0f);
|
||||
|
||||
@@ -326,7 +339,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
user.Info.IncreaseSkillLevel("weapons",
|
||||
SkillSettings.Current.SkillIncreasePerSecondWhenOperatingTurret * deltaTime / Math.Max(user.GetSkillLevel("weapons"), 1.0f),
|
||||
user.WorldPosition + Vector2.UnitY * 150.0f);
|
||||
user.Position + Vector2.UnitY * 150.0f);
|
||||
}
|
||||
|
||||
float rotMidDiff = MathHelper.WrapAngle(rotation - (minRotation + maxRotation) / 2.0f);
|
||||
@@ -371,6 +384,11 @@ namespace Barotrauma.Items.Components
|
||||
angularVelocity *= -0.5f;
|
||||
}
|
||||
|
||||
UpdateLightComponent();
|
||||
}
|
||||
|
||||
private void UpdateLightComponent()
|
||||
{
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.Rotation = Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
@@ -709,7 +727,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true,
|
||||
customPredicate: (Fixture f) => { return !item.StaticFixtures.Contains(f); });
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
if (f.UserData is Item i && i.GetComponent<Turret>() != null) { return false; }
|
||||
return !item.StaticFixtures.Contains(f);
|
||||
});
|
||||
if (pickedBody == null) { return; }
|
||||
Character targetCharacter = null;
|
||||
if (pickedBody.UserData is Character c)
|
||||
@@ -753,7 +775,7 @@ namespace Barotrauma.Items.Components
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget &&
|
||||
previousTarget.IsDead)
|
||||
{
|
||||
character?.Speak(TextManager.Get("DialogTurretTargetDead"), null, 0.0f, "killedtarget" + previousTarget.ID, 30.0f);
|
||||
character.Speak(TextManager.Get("DialogTurretTargetDead"), null, 0.0f, "killedtarget" + previousTarget.ID, 10.0f);
|
||||
character.AIController.SelectTarget(null);
|
||||
}
|
||||
|
||||
@@ -765,7 +787,7 @@ namespace Barotrauma.Items.Components
|
||||
PowerContainer batteryToLoad = null;
|
||||
foreach (PowerContainer battery in batteries)
|
||||
{
|
||||
if (battery.Item.NonInteractable) { continue; }
|
||||
if (!battery.Item.IsInteractable(character)) { continue; }
|
||||
if (batteryToLoad == null || battery.Charge < lowestCharge)
|
||||
{
|
||||
batteryToLoad = battery;
|
||||
@@ -786,18 +808,14 @@ namespace Barotrauma.Items.Components
|
||||
int maxProjectileCount = 0;
|
||||
foreach (MapEntity e in item.linkedTo)
|
||||
{
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (!item.IsInteractable(character)) { continue; }
|
||||
if (e is Item projectileContainer)
|
||||
{
|
||||
var containedItems = projectileContainer.ContainedItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
var container = projectileContainer.GetComponent<ItemContainer>();
|
||||
maxProjectileCount += container.Capacity;
|
||||
var container = projectileContainer.GetComponent<ItemContainer>();
|
||||
maxProjectileCount += container.Capacity;
|
||||
|
||||
int projectiles = containedItems.Count(it => it.Condition > 0.0f);
|
||||
usableProjectileCount += projectiles;
|
||||
}
|
||||
int projectiles = projectileContainer.ContainedItems.Count(it => it.Condition > 0.0f);
|
||||
usableProjectileCount += projectiles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,7 +827,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
containerItem = e as Item;
|
||||
if (containerItem == null) { continue; }
|
||||
if (containerItem.NonInteractable) { continue; }
|
||||
if (!containerItem.IsInteractable(character)) { continue; }
|
||||
if (character.AIController is HumanAIController aiController && aiController.IgnoredItems.Contains(containerItem)) { continue; }
|
||||
container = containerItem.GetComponent<ItemContainer>();
|
||||
if (container != null) { break; }
|
||||
@@ -852,53 +870,135 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//enough shells and power
|
||||
Character closestEnemy = null;
|
||||
float closestDist = AIRange * AIRange;
|
||||
Vector2? targetPos = null;
|
||||
float shootDistance = AIRange * item.OffsetOnSelectedMultiplier;
|
||||
float closestDistance = shootDistance * shootDistance;
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
// Ignore dead, friendly, and those that are inside the same sub
|
||||
if (enemy.IsDead || !enemy.Enabled || enemy.Submarine == character.Submarine) { continue; }
|
||||
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
|
||||
|
||||
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
|
||||
float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
|
||||
float angle = -MathUtils.VectorToAngle(enemy.WorldPosition - item.WorldPosition);
|
||||
float midRotation = (minRotation + maxRotation) / 2.0f;
|
||||
while (midRotation - angle < -MathHelper.Pi) { angle -= MathHelper.TwoPi; }
|
||||
while (midRotation - angle > MathHelper.Pi) { angle += MathHelper.TwoPi; }
|
||||
|
||||
if (angle < minRotation || angle > maxRotation) { continue; }
|
||||
|
||||
if (dist > closestDistance) { continue; }
|
||||
if (!CheckTurretAngle(enemy.WorldPosition)) { continue; }
|
||||
closestEnemy = enemy;
|
||||
closestDist = dist;
|
||||
closestDistance = dist;
|
||||
}
|
||||
|
||||
if (closestEnemy == null) { return false; }
|
||||
|
||||
character.AIController.SelectTarget(closestEnemy.AiTarget);
|
||||
if (closestEnemy != null)
|
||||
{
|
||||
targetPos = closestEnemy.WorldPosition;
|
||||
}
|
||||
else if (item.Submarine != null && Level.Loaded != null)
|
||||
{
|
||||
// Check ice spires
|
||||
closestDistance = shootDistance;
|
||||
foreach (var wall in Level.Loaded.ExtraWalls)
|
||||
{
|
||||
if (!(wall is DestructibleLevelWall destructibleWall) || destructibleWall.Destroyed) { continue; }
|
||||
foreach (var cell in wall.Cells)
|
||||
{
|
||||
if (cell.DoesDamage)
|
||||
{
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
Vector2 p1 = edge.Point1 + cell.Translation;
|
||||
Vector2 p2 = edge.Point2 + cell.Translation;
|
||||
Vector2 closestPoint = MathUtils.GetClosestPointOnLineSegment(p1, p2, item.WorldPosition);
|
||||
if (!CheckTurretAngle(closestPoint))
|
||||
{
|
||||
// The closest point can't be targeted -> get a point directly in front of the turret
|
||||
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
|
||||
if (MathUtils.GetLineIntersection(p1, p2, item.WorldPosition, item.WorldPosition + barrelDir * shootDistance, out Vector2 intersection))
|
||||
{
|
||||
closestPoint = intersection;
|
||||
if (!CheckTurretAngle(closestPoint)) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
float dist = Vector2.Distance(closestPoint, item.WorldPosition);
|
||||
if (dist > AIRange + 1000) { continue; }
|
||||
float dot = 0;
|
||||
if (item.Submarine.Velocity != Vector2.Zero)
|
||||
{
|
||||
dot = Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), Vector2.Normalize(closestPoint - item.Submarine.WorldPosition));
|
||||
}
|
||||
float minAngle = 0.5f;
|
||||
if (dot < minAngle && dist > 1000)
|
||||
{
|
||||
// The sub is not moving towards the target and it's not very close to the turret either -> ignore
|
||||
continue;
|
||||
}
|
||||
// Allow targeting farther when heading towards the spire (up to 1000 px)
|
||||
dist -= MathHelper.Lerp(0, 1000, MathUtils.InverseLerp(minAngle, 1, dot)); ;
|
||||
if (dist > closestDistance) { continue; }
|
||||
targetPos = closestPoint;
|
||||
closestDistance = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
character.CursorPosition = closestEnemy.WorldPosition;
|
||||
if (targetPos == null) { return false; }
|
||||
|
||||
if (closestEnemy != null && character.AIController.SelectedAiTarget != closestEnemy.AiTarget)
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget == null)
|
||||
{
|
||||
if (GameMain.Config.RecentlyEncounteredCreatures.Contains(closestEnemy.SpeciesName))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNewTargetSpotted"), null, 0.0f, "newtargetspotted", 30.0f);
|
||||
}
|
||||
else if (GameMain.Config.EncounteredCreatures.Any(name => name.Equals(closestEnemy.SpeciesName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogIdentifiedTargetSpotted", "[speciesname]", closestEnemy.DisplayName), null, 0.0f, "identifiedtargetspotted", 30.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted"), null, 0.0f, "unidentifiedtargetspotted", 5.0f);
|
||||
}
|
||||
}
|
||||
else if (GameMain.Config.EncounteredCreatures.None(name => name.Equals(closestEnemy.SpeciesName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted"), null, 0.0f, "unidentifiedtargetspotted", 5.0f);
|
||||
}
|
||||
character.AddEncounter(closestEnemy);
|
||||
character.AIController.SelectTarget(closestEnemy.AiTarget);
|
||||
}
|
||||
else if (closestEnemy == null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogIceSpireSpotted"), null, 0.0f, "icespirespotted", 60.0f);
|
||||
}
|
||||
|
||||
character.CursorPosition = targetPos.Value;
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
character.CursorPosition -= character.Submarine.Position;
|
||||
}
|
||||
|
||||
float enemyAngle = MathUtils.VectorToAngle(closestEnemy.WorldPosition - item.WorldPosition);
|
||||
float enemyAngle = MathUtils.VectorToAngle(targetPos.Value - item.WorldPosition);
|
||||
float turretAngle = -rotation;
|
||||
|
||||
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) { return false; }
|
||||
|
||||
|
||||
Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition);
|
||||
Vector2 end = ConvertUnits.ToSimUnits(closestEnemy.WorldPosition);
|
||||
if (closestEnemy.Submarine != null)
|
||||
Vector2 end = ConvertUnits.ToSimUnits(targetPos.Value);
|
||||
if (closestEnemy != null && closestEnemy.Submarine != null)
|
||||
{
|
||||
start -= closestEnemy.Submarine.SimPosition;
|
||||
end -= closestEnemy.Submarine.SimPosition;
|
||||
}
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true,
|
||||
customPredicate: (Fixture f) => { return !item.StaticFixtures.Contains(f); });
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
if (f.UserData is Item i && i.GetComponent<Turret>() != null) { return false; }
|
||||
return !item.StaticFixtures.Contains(f);
|
||||
});
|
||||
if (pickedBody == null) { return false; }
|
||||
Character targetCharacter = null;
|
||||
if (pickedBody.UserData is Character c)
|
||||
@@ -929,17 +1029,26 @@ namespace Barotrauma.Items.Components
|
||||
// Don't shoot friendly submarines.
|
||||
if (sub.TeamID == Item.Submarine.TeamID) { return false; }
|
||||
}
|
||||
else
|
||||
else if (!(pickedBody.UserData is Voronoi2.VoronoiCell cell && cell.IsDestructible))
|
||||
{
|
||||
// Hit something else, probably a level wall
|
||||
return false;
|
||||
}
|
||||
}
|
||||
character?.Speak(TextManager.GetWithVariable("DialogFireTurret", "[itemname]", item.Name, true), null, 0.0f, "fireturret", 5.0f);
|
||||
character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f);
|
||||
character.SetInput(InputType.Shoot, true, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckTurretAngle(Vector2 target)
|
||||
{
|
||||
float angle = -MathUtils.VectorToAngle(target - item.WorldPosition);
|
||||
float midRotation = (minRotation + maxRotation) / 2.0f;
|
||||
while (midRotation - angle < -MathHelper.Pi) { angle -= MathHelper.TwoPi; }
|
||||
while (midRotation - angle > MathHelper.Pi) { angle += MathHelper.TwoPi; }
|
||||
return angle > minRotation && angle < maxRotation;
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
@@ -984,7 +1093,6 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
//check if the contained item is another itemcontainer with projectiles inside it
|
||||
if (containedItem.ContainedItems == null) { continue; }
|
||||
foreach (Item subContainedItem in containedItem.ContainedItems)
|
||||
{
|
||||
projectileComponent = subContainedItem.GetComponent<Projectile>();
|
||||
@@ -1094,6 +1202,7 @@ namespace Barotrauma.Items.Components
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
FindLightComponent();
|
||||
if (!loadedBaseRotation.HasValue)
|
||||
{
|
||||
if (item.FlippedX) { FlipX(relativeToSub: false); }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
@@ -9,17 +10,209 @@ namespace Barotrauma
|
||||
{
|
||||
partial class Inventory : IServerSerializable, IClientSerializable
|
||||
{
|
||||
public const int MaxStackSize = 32;
|
||||
|
||||
public class ItemSlot
|
||||
{
|
||||
private readonly List<Item> items = new List<Item>(MaxStackSize);
|
||||
|
||||
public bool HideIfEmpty;
|
||||
|
||||
public IEnumerable<Item> Items
|
||||
{
|
||||
get { return items; }
|
||||
}
|
||||
|
||||
public int ItemCount
|
||||
{
|
||||
get { return items.Count; }
|
||||
}
|
||||
|
||||
public bool CanBePut(Item item)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (items.Count > 0)
|
||||
{
|
||||
if (item.IsFullCondition)
|
||||
{
|
||||
if (items.Any(it => !it.IsFullCondition)) { return false; }
|
||||
}
|
||||
else if (MathUtils.NearlyEqual(item.Condition, 0.0f))
|
||||
{
|
||||
if (items.Any(it => !MathUtils.NearlyEqual(it.Condition, 0.0f))) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (items[0].Prefab.Identifier != item.Prefab.Identifier ||
|
||||
items.Count + 1 > item.Prefab.MaxStackSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanBePut(ItemPrefab itemPrefab)
|
||||
{
|
||||
if (itemPrefab == null) { return false; }
|
||||
if (items.Count > 0)
|
||||
{
|
||||
if (items.Any(it => !it.IsFullCondition)) { return false; }
|
||||
if (items[0].Prefab.Identifier != itemPrefab.Identifier ||
|
||||
items.Count + 1 > itemPrefab.MaxStackSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <param name="maxStackSize">Defaults to <see cref="ItemPrefab.MaxStackSize"/> if null</param>
|
||||
public int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize = null)
|
||||
{
|
||||
if (itemPrefab == null) { return 0; }
|
||||
maxStackSize ??= itemPrefab.MaxStackSize;
|
||||
if (items.Count > 0)
|
||||
{
|
||||
if (items.Any(it => !it.IsFullCondition)) { return 0; }
|
||||
if (items[0].Prefab.Identifier != itemPrefab.Identifier) { return 0; }
|
||||
return maxStackSize.Value - items.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
return maxStackSize.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(Item item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new InvalidOperationException("Tried to add a null item to an inventory slot.");
|
||||
}
|
||||
if (items.Count > 0)
|
||||
{
|
||||
if (items[0].Prefab.Identifier != item.Prefab.Identifier)
|
||||
{
|
||||
throw new InvalidOperationException("Tried to stack different types of items.");
|
||||
}
|
||||
else if (items.Count + 1 > item.Prefab.MaxStackSize)
|
||||
{
|
||||
throw new InvalidOperationException("Tried to add an item to a full inventory slot (stack already full).");
|
||||
}
|
||||
}
|
||||
if (items.Contains(item)) { return; }
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes one item from the slot
|
||||
/// </summary>
|
||||
public Item RemoveItem()
|
||||
{
|
||||
if (items.Count == 0) { return null; }
|
||||
|
||||
var item = items[0];
|
||||
items.RemoveAt(0);
|
||||
return item;
|
||||
}
|
||||
|
||||
public void RemoveItem(Item item)
|
||||
{
|
||||
items.Remove(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the slot
|
||||
/// </summary>
|
||||
public void RemoveAllItems()
|
||||
{
|
||||
items.Clear();
|
||||
}
|
||||
|
||||
public bool Any()
|
||||
{
|
||||
return items.Count > 0;
|
||||
}
|
||||
|
||||
public bool Empty()
|
||||
{
|
||||
return items.Count == 0;
|
||||
}
|
||||
|
||||
public Item First()
|
||||
{
|
||||
return items[0];
|
||||
}
|
||||
|
||||
public Item FirstOrDefault()
|
||||
{
|
||||
return items.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Item LastOrDefault()
|
||||
{
|
||||
return items.LastOrDefault();
|
||||
}
|
||||
|
||||
public bool Contains(Item item)
|
||||
{
|
||||
return items.Contains(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public readonly Entity Owner;
|
||||
|
||||
protected readonly int capacity;
|
||||
|
||||
public Item[] Items;
|
||||
protected bool[] hideEmptySlot;
|
||||
protected readonly ItemSlot[] slots;
|
||||
|
||||
public bool Locked;
|
||||
|
||||
protected float syncItemsDelay;
|
||||
|
||||
/// <summary>
|
||||
/// All items contained in the inventory. Stacked items are returned as individual instances. DO NOT modify the contents of the inventory while enumerating this list.
|
||||
/// </summary>
|
||||
public IEnumerable<Item> AllItems
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
foreach (var item in slots[i].Items)
|
||||
{
|
||||
bool duplicateFound = false;
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
if (slots[j].Items.Contains(item))
|
||||
{
|
||||
duplicateFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!duplicateFound) { yield return item; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Item> allItemsList = new List<Item>();
|
||||
/// <summary>
|
||||
/// All items contained in the inventory. Allows modifying the contents of the inventory while being enumerated.
|
||||
/// </summary>
|
||||
public IEnumerable<Item> AllItemsMod
|
||||
{
|
||||
get
|
||||
{
|
||||
allItemsList.Clear();
|
||||
allItemsList.AddRange(AllItems);
|
||||
return allItemsList;
|
||||
}
|
||||
}
|
||||
|
||||
public int Capacity
|
||||
{
|
||||
get { return capacity; }
|
||||
@@ -31,8 +224,11 @@ namespace Barotrauma
|
||||
|
||||
this.Owner = owner;
|
||||
|
||||
Items = new Item[capacity];
|
||||
hideEmptySlot = new bool[capacity];
|
||||
slots = new ItemSlot[capacity];
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
slots[i] = new ItemSlot();
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
this.slotsPerRow = slotsPerRow;
|
||||
@@ -54,71 +250,117 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Item FindItemRecursive(Item item, Predicate<Item> condition)
|
||||
/// <summary>
|
||||
/// Is the item contained in this inventory. Does not recursively check items inside items.
|
||||
/// </summary>
|
||||
public bool Contains(Item item)
|
||||
{
|
||||
if (condition.Invoke(item))
|
||||
return slots.Any(i => i.Contains(item));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the first item in the inventory, or null if the inventory is empty.
|
||||
/// </summary>
|
||||
public Item FirstOrDefault()
|
||||
{
|
||||
foreach (var itemSlot in slots)
|
||||
{
|
||||
return item;
|
||||
var item = itemSlot.FirstOrDefault();
|
||||
if (item != null) { return item; }
|
||||
}
|
||||
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
|
||||
if (containers != null)
|
||||
{
|
||||
foreach (var container in containers)
|
||||
{
|
||||
foreach (var inventoryItem in container.Inventory.Items)
|
||||
{
|
||||
var findItem = FindItemRecursive(inventoryItem, condition);
|
||||
if (findItem != null)
|
||||
{
|
||||
return findItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the last item in the inventory, or null if the inventory is empty.
|
||||
/// </summary>
|
||||
public Item LastOrDefault()
|
||||
{
|
||||
for (int i = slots.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var item = slots[i].LastOrDefault();
|
||||
if (item != null) { return item; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the item stored in the specified inventory slot. If the slot contains a stack of items, returns the first item in the stack.
|
||||
/// </summary>
|
||||
public Item GetItemAt(int index)
|
||||
{
|
||||
if (index < 0 || index >= slots.Length) { return null; }
|
||||
return slots[index].FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all the item stored in the specified inventory slot. Can return more than one item if the slot contains a stack of items.
|
||||
/// </summary>
|
||||
public IEnumerable<Item> GetItemsAt(int index)
|
||||
{
|
||||
if (index < 0 || index >= slots.Length) { return Enumerable.Empty<Item>(); }
|
||||
return slots[index].Items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the index of the first slot the item is contained in.
|
||||
/// </summary>
|
||||
public int FindIndex(Item item)
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (Items[i] == item) return i;
|
||||
if (slots[i].Contains(item)) { return i; }
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// Returns true if the item owns any of the parent inventories
|
||||
|
||||
/// <summary>
|
||||
/// Find the indices of all the slots the item is contained in (two-hand items for example can be in multiple slots). Note that this method instantiates a new list.
|
||||
/// </summary>
|
||||
public List<int> FindIndices(Item item)
|
||||
{
|
||||
List<int> indices = new List<int>();
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (slots[i].Contains(item)) { indices.Add(i); }
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the item owns any of the parent inventories.
|
||||
/// </summary>
|
||||
public virtual bool ItemOwnsSelf(Item item)
|
||||
{
|
||||
if (Owner == null) return false;
|
||||
if (!(Owner is Item)) return false;
|
||||
if (Owner == null) { return false; }
|
||||
if (!(Owner is Item)) { return false; }
|
||||
Item ownerItem = Owner as Item;
|
||||
if (ownerItem == item) return true;
|
||||
if (ownerItem.ParentInventory == null) return false;
|
||||
if (ownerItem == item) { return true; }
|
||||
if (ownerItem.ParentInventory == null) { return false; }
|
||||
return ownerItem.ParentInventory.ItemOwnsSelf(item);
|
||||
}
|
||||
|
||||
public virtual int FindAllowedSlot(Item item)
|
||||
{
|
||||
if (ItemOwnsSelf(item)) return -1;
|
||||
if (ItemOwnsSelf(item)) { return -1; }
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
//item is already in the inventory!
|
||||
if (Items[i] == item) return -1;
|
||||
if (slots[i].Contains(item)) { return -1; }
|
||||
}
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (Items[i] == null) return i;
|
||||
if (slots[i].CanBePut(item)) { return i; }
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the item be put in the inventory (i.e. is there a suitable free slot or a stack the item can be put in).
|
||||
/// </summary>
|
||||
public bool CanBePut(Item item)
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
@@ -128,20 +370,54 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the item be put in the specified slot.
|
||||
/// </summary>
|
||||
public virtual bool CanBePut(Item item, int i)
|
||||
{
|
||||
if (ItemOwnsSelf(item)) return false;
|
||||
if (i < 0 || i >= Items.Length) return false;
|
||||
return (Items[i] == null);
|
||||
if (ItemOwnsSelf(item)) { return false; }
|
||||
if (i < 0 || i >= slots.Length) { return false; }
|
||||
return slots[i].CanBePut(item);
|
||||
}
|
||||
|
||||
|
||||
public bool CanBePut(ItemPrefab itemPrefab)
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (CanBePut(itemPrefab, i)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool CanBePut(ItemPrefab itemPrefab, int i)
|
||||
{
|
||||
if (i < 0 || i >= slots.Length) { return false; }
|
||||
return slots[i].CanBePut(itemPrefab);
|
||||
}
|
||||
|
||||
public int HowManyCanBePut(ItemPrefab itemPrefab)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
count += HowManyCanBePut(itemPrefab, i);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i)
|
||||
{
|
||||
if (i < 0 || i >= slots.Length) { return 0; }
|
||||
return slots[i].HowManyCanBePut(itemPrefab);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If there is room, puts the item in the inventory and returns true, otherwise returns false
|
||||
/// </summary>
|
||||
public virtual bool TryPutItem(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
|
||||
public virtual bool TryPutItem(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
|
||||
{
|
||||
int slot = FindAllowedSlot(item);
|
||||
if (slot < 0) return false;
|
||||
if (slot < 0) { return false; }
|
||||
|
||||
PutItem(item, slot, user, true, createNetworkEvent);
|
||||
return true;
|
||||
@@ -149,7 +425,7 @@ namespace Barotrauma
|
||||
|
||||
public virtual bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true)
|
||||
{
|
||||
if (i < 0 || i >= Items.Length)
|
||||
if (i < 0 || i >= slots.Length)
|
||||
{
|
||||
string errorMsg = "Inventory.TryPutItem failed: index was out of range(" + i + ").\n" + Environment.StackTrace.CleanupStackTrace();
|
||||
GameAnalyticsManager.AddErrorEventOnce("Inventory.TryPutItem:IndexOutOfRange", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
@@ -158,31 +434,41 @@ namespace Barotrauma
|
||||
|
||||
if (Owner == null) return false;
|
||||
//there's already an item in the slot
|
||||
if (Items[i] != null && allowCombine)
|
||||
if (slots[i].Any() && allowCombine)
|
||||
{
|
||||
if (Items[i].Combine(item, user))
|
||||
if (slots[i].First().Combine(item, user))
|
||||
{
|
||||
//item in the slot removed as a result of combining -> put this item in the now free slot
|
||||
if (Items[i] == null)
|
||||
if (!slots[i].Any())
|
||||
{
|
||||
return TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (Items[i] != null && item.ParentInventory != null && allowSwapping)
|
||||
{
|
||||
return TrySwapping(i, item, user, createNetworkEvent);
|
||||
}
|
||||
else if (CanBePut(item, i))
|
||||
if (CanBePut(item, i))
|
||||
{
|
||||
PutItem(item, i, user, true, createNetworkEvent);
|
||||
return true;
|
||||
}
|
||||
else if (slots[i].Any() && item.ParentInventory != null && allowSwapping)
|
||||
{
|
||||
var itemInSlot = slots[i].First();
|
||||
if (itemInSlot.OwnInventory != null &&
|
||||
!itemInSlot.OwnInventory.Contains(item) &&
|
||||
(itemInSlot.GetComponent<ItemContainer>()?.MaxStackSize ?? 0) == 1 &&
|
||||
itemInSlot.OwnInventory.TrySwapping(0, item, user, createNetworkEvent, swapWholeStack: false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return
|
||||
TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: true) ||
|
||||
TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
if (slots != null && createNetworkEvent) slots[i].ShowBorderHighlight(GUI.Style.Red, 0.1f, 0.9f);
|
||||
if (visualSlots != null && createNetworkEvent) { visualSlots[i].ShowBorderHighlight(GUI.Style.Red, 0.1f, 0.9f); }
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -190,14 +476,14 @@ namespace Barotrauma
|
||||
|
||||
protected virtual void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true)
|
||||
{
|
||||
if (i < 0 || i >= Items.Length)
|
||||
if (i < 0 || i >= slots.Length)
|
||||
{
|
||||
string errorMsg = "Inventory.PutItem failed: index was out of range(" + i + ").\n" + Environment.StackTrace.CleanupStackTrace();
|
||||
GameAnalyticsManager.AddErrorEventOnce("Inventory.PutItem:IndexOutOfRange", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Owner == null) return;
|
||||
if (Owner == null) { return; }
|
||||
|
||||
Inventory prevInventory = item.ParentInventory;
|
||||
Inventory prevOwnerInventory = item.FindParentInventory(inv => inv is CharacterInventory);
|
||||
@@ -206,20 +492,23 @@ namespace Barotrauma
|
||||
{
|
||||
CreateNetworkEvent();
|
||||
//also delay syncing the inventory the item was inside
|
||||
if (prevInventory != null && prevInventory != this) prevInventory.syncItemsDelay = 1.0f;
|
||||
if (prevInventory != null && prevInventory != this) { prevInventory.syncItemsDelay = 1.0f; }
|
||||
}
|
||||
|
||||
if (removeItem)
|
||||
{
|
||||
item.Drop(user);
|
||||
if (item.ParentInventory != null) item.ParentInventory.RemoveItem(item);
|
||||
if (item.ParentInventory != null) { item.ParentInventory.RemoveItem(item); }
|
||||
}
|
||||
|
||||
Items[i] = item;
|
||||
slots[i].Add(item);
|
||||
item.ParentInventory = this;
|
||||
|
||||
#if CLIENT
|
||||
if (slots != null) slots[i].ShowBorderHighlight(Color.White, 0.1f, 0.4f);
|
||||
if (visualSlots != null)
|
||||
{
|
||||
visualSlots[i]?.ShowBorderHighlight(Color.White, 0.1f, 0.4f);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (item.body != null)
|
||||
@@ -258,33 +547,49 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (Items[i] != null) return false;
|
||||
if (slots[i].Any()) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsFull()
|
||||
/// <summary>
|
||||
/// Is there room to put more items in the inventory. Doesn't take stacking into account by default.
|
||||
/// </summary>
|
||||
/// <param name="takeStacksIntoAccount">If true, the inventory is not considered full if all the stacks are not full.</param>
|
||||
public virtual bool IsFull(bool takeStacksIntoAccount = false)
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
if (takeStacksIntoAccount)
|
||||
{
|
||||
if (Items[i] == null) return false;
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (!slots[i].Any()) { return false; }
|
||||
var item = slots[i].FirstOrDefault();
|
||||
if (slots[i].ItemCount < item.Prefab.MaxStackSize) { return false; }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (!slots[i].Any()) { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool TrySwapping(int index, Item item, Character user, bool createNetworkEvent)
|
||||
protected bool TrySwapping(int index, Item item, Character user, bool createNetworkEvent, bool swapWholeStack)
|
||||
{
|
||||
if (item?.ParentInventory == null || Items[index] == null) return false;
|
||||
if (item?.ParentInventory == null || !slots[index].Any()) { return false; }
|
||||
|
||||
//swap to InvSlotType.Any if possible
|
||||
Inventory otherInventory = item.ParentInventory;
|
||||
bool otherIsEquipped = false;
|
||||
int otherIndex = -1;
|
||||
for (int i = 0; i < otherInventory.Items.Length; i++)
|
||||
for (int i = 0; i < otherInventory.slots.Length; i++)
|
||||
{
|
||||
if (otherInventory.Items[i] != item) continue;
|
||||
if (!otherInventory.slots[i].Contains(item)) { continue; }
|
||||
if (otherInventory is CharacterInventory characterInventory)
|
||||
{
|
||||
if (characterInventory.SlotTypes[i] == InvSlotType.Any)
|
||||
@@ -298,89 +603,138 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (otherIndex == -1) otherIndex = Array.IndexOf(otherInventory.Items, item);
|
||||
Item existingItem = Items[index];
|
||||
|
||||
for (int j = 0; j < otherInventory.capacity; j++)
|
||||
if (otherIndex == -1)
|
||||
{
|
||||
if (otherInventory.Items[j] == item) { otherInventory.Items[j] = null; }
|
||||
}
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (Items[j] == existingItem) { Items[j] = null; }
|
||||
otherIndex = otherInventory.FindIndex(item);
|
||||
if (otherIndex == -1)
|
||||
{
|
||||
DebugConsole.ThrowError("Something went wrong when trying to swap items between inventory slots: couldn't find the source item from it's inventory.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
List<Item> existingItems = new List<Item>();
|
||||
if (swapWholeStack)
|
||||
{
|
||||
existingItems.AddRange(slots[index].Items);
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (existingItems.Any(existingItem => slots[j].Contains(existingItem))) { slots[j].RemoveAllItems(); }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
existingItems.Add(slots[index].FirstOrDefault());
|
||||
slots[index].RemoveItem(existingItems.First());
|
||||
}
|
||||
|
||||
List<Item> stackedItems = new List<Item>();
|
||||
if (swapWholeStack)
|
||||
{
|
||||
for (int j = 0; j < otherInventory.capacity; j++)
|
||||
{
|
||||
if (otherInventory.slots[j].Contains(item))
|
||||
{
|
||||
stackedItems.AddRange(otherInventory.slots[j].Items);
|
||||
otherInventory.slots[j].RemoveAllItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stackedItems.Add(item);
|
||||
otherInventory.slots[otherIndex].RemoveItem(item);
|
||||
}
|
||||
|
||||
(otherInventory.Owner as Character)?.DeselectItem(item);
|
||||
(otherInventory.Owner as Character)?.DeselectItem(existingItem);
|
||||
|
||||
bool swapSuccessful = false;
|
||||
if (otherIsEquipped)
|
||||
{
|
||||
swapSuccessful =
|
||||
TryPutItem(item, index, false, false, user, createNetworkEvent) &&
|
||||
otherInventory.TryPutItem(existingItem, otherIndex, false, false, user, createNetworkEvent);
|
||||
stackedItems.Distinct().All(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent))
|
||||
&&
|
||||
(existingItems.All(existingItem => otherInventory.TryPutItem(existingItem, otherIndex, false, false, user, createNetworkEvent)) ||
|
||||
existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(), user, CharacterInventory.anySlot, createNetworkEvent));
|
||||
}
|
||||
else
|
||||
{
|
||||
swapSuccessful =
|
||||
otherInventory.TryPutItem(existingItem, otherIndex, false, false, user, createNetworkEvent) &&
|
||||
TryPutItem(item, index, false, false, user, createNetworkEvent);
|
||||
swapSuccessful =
|
||||
(existingItems.All(existingItem => otherInventory.TryPutItem(existingItem, otherIndex, false, false, user, createNetworkEvent)) ||
|
||||
existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(),user, CharacterInventory.anySlot, createNetworkEvent))
|
||||
&&
|
||||
stackedItems.Distinct().All(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent));
|
||||
}
|
||||
|
||||
//if the item in the slot can be moved to the slot of the moved item
|
||||
if (swapSuccessful)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Items[index] == item, "Something when wrong when swapping items, item is not present in the inventory.");
|
||||
System.Diagnostics.Debug.Assert(otherInventory.Items[otherIndex] == existingItem, "Something when wrong when swapping items, item is not present in the other inventory.");
|
||||
System.Diagnostics.Debug.Assert(slots[index].Contains(item), "Something when wrong when swapping items, item is not present in the inventory.");
|
||||
System.Diagnostics.Debug.Assert(otherInventory.Contains(existingItems.FirstOrDefault()), "Something when wrong when swapping items, item is not present in the other inventory.");
|
||||
#if CLIENT
|
||||
if (slots != null)
|
||||
if (visualSlots != null)
|
||||
{
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (Items[j] == item) slots[j].ShowBorderHighlight(GUI.Style.Green, 0.1f, 0.9f);
|
||||
if (slots[j].Contains(item)) { visualSlots[j].ShowBorderHighlight(GUI.Style.Green, 0.1f, 0.9f); }
|
||||
}
|
||||
for (int j = 0; j < otherInventory.capacity; j++)
|
||||
{
|
||||
if (otherInventory.Items[j] == existingItem) otherInventory.slots[j].ShowBorderHighlight(GUI.Style.Green, 0.1f, 0.9f);
|
||||
if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault())) { otherInventory.visualSlots[j].ShowBorderHighlight(GUI.Style.Green, 0.1f, 0.9f); }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
else
|
||||
else //swapping the items failed -> move them back to where they were
|
||||
{
|
||||
for (int j = 0; j < capacity; j++)
|
||||
if (swapWholeStack)
|
||||
{
|
||||
if (Items[j] == item) Items[j] = null;
|
||||
foreach (Item stackedItem in stackedItems)
|
||||
{
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (slots[j].Contains(stackedItem)) { slots[j].RemoveItem(stackedItem); };
|
||||
}
|
||||
}
|
||||
foreach (Item existingItem in existingItems)
|
||||
{
|
||||
for (int j = 0; j < otherInventory.capacity; j++)
|
||||
{
|
||||
if (otherInventory.slots[j].Contains(existingItem)) { otherInventory.slots[j].RemoveItem(existingItem); }
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < otherInventory.capacity; j++)
|
||||
else
|
||||
{
|
||||
if (otherInventory.Items[j] == existingItem) otherInventory.Items[j] = null;
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (slots[j].Contains(item)) { slots[j].RemoveAllItems(); };
|
||||
}
|
||||
for (int j = 0; j < otherInventory.capacity; j++)
|
||||
{
|
||||
if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault())) { otherInventory.slots[j].RemoveAllItems(); }
|
||||
}
|
||||
}
|
||||
|
||||
if (otherIsEquipped)
|
||||
{
|
||||
TryPutItem(existingItem, index, false, false, user, createNetworkEvent);
|
||||
otherInventory.TryPutItem(item, otherIndex, false, false, user, createNetworkEvent);
|
||||
existingItems.ForEach(existingItem => TryPutItem(existingItem, index, false, false, user, createNetworkEvent));
|
||||
stackedItems.ForEach(stackedItem => otherInventory.TryPutItem(stackedItem, otherIndex, false, false, user, createNetworkEvent));
|
||||
}
|
||||
else
|
||||
{
|
||||
otherInventory.TryPutItem(item, otherIndex, false, false, user, createNetworkEvent);
|
||||
TryPutItem(existingItem, index, false, false, user, createNetworkEvent);
|
||||
stackedItems.ForEach(stackedItem => otherInventory.TryPutItem(stackedItem, otherIndex, false, false, user, createNetworkEvent));
|
||||
existingItems.ForEach(existingItem => TryPutItem(existingItem, index, false, false, user, createNetworkEvent));
|
||||
}
|
||||
|
||||
//swapping the items failed -> move them back to where they were
|
||||
//otherInventory.TryPutItem(item, otherIndex, false, false, user, createNetworkEvent);
|
||||
//TryPutItem(existingItem, index, false, false, user, createNetworkEvent);
|
||||
#if CLIENT
|
||||
if (slots != null)
|
||||
if (visualSlots != null)
|
||||
{
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (Items[j] == existingItem)
|
||||
if (slots[j].Contains(existingItems.FirstOrDefault()))
|
||||
{
|
||||
slots[j].ShowBorderHighlight(GUI.Style.Red, 0.1f, 0.9f);
|
||||
visualSlots[j].ShowBorderHighlight(GUI.Style.Red, 0.1f, 0.9f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,10 +754,10 @@ namespace Barotrauma
|
||||
|
||||
public Item FindItem(Func<Item, bool> predicate, bool recursive)
|
||||
{
|
||||
Item match = Items.FirstOrDefault(i => i != null && predicate(i));
|
||||
Item match = AllItems.FirstOrDefault(i => predicate(i));
|
||||
if (match == null && recursive)
|
||||
{
|
||||
foreach (var item in Items)
|
||||
foreach (var item in AllItems)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
if (item.OwnInventory != null)
|
||||
@@ -422,9 +776,8 @@ namespace Barotrauma
|
||||
public List<Item> FindAllItems(Func<Item, bool> predicate, bool recursive = false, List<Item> list = null)
|
||||
{
|
||||
list ??= new List<Item>();
|
||||
foreach (var item in Items)
|
||||
foreach (var item in AllItems)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
if (predicate(item))
|
||||
{
|
||||
list.Add(item);
|
||||
@@ -448,30 +801,57 @@ namespace Barotrauma
|
||||
|
||||
public Item FindItemByIdentifier(string identifier, bool recursive = false)
|
||||
{
|
||||
if (identifier == null) return null;
|
||||
if (identifier == null) { return null; }
|
||||
return FindItem(i => i.Prefab.Identifier == identifier, recursive);
|
||||
}
|
||||
|
||||
public virtual void RemoveItem(Item item)
|
||||
{
|
||||
if (item == null) return;
|
||||
if (item == null) { return; }
|
||||
|
||||
//go through the inventory and remove the item from all slots
|
||||
for (int n = 0; n < capacity; n++)
|
||||
{
|
||||
if (Items[n] != item) continue;
|
||||
|
||||
Items[n] = null;
|
||||
if (!slots[n].Contains(item)) { continue; }
|
||||
|
||||
slots[n].RemoveItem(item);
|
||||
item.ParentInventory = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces an item to a specific slot. Doesn't remove the item from existing slots/inventories or do any other sanity checks, use with caution!
|
||||
/// </summary>
|
||||
public void ForceToSlot(Item item, int index)
|
||||
{
|
||||
slots[index].Add(item);
|
||||
item.ParentInventory = this;
|
||||
if (item.body != null)
|
||||
{
|
||||
item.body.Enabled = false;
|
||||
item.body.BodyType = FarseerPhysics.BodyType.Dynamic;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from a specific slot. Doesn't do any sanity checks, use with caution!
|
||||
/// </summary>
|
||||
public void ForceRemoveFromSlot(Item item, int index)
|
||||
{
|
||||
slots[index].RemoveItem(item);
|
||||
}
|
||||
|
||||
|
||||
public void SharedWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
msg.Write((byte)capacity);
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
msg.Write((ushort)(Items[i] == null ? 0 : Items[i].ID));
|
||||
msg.WriteRangedInteger(slots[i].ItemCount, 0, MaxStackSize);
|
||||
foreach (Item item in slots[i].Items)
|
||||
{
|
||||
msg.Write((ushort)(item == null ? 0 : item.ID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,29 +862,17 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (Items[i] == null) continue;
|
||||
foreach (ItemContainer itemContainer in Items[i].GetComponents<ItemContainer>())
|
||||
if (!slots[i].Any()) { continue; }
|
||||
foreach (Item item in slots[i].Items)
|
||||
{
|
||||
itemContainer.Inventory.DeleteAllItems();
|
||||
foreach (ItemContainer itemContainer in item.GetComponents<ItemContainer>())
|
||||
{
|
||||
itemContainer.Inventory.DeleteAllItems();
|
||||
}
|
||||
}
|
||||
Items[i].Remove();
|
||||
slots[i].Items.ForEachMod(it => it.Remove());
|
||||
slots[i].RemoveAllItems();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Item> GetAllItems()
|
||||
{
|
||||
List<Item> deletedItems = new List<Item>();
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (Items[i] == null) continue;
|
||||
foreach (ItemContainer itemContainer in Items[i].GetComponents<ItemContainer>())
|
||||
{
|
||||
deletedItems.AddRange(itemContainer.Inventory.GetAllItems());
|
||||
}
|
||||
deletedItems.Add(Items[i]);
|
||||
}
|
||||
|
||||
return deletedItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ using Microsoft.Xna.Framework.Graphics;
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
partial class Item : MapEntity, IDamageable, ISerializableEntity, IServerSerializable, IClientSerializable
|
||||
partial class Item : MapEntity, IDamageable, IIgnorable, ISerializableEntity, IServerSerializable, IClientSerializable
|
||||
{
|
||||
public static List<Item> ItemList = new List<Item>();
|
||||
public ItemPrefab Prefab => prefab as ItemPrefab;
|
||||
@@ -76,7 +76,7 @@ namespace Barotrauma
|
||||
private readonly bool hasWaterStatusEffects;
|
||||
|
||||
private Inventory parentInventory;
|
||||
private readonly Inventory ownInventory;
|
||||
private readonly ItemInventory ownInventory;
|
||||
|
||||
private Rectangle defaultRect;
|
||||
|
||||
@@ -171,6 +171,43 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use <see cref="IsPlayerInteractable"/> to also check <see cref="NonInteractable"/>
|
||||
/// </summary>
|
||||
[Editable, Serialize(false, true, description: "When enabled, item is interactable only for characters on non-player teams.", alwaysUseInstanceValues: true)]
|
||||
public bool NonPlayerTeamInteractable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks both <see cref="NonInteractable"/> and <see cref="NonPlayerTeamInteractable"/>
|
||||
/// </summary>
|
||||
public bool IsPlayerTeamInteractable
|
||||
{
|
||||
get
|
||||
{
|
||||
return !NonInteractable && !NonPlayerTeamInteractable;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns interactibility based on whether the character is on a player team
|
||||
/// </summary>
|
||||
public bool IsInteractable(Character character)
|
||||
{
|
||||
if (character != null && character.IsOnPlayerTeam)
|
||||
{
|
||||
|
||||
return IsPlayerTeamInteractable;
|
||||
}
|
||||
else
|
||||
{
|
||||
return !NonInteractable;
|
||||
}
|
||||
}
|
||||
|
||||
private float rotationRad;
|
||||
|
||||
[Editable(0.0f, 360.0f, DecimalCount = 1, ValueStep = 1f), Serialize(0.0f, true)]
|
||||
@@ -414,6 +451,7 @@ namespace Barotrauma
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (!MathUtils.IsValid(value)) { return; }
|
||||
if (Indestructible) { return; }
|
||||
if (InvulnerableToDamage && value <= condition) { return;}
|
||||
|
||||
float prev = condition;
|
||||
bool wasInFullCondition = IsFullCondition;
|
||||
@@ -466,10 +504,13 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool Indestructible
|
||||
{
|
||||
get { return indestructible ?? Prefab.Indestructible; }
|
||||
set { indestructible = value; }
|
||||
get => indestructible ?? Prefab.Indestructible;
|
||||
set => indestructible = value;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, isSaveable: true, "When enabled will prevent the item from taking damage from all sources")]
|
||||
public bool InvulnerableToDamage { get; set; }
|
||||
|
||||
public bool StolenDuringRound;
|
||||
|
||||
private bool spawnedInOutpost;
|
||||
@@ -566,12 +607,12 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//which type of inventory slots (head, torso, any, etc) the item can be placed in
|
||||
public List<InvSlotType> AllowedSlots
|
||||
public IEnumerable<InvSlotType> AllowedSlots
|
||||
{
|
||||
get
|
||||
{
|
||||
Pickable p = GetComponent<Pickable>();
|
||||
return (p == null) ? new List<InvSlotType>() { InvSlotType.Any } : p.AllowedSlots;
|
||||
return (p == null) ? InvSlotType.Any.ToEnumerable() : p.AllowedSlots;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,19 +630,11 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
// It's not a good practice to return null if the method tells that it returns a collection, because:
|
||||
// a) the user has to handle this -> more code and more null reference exceptions
|
||||
// b) it makes it more difficult to make use of chained function calls (which are quite powerful), although '?' makes it possible
|
||||
// c) it's against the functional paradigm that e.g. Linq follows (for good reasons)
|
||||
// In general, it's better to return an empty collection instead,
|
||||
// but changing it here might cause unwanted implications.
|
||||
// Also it can be a minor optimization to return null instead of creating an empty collection,
|
||||
// but if that's the case I'd prefer caching an empty collection and using that instead. Just something to consider in the future.
|
||||
return ownInventory?.Items.Where(i => i != null);
|
||||
return ownInventory?.AllItems ?? Enumerable.Empty<Item>();
|
||||
}
|
||||
}
|
||||
|
||||
public Inventory OwnInventory
|
||||
public ItemInventory OwnInventory
|
||||
{
|
||||
get { return ownInventory; }
|
||||
}
|
||||
@@ -643,6 +676,8 @@ namespace Barotrauma
|
||||
get { return allPropertyObjects; }
|
||||
}
|
||||
|
||||
public bool IgnoreByAI => OrderedToBeIgnored || HasTag("ignorebyai");
|
||||
public bool OrderedToBeIgnored { get; set; }
|
||||
|
||||
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: this(new Rectangle(
|
||||
@@ -845,6 +880,8 @@ namespace Barotrauma
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public bool IsContainerPreferred(ItemContainer container, out bool isPreferencesDefined, out bool isSecondary) => Prefab.IsContainerPreferred(this, container, out isPreferencesDefined, out isSecondary);
|
||||
|
||||
public override MapEntity Clone()
|
||||
{
|
||||
Item clone = new Item(rect, Prefab, Submarine, callOnItemLoaded: false)
|
||||
@@ -899,14 +936,12 @@ namespace Barotrauma
|
||||
component.OnItemLoaded();
|
||||
}
|
||||
|
||||
if (ContainedItems != null)
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
var containedClone = containedItem.Clone();
|
||||
clone.ownInventory.TryPutItem(containedClone as Item, null);
|
||||
}
|
||||
}
|
||||
var containedClone = containedItem.Clone();
|
||||
clone.ownInventory.TryPutItem(containedClone as Item, null);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -1141,13 +1176,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (parentInventory != null && parentInventory.Owner != null)
|
||||
{
|
||||
if (parentInventory.Owner is Character)
|
||||
if (parentInventory.Owner is Character character)
|
||||
{
|
||||
CurrentHull = ((Character)parentInventory.Owner).AnimController.CurrentHull;
|
||||
CurrentHull = character.AnimController.CurrentHull;
|
||||
}
|
||||
else if (parentInventory.Owner is Item)
|
||||
else if (parentInventory.Owner is Item item)
|
||||
{
|
||||
CurrentHull = ((Item)parentInventory.Owner).CurrentHull;
|
||||
CurrentHull = item.CurrentHull;
|
||||
}
|
||||
|
||||
Submarine = parentInventory.Owner.Submarine;
|
||||
@@ -1157,7 +1192,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
CurrentHull = Hull.FindHull(WorldPosition, CurrentHull);
|
||||
if (body != null && body.Enabled)
|
||||
if (body != null && body.Enabled && (body.BodyType == BodyType.Dynamic || Submarine == null))
|
||||
{
|
||||
Submarine = CurrentHull?.Submarine;
|
||||
body.Submarine = Submarine;
|
||||
@@ -1180,7 +1215,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the item or any of its containers of the item set to be ignored?
|
||||
/// Should this item or any of its containers be ignored by the AI?
|
||||
/// </summary>
|
||||
public bool IsThisOrAnyContainerIgnoredByAI()
|
||||
{
|
||||
@@ -1308,22 +1343,17 @@ namespace Barotrauma
|
||||
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
|
||||
{
|
||||
var containedItems = ownInventory?.Items;
|
||||
if (containedItems != null)
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
foreach (Item containedItem in containedItems)
|
||||
if (effect.TargetIdentifiers != null &&
|
||||
!effect.TargetIdentifiers.Contains(containedItem.prefab.Identifier) &&
|
||||
!effect.TargetIdentifiers.Any(id => containedItem.HasTag(id)))
|
||||
{
|
||||
if (containedItem == null) { continue; }
|
||||
if (effect.TargetIdentifiers != null &&
|
||||
!effect.TargetIdentifiers.Contains(containedItem.prefab.Identifier) &&
|
||||
!effect.TargetIdentifiers.Any(id => containedItem.HasTag(id)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
hasTargets = true;
|
||||
targets.Add(containedItem);
|
||||
continue;
|
||||
}
|
||||
|
||||
hasTargets = true;
|
||||
targets.Add(containedItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1385,7 +1415,7 @@ namespace Barotrauma
|
||||
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
|
||||
{
|
||||
if (Indestructible) { return new AttackResult(); }
|
||||
if (Indestructible || InvulnerableToDamage) { return new AttackResult(); }
|
||||
|
||||
float damageAmount = attack.GetItemDamage(deltaTime);
|
||||
Condition -= damageAmount;
|
||||
@@ -1575,8 +1605,7 @@ namespace Barotrauma
|
||||
body.SetTransform(body.SimPosition + prevSub.SimPosition - Submarine.SimPosition, body.Rotation);
|
||||
}
|
||||
|
||||
var containedItems = ownInventory?.Items;
|
||||
if (Submarine != prevSub && containedItems != null)
|
||||
if (Submarine != prevSub)
|
||||
{
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
@@ -1664,15 +1693,10 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
var containedItems = ownInventory?.Items;
|
||||
if (containedItems != null)
|
||||
foreach (Item contained in ContainedItems)
|
||||
{
|
||||
foreach (Item contained in containedItems)
|
||||
{
|
||||
if (contained == null) { continue; }
|
||||
if (contained.body != null) { contained.HandleCollision(impact); }
|
||||
}
|
||||
}
|
||||
if (contained.body != null) { contained.HandleCollision(impact); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1936,7 +1960,7 @@ namespace Barotrauma
|
||||
Skill requiredSkill = null;
|
||||
float skillMultiplier = 1;
|
||||
#endif
|
||||
if (NonInteractable) { return false; }
|
||||
if (!IsInteractable(picker)) { return false; }
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
bool pickHit = false, selectHit = false;
|
||||
@@ -2040,24 +2064,19 @@ namespace Barotrauma
|
||||
|
||||
public float GetContainedItemConditionPercentage()
|
||||
{
|
||||
var containedItems = ContainedItems;
|
||||
if (ownInventory == null) { return -1; }
|
||||
|
||||
if (containedItems != null)
|
||||
float condition = 0f;
|
||||
float maxCondition = 0f;
|
||||
foreach (Item item in ContainedItems)
|
||||
{
|
||||
float condition = 0f;
|
||||
float maxCondition = 0f;
|
||||
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
condition += item.condition;
|
||||
maxCondition += item.MaxCondition;
|
||||
}
|
||||
|
||||
if (maxCondition > 0.0f)
|
||||
{
|
||||
return condition / maxCondition;
|
||||
}
|
||||
condition += item.condition;
|
||||
maxCondition += item.MaxCondition;
|
||||
}
|
||||
if (maxCondition > 0.0f)
|
||||
{
|
||||
return condition / maxCondition;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -2252,7 +2271,6 @@ namespace Barotrauma
|
||||
|
||||
public void Unequip(Character character)
|
||||
{
|
||||
character.DeselectItem(this);
|
||||
foreach (ItemComponent ic in components) { ic.Unequip(character); }
|
||||
}
|
||||
|
||||
@@ -2689,11 +2707,18 @@ namespace Barotrauma
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
var holdable = GetComponent<Holdable>();
|
||||
bool wasAttached = holdable?.Attached ?? false;
|
||||
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, Prefab.ConfigElement);
|
||||
Sprite.ReloadXML();
|
||||
SpriteDepth = Sprite.Depth;
|
||||
condition = MaxCondition;
|
||||
components.ForEach(c => c.Reset());
|
||||
if (wasAttached)
|
||||
{
|
||||
holdable.AttachToWall();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
@@ -2740,11 +2765,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.SelectedConstruction == this) character.SelectedConstruction = null;
|
||||
for (int i = 0; i < character.SelectedItems.Length; i++)
|
||||
{
|
||||
if (character.SelectedItems[i] == this) character.SelectedItems[i] = null;
|
||||
}
|
||||
if (character.SelectedConstruction == this) { character.SelectedConstruction = null; }
|
||||
}
|
||||
|
||||
Door door = GetComponent<Door>();
|
||||
|
||||
@@ -3,6 +3,7 @@ using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -22,19 +23,21 @@ namespace Barotrauma
|
||||
|
||||
public override int FindAllowedSlot(Item item)
|
||||
{
|
||||
if (ItemOwnsSelf(item)) return -1;
|
||||
if (ItemOwnsSelf(item)) { return -1; }
|
||||
|
||||
//item is already in the inventory!
|
||||
if (Contains(item)) { return -1; }
|
||||
if (!container.CanBeContained(item)) { return -1; }
|
||||
|
||||
//try to stack first
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
//item is already in the inventory!
|
||||
if (Items[i] == item) return -1;
|
||||
if (slots[i].Any() && CanBePut(item, i)) { return i; }
|
||||
}
|
||||
|
||||
if (!container.CanBeContained(item)) return -1;
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (Items[i] == null) return i;
|
||||
if (CanBePut(item, i)) { return i; }
|
||||
}
|
||||
|
||||
return -1;
|
||||
@@ -42,12 +45,50 @@ namespace Barotrauma
|
||||
|
||||
public override bool CanBePut(Item item, int i)
|
||||
{
|
||||
if (ItemOwnsSelf(item)) return false;
|
||||
if (i < 0 || i >= Items.Length) return false;
|
||||
return (item != null && Items[i] == null && container.CanBeContained(item));
|
||||
if (ItemOwnsSelf(item)) { return false; }
|
||||
if (i < 0 || i >= slots.Length) { return false; }
|
||||
if (!container.CanBeContained(item)) { return false; }
|
||||
return item != null && slots[i].CanBePut(item) && slots[i].ItemCount < container.MaxStackSize;
|
||||
}
|
||||
|
||||
public override bool TryPutItem(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
|
||||
public override bool CanBePut(ItemPrefab itemPrefab, int i)
|
||||
{
|
||||
if (i < 0 || i >= slots.Length) { return false; }
|
||||
if (!container.CanBeContained(itemPrefab)) { return false; }
|
||||
return itemPrefab != null && slots[i].CanBePut(itemPrefab) && slots[i].ItemCount < container.MaxStackSize;
|
||||
}
|
||||
|
||||
public override int HowManyCanBePut(ItemPrefab itemPrefab, int i)
|
||||
{
|
||||
if (itemPrefab == null) { return 0; }
|
||||
if (i < 0 || i >= slots.Length) { return 0; }
|
||||
if (!container.CanBeContained(itemPrefab)) { return 0; }
|
||||
return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.MaxStackSize));
|
||||
}
|
||||
|
||||
public override bool IsFull(bool takeStacksIntoAccount = false)
|
||||
{
|
||||
if (takeStacksIntoAccount)
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (!slots[i].Any()) { return false; }
|
||||
var item = slots[i].FirstOrDefault();
|
||||
if (slots[i].ItemCount < Math.Min(item.Prefab.MaxStackSize, container.MaxStackSize)) { return false; }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (!slots[i].Any()) { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryPutItem(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
|
||||
{
|
||||
bool wasPut = base.TryPutItem(item, user, allowedSlots, createNetworkEvent);
|
||||
|
||||
@@ -55,8 +96,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!c.HasSelectedItem(item)) continue;
|
||||
|
||||
if (!c.HeldItems.Contains(item)) { continue; }
|
||||
item.Unequip(c);
|
||||
break;
|
||||
}
|
||||
@@ -71,13 +111,11 @@ namespace Barotrauma
|
||||
public override bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true)
|
||||
{
|
||||
bool wasPut = base.TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent);
|
||||
|
||||
if (wasPut)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!c.HasSelectedItem(item)) continue;
|
||||
|
||||
if (!c.HeldItems.Contains(item)) { continue; }
|
||||
item.Unequip(c);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Barotrauma
|
||||
public readonly float OutCondition;
|
||||
//should the condition of the deconstructed item be copied to the output items
|
||||
public readonly bool CopyCondition;
|
||||
public float Commonness { get; }
|
||||
|
||||
public DeconstructItem(XElement element)
|
||||
{
|
||||
@@ -30,6 +31,7 @@ namespace Barotrauma
|
||||
MaxCondition = element.GetAttributeFloat("maxcondition", 1.0f);
|
||||
OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
|
||||
CopyCondition = element.GetAttributeBool("copycondition", false);
|
||||
Commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +68,7 @@ namespace Barotrauma
|
||||
public readonly float RequiredTime;
|
||||
public readonly float OutCondition; //Percentage-based from 0 to 1
|
||||
public readonly List<Skill> RequiredSkills;
|
||||
public int Amount { get; }
|
||||
|
||||
public FabricationRecipe(XElement element, ItemPrefab itemPrefab)
|
||||
{
|
||||
@@ -79,6 +82,7 @@ namespace Barotrauma
|
||||
RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f);
|
||||
OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
|
||||
RequiredItems = new List<RequiredItem>();
|
||||
Amount = element.GetAttributeInt("amount", 1);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -118,7 +122,7 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
var existing = RequiredItems.Find(r => r.ItemPrefabs.Count == 1 && r.ItemPrefabs[0] == requiredItem);
|
||||
var existing = RequiredItems.Find(r => r.ItemPrefabs.Count == 1 && r.ItemPrefabs[0] == requiredItem && MathUtils.NearlyEqual(r.MinCondition, minCondition));
|
||||
if (existing == null)
|
||||
{
|
||||
RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition));
|
||||
@@ -137,7 +141,7 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
var existing = RequiredItems.Find(r => r.ItemPrefabs.SequenceEqual(matchingItems));
|
||||
var existing = RequiredItems.Find(r => r.ItemPrefabs.SequenceEqual(matchingItems) && MathUtils.NearlyEqual(r.MinCondition, minCondition));
|
||||
if (existing == null)
|
||||
{
|
||||
RequiredItems.Add(new RequiredItem(matchingItems, count, minCondition, useCondition));
|
||||
@@ -157,7 +161,10 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly HashSet<string> Primary = new HashSet<string>();
|
||||
public readonly HashSet<string> Secondary = new HashSet<string>();
|
||||
|
||||
public float SpawnProbability { get; private set; }
|
||||
public float MaxCondition { get; private set; }
|
||||
public float MinCondition { get; private set; }
|
||||
public int MinAmount { get; private set; }
|
||||
public int MaxAmount { get; private set; }
|
||||
|
||||
@@ -168,6 +175,8 @@ namespace Barotrauma
|
||||
SpawnProbability = element.GetAttributeFloat("spawnprobability", 0.0f);
|
||||
MinAmount = element.GetAttributeInt("minamount", 0);
|
||||
MaxAmount = Math.Max(MinAmount, element.GetAttributeInt("maxamount", 0));
|
||||
MaxCondition = element.GetAttributeFloat("maxcondition", 100f);
|
||||
MinCondition = element.GetAttributeFloat("mincondition", 0f);
|
||||
|
||||
if (element.Attribute("spawnprobability") == null)
|
||||
{
|
||||
@@ -498,10 +507,27 @@ namespace Barotrauma
|
||||
|
||||
public bool CanSpriteFlipY { get; private set; }
|
||||
|
||||
private int maxStackSize;
|
||||
[Serialize(1, false)]
|
||||
public int MaxStackSize
|
||||
{
|
||||
get { return maxStackSize; }
|
||||
set { maxStackSize = MathHelper.Clamp(value, 1, Inventory.MaxStackSize); }
|
||||
}
|
||||
|
||||
public Vector2 Size => size;
|
||||
|
||||
public bool CanBeBought => (defaultPrice != null && defaultPrice.CanBeBought) || (locationPrices != null && locationPrices.Any(p => p.Value.CanBeBought));
|
||||
|
||||
/// <summary>
|
||||
/// Any item with a Price element in the definition can be sold everywhere.
|
||||
/// </summary>
|
||||
public bool CanBeSold => defaultPrice != null;
|
||||
|
||||
public bool RandomDeconstructionOutput { get; }
|
||||
|
||||
public int RandomDeconstructionOutputAmount { get; }
|
||||
|
||||
public static void RemoveByFile(string filePath) => Prefabs.RemoveByFile(filePath);
|
||||
|
||||
public static void LoadFromFile(ContentFile file)
|
||||
@@ -876,6 +902,8 @@ namespace Barotrauma
|
||||
case "deconstruct":
|
||||
DeconstructTime = subElement.GetAttributeFloat("time", 1.0f);
|
||||
AllowDeconstruct = true;
|
||||
RandomDeconstructionOutput = subElement.GetAttributeBool("chooserandom", false);
|
||||
RandomDeconstructionOutputAmount = subElement.GetAttributeInt("amount", 1);
|
||||
foreach (XElement deconstructItem in subElement.Elements())
|
||||
{
|
||||
if (deconstructItem.Attribute("name") != null)
|
||||
@@ -883,10 +911,9 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Error in item config \"" + Name + "\" - use item identifiers instead of names to configure the deconstruct items.");
|
||||
continue;
|
||||
}
|
||||
|
||||
DeconstructItems.Add(new DeconstructItem(deconstructItem));
|
||||
}
|
||||
|
||||
RandomDeconstructionOutputAmount = Math.Min(RandomDeconstructionOutputAmount, DeconstructItems.Count);
|
||||
break;
|
||||
case "fabricate":
|
||||
case "fabricable":
|
||||
@@ -1073,32 +1100,34 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsContainerPreferred(ItemContainer itemContainer, out bool isPreferencesDefined, out bool isSecondary)
|
||||
public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary)
|
||||
{
|
||||
isPreferencesDefined = PreferredContainers.Any();
|
||||
isSecondary = false;
|
||||
if (!isPreferencesDefined) { return true; }
|
||||
if (PreferredContainers.Any(pc => IsContainerPreferred(pc.Primary, itemContainer)))
|
||||
if (PreferredContainers.Any(pc => IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Primary, targetContainer)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
isSecondary = true;
|
||||
return PreferredContainers.Any(pc => IsContainerPreferred(pc.Secondary, itemContainer));
|
||||
return PreferredContainers.Any(pc => IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Secondary, targetContainer));
|
||||
}
|
||||
|
||||
public bool IsContainerPreferred(string[] identifiersOrTags, out bool isPreferencesDefined, out bool isSecondary)
|
||||
public bool IsContainerPreferred(Item item, string[] identifiersOrTags, out bool isPreferencesDefined, out bool isSecondary)
|
||||
{
|
||||
isPreferencesDefined = PreferredContainers.Any();
|
||||
isSecondary = false;
|
||||
if (!isPreferencesDefined) { return true; }
|
||||
if (PreferredContainers.Any(pc => IsContainerPreferred(pc.Primary, identifiersOrTags)))
|
||||
if (PreferredContainers.Any(pc => IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Primary, identifiersOrTags)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
isSecondary = true;
|
||||
return PreferredContainers.Any(pc => IsContainerPreferred(pc.Secondary, identifiersOrTags));
|
||||
return PreferredContainers.Any(pc => IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Secondary, identifiersOrTags));
|
||||
}
|
||||
|
||||
private bool IsItemConditionAcceptable(Item item, PreferredContainer pc) => item.ConditionPercentage >= pc.MinCondition && item.ConditionPercentage <= pc.MaxCondition;
|
||||
|
||||
public static bool IsContainerPreferred(IEnumerable<string> preferences, ItemContainer c) => preferences.Any(id => c.Item.Prefab.Identifier == id || c.Item.HasTag(id));
|
||||
public static bool IsContainerPreferred(IEnumerable<string> preferences, IEnumerable<string> ids) => ids.Any(id => preferences.Contains(id));
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public bool IsOptional { get; set; }
|
||||
|
||||
public bool MatchOnEmpty { get; set; }
|
||||
|
||||
public bool IgnoreInEditor { get; set; }
|
||||
@@ -30,6 +31,10 @@ namespace Barotrauma
|
||||
public string Msg;
|
||||
public string MsgTag;
|
||||
|
||||
/// <summary>
|
||||
/// Should broken (0 condition) items be excluded
|
||||
/// </summary>
|
||||
public bool ExcludeBroken { get; private set; }
|
||||
|
||||
public RelationType Type
|
||||
{
|
||||
@@ -107,21 +112,20 @@ namespace Barotrauma
|
||||
return CheckContained(parentItem);
|
||||
case RelationType.Container:
|
||||
if (parentItem == null || parentItem.Container == null) { return MatchOnEmpty; }
|
||||
return parentItem.Container.Condition > 0.0f && MatchesItem(parentItem.Container);
|
||||
return (!ExcludeBroken || parentItem.Container.Condition > 0.0f) && MatchesItem(parentItem.Container);
|
||||
case RelationType.Equipped:
|
||||
if (character == null) { return false; }
|
||||
if (MatchOnEmpty && character.SelectedItems.All(it => it == null)) { return true; }
|
||||
foreach (Item equippedItem in character.SelectedItems)
|
||||
if (MatchOnEmpty && !character.HeldItems.Any()) { return true; }
|
||||
foreach (Item equippedItem in character.HeldItems)
|
||||
{
|
||||
if (equippedItem == null) { continue; }
|
||||
if (equippedItem.Condition > 0.0f && MatchesItem(equippedItem)) { return true; }
|
||||
if ((!ExcludeBroken || equippedItem.Condition > 0.0f) && MatchesItem(equippedItem)) { return true; }
|
||||
}
|
||||
break;
|
||||
case RelationType.Picked:
|
||||
if (character == null || character.Inventory == null) { return false; }
|
||||
foreach (Item pickedItem in character.Inventory.Items)
|
||||
foreach (Item pickedItem in character.Inventory.AllItems)
|
||||
{
|
||||
if (pickedItem == null) { continue; }
|
||||
if (MatchesItem(pickedItem)) { return true; }
|
||||
}
|
||||
break;
|
||||
@@ -134,18 +138,16 @@ namespace Barotrauma
|
||||
|
||||
private bool CheckContained(Item parentItem)
|
||||
{
|
||||
var containedItems = parentItem.OwnInventory?.Items;
|
||||
if (containedItems == null) { return false; }
|
||||
if (parentItem.OwnInventory == null) { return false; }
|
||||
|
||||
if (MatchOnEmpty && !containedItems.Any(ci => ci != null))
|
||||
if (MatchOnEmpty && parentItem.OwnInventory.IsEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (Item contained in containedItems)
|
||||
foreach (Item contained in parentItem.ContainedItems)
|
||||
{
|
||||
if (contained == null) { continue; }
|
||||
if (contained.Condition > 0.0f && MatchesItem(contained)) { return true; }
|
||||
if ((!ExcludeBroken || contained.Condition > 0.0f) && MatchesItem(contained)) { return true; }
|
||||
if (CheckContained(contained)) { return true; }
|
||||
}
|
||||
return false;
|
||||
@@ -157,7 +159,8 @@ namespace Barotrauma
|
||||
new XAttribute("items", JoinedIdentifiers),
|
||||
new XAttribute("type", type.ToString()),
|
||||
new XAttribute("optional", IsOptional),
|
||||
new XAttribute("ignoreineditor", IgnoreInEditor));
|
||||
new XAttribute("ignoreineditor", IgnoreInEditor),
|
||||
new XAttribute("excludebroken", ExcludeBroken));
|
||||
|
||||
if (excludedIdentifiers.Length > 0)
|
||||
{
|
||||
@@ -215,9 +218,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (identifiers.Length == 0 && excludedIdentifiers.Length == 0 && !returnEmpty) { return null; }
|
||||
|
||||
RelatedItem ri = new RelatedItem(identifiers, excludedIdentifiers);
|
||||
RelatedItem ri = new RelatedItem(identifiers, excludedIdentifiers)
|
||||
{
|
||||
ExcludeBroken = element.GetAttributeBool("excludebroken", true)
|
||||
};
|
||||
string typeStr = element.GetAttributeString("type", "");
|
||||
if (string.IsNullOrEmpty(typeStr))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user