v1.0.13.1 (first post-1.0 patch)
This commit is contained in:
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
public static readonly List<InvSlotType> anySlot = new List<InvSlotType>() { InvSlotType.Any };
|
||||
public static readonly List<InvSlotType> AnySlot = new List<InvSlotType>() { InvSlotType.Any };
|
||||
|
||||
protected bool[] IsEquipped;
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace Barotrauma
|
||||
if (item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
var wearable = item.GetComponent<Wearable>();
|
||||
if (wearable != null && !wearable.AutoEquipWhenFull && CheckIfAnySlotAvailable(item, false) == -1)
|
||||
if (wearable != null && !wearable.AutoEquipWhenFull && !IsAnySlotAvailable(item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -336,7 +336,7 @@ namespace Barotrauma
|
||||
//try to place the item in a LimbSlot.Any slot if that's allowed
|
||||
if (allowedSlots.Contains(InvSlotType.Any) && item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
int freeIndex = CheckIfAnySlotAvailable(item, inWrongSlot);
|
||||
int freeIndex = GetFreeAnySlot(item, inWrongSlot);
|
||||
if (freeIndex > -1)
|
||||
{
|
||||
PutItem(item, freeIndex, user, true, createNetworkEvent);
|
||||
@@ -393,7 +393,9 @@ namespace Barotrauma
|
||||
return placedInSlot > -1;
|
||||
}
|
||||
|
||||
public int CheckIfAnySlotAvailable(Item item, bool inWrongSlot)
|
||||
public bool IsAnySlotAvailable(Item item) => GetFreeAnySlot(item, inWrongSlot: false) > -1;
|
||||
|
||||
private int GetFreeAnySlot(Item item, bool inWrongSlot)
|
||||
{
|
||||
//attempt to stack first
|
||||
for (int i = 0; i < capacity; i++)
|
||||
|
||||
@@ -561,6 +561,12 @@ namespace Barotrauma.Items.Components
|
||||
gap.ConnectedDoor = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (OutsideSubmarineFixture != null)
|
||||
{
|
||||
OutsideSubmarineFixture.Body.Remove(OutsideSubmarineFixture);
|
||||
OutsideSubmarineFixture = null;
|
||||
}
|
||||
|
||||
//no need to remove the gap if we're unloading the whole submarine
|
||||
//otherwise the gap will be removed twice and cause console warnings
|
||||
|
||||
@@ -600,7 +600,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
//no further data needed, the event just triggers the discharge
|
||||
msg.WriteUInt16(user?.ID ?? Entity.NullEntityID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,7 +700,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
Vector2 attachPos = GetAttachPosition(character, useWorldCoordinates: true);
|
||||
Submarine attachSubmarine = Structure.GetAttachTarget(attachPos)?.Submarine ?? item.Submarine;
|
||||
int maxAttachableCount = (int)character.Info.GetSavedStatValue(StatTypes.MaxAttachableCount, item.Prefab.Identifier);
|
||||
int maxAttachableCount = (int)character.Info.GetSavedStatValueWithBotsInMp(StatTypes.MaxAttachableCount, item.Prefab.Identifier);
|
||||
|
||||
int currentlyAttachedCount = Item.ItemList.Count(
|
||||
i => i.Submarine == attachSubmarine && i.GetComponent<Holdable>() is Holdable holdable && holdable.Attached && i.Prefab.Identifier == item.Prefab.Identifier);
|
||||
if (maxAttachableCount == 0)
|
||||
|
||||
@@ -260,7 +260,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
|
||||
float rotation = (Item.body.Dir == 1.0f) ? Item.body.Rotation : Item.body.Rotation - MathHelper.Pi;
|
||||
float spread = GetSpread(character) * Projectile.GetSpreadFromPool(projectile.SpreadCounter);
|
||||
float spread = GetSpread(character) * projectile.GetSpreadFromPool();
|
||||
|
||||
var lastProjectile = LastProjectile;
|
||||
if (lastProjectile != projectile)
|
||||
@@ -277,7 +277,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Item.body.ApplyLinearImpulse(new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * Item.body.Mass * -50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * 20.0f * Projectile.GetSpreadFromPool(projectile.SpreadCounter));
|
||||
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * 20.0f * projectile.GetSpreadFromPool());
|
||||
}
|
||||
Item.RemoveContained(projectile.Item);
|
||||
}
|
||||
|
||||
@@ -84,6 +84,9 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can the item repair multiple things at once, or will it only affect the first thing the ray from the barrel hits.")]
|
||||
public bool RepairMultiple { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Can the item repair multiple walls at once? Only relevant if RepairMultiple is true.")]
|
||||
public bool RepairMultipleWalls { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can the item repair things through holes in walls.")]
|
||||
public bool RepairThroughHoles { get; set; }
|
||||
|
||||
@@ -383,6 +386,7 @@ namespace Barotrauma.Items.Components
|
||||
//stop the ray if it already hit a door/wall and is now about to hit some other type of entity
|
||||
if (lastHitType == typeof(Item) || lastHitType == typeof(Structure)) { break; }
|
||||
}
|
||||
if (!RepairMultipleWalls && (bodyType == typeof(Structure) || (body.UserData as Item)?.GetComponent<Door>() != null)) { break; }
|
||||
|
||||
Character hitCharacter = null;
|
||||
if (body.UserData is Limb limb)
|
||||
|
||||
@@ -236,7 +236,7 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.No, 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). Note that there's also a generic BotPriority for all item prefabs.")]
|
||||
[Serialize(0f, IsPropertySaveable.No, 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 forced). Note that there's also a generic BotPriority for all item prefabs.")]
|
||||
public float CombatPriority { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -687,9 +687,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public virtual void FlipY(bool relativeToSub) { }
|
||||
|
||||
public bool IsNotEmpty(Character user, bool checkContainedItems = true) =>
|
||||
HasRequiredContainedItems(user, addMessage: false) &&
|
||||
(!checkContainedItems || Item.OwnInventory == null || Item.OwnInventory.AllItems.Any(i => i.Condition > 0));
|
||||
/// <summary>
|
||||
/// Shorthand for !HasRequiredContainedItems()
|
||||
/// </summary>
|
||||
public bool IsEmpty(Character user) => !HasRequiredContainedItems(user, addMessage: false);
|
||||
|
||||
public bool HasRequiredContainedItems(Character user, bool addMessage, LocalizedString msg = null)
|
||||
{
|
||||
|
||||
@@ -511,25 +511,28 @@ namespace Barotrauma.Items.Components
|
||||
if (activeContainedItem.ExcludeFullCondition && contained.IsFullCondition) { continue; }
|
||||
StatusEffect effect = activeContainedItem.StatusEffect;
|
||||
|
||||
targets.Clear();
|
||||
bool wearing = item.GetComponent<Wearable>() is Wearable { IsActive: true };
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, item.AllPropertyObjects);
|
||||
targets.AddRange(item.AllPropertyObjects);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
|
||||
{
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, contained.AllPropertyObjects);
|
||||
targets.AddRange(contained.AllPropertyObjects);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Character) && item.ParentInventory?.Owner is Character character)
|
||||
{
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, character);
|
||||
targets.Add(character);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
effect.AddNearbyTargets(item.WorldPosition, targets);
|
||||
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
|
||||
}
|
||||
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, targets);
|
||||
if (wearing) { effect.Apply(ActionType.OnWearing, deltaTime, item, targets); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ namespace Barotrauma.Items.Components
|
||||
itemList.Enabled = true;
|
||||
if (amountInput != null)
|
||||
{
|
||||
amountInput.Enabled = true;
|
||||
amountInput.Enabled = amountTextMax.Enabled;
|
||||
}
|
||||
RefreshActivateButtonText();
|
||||
#endif
|
||||
|
||||
@@ -14,19 +14,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Projectile : ItemComponent, IServerSerializable
|
||||
{
|
||||
const int SpreadCounterWrapAround = 256;
|
||||
|
||||
private static readonly ImmutableArray<float> spreadPool;
|
||||
static Projectile()
|
||||
{
|
||||
MTRandom random = new MTRandom(0);
|
||||
spreadPool = Enumerable.Range(0, SpreadCounterWrapAround).Select(f => (float)random.NextDouble() - 0.5f).ToImmutableArray();
|
||||
spreadPool = Enumerable.Range(0, byte.MaxValue + 1).Select(f => (float)random.NextDouble() - 0.5f).ToImmutableArray();
|
||||
}
|
||||
|
||||
public static float GetSpreadFromPool(int seed)
|
||||
public static byte SpreadCounter { get; private set; }
|
||||
|
||||
public static void ResetSpreadCounter()
|
||||
{
|
||||
if (seed < 0) { seed = -seed; }
|
||||
return spreadPool[seed % SpreadCounterWrapAround];
|
||||
SpreadCounter = 0;
|
||||
}
|
||||
|
||||
struct HitscanResult
|
||||
@@ -63,7 +62,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool removePending;
|
||||
|
||||
public byte SpreadCounter { get; private set; }
|
||||
private byte spreadIndex;
|
||||
|
||||
//continuous collision detection is used while the projectile is moving faster than this
|
||||
const float ContinuousCollisionThreshold = 5.0f;
|
||||
@@ -212,7 +211,7 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Override random spread with static spread; hitscan are launched with an equal amount of angle between them. Only applies when firing multiple hitscan.")]
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Override random spread with static spread; projectiles are launched with an equal amount of angle between them. Only applies when firing multiple projectiles.")]
|
||||
public bool StaticSpread
|
||||
{
|
||||
get;
|
||||
@@ -300,7 +299,8 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
SpreadCounter = (byte)(item.ID % SpreadCounterWrapAround);
|
||||
spreadIndex = SpreadCounter;
|
||||
SpreadCounter++;
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
@@ -329,6 +329,12 @@ namespace Barotrauma.Items.Components
|
||||
originalCollisionTargets = item.body.CollidesWith;
|
||||
}
|
||||
|
||||
public float GetSpreadFromPool()
|
||||
{
|
||||
spreadIndex = (byte)MathUtils.PositiveModulo(spreadIndex, spreadPool.Length);
|
||||
return spreadPool[spreadIndex];
|
||||
}
|
||||
|
||||
private void Launch(Character user, Vector2 simPosition, float rotation, float damageMultiplier = 1f, float launchImpulseModifier = 0f)
|
||||
{
|
||||
if (Item.body == null) { return; }
|
||||
@@ -380,8 +386,8 @@ namespace Barotrauma.Items.Components
|
||||
if (createNetworkEvent && !Item.Removed && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
#if SERVER
|
||||
launchRot = rotation;
|
||||
Item.CreateServerEvent(this, new EventData(launch: true, spreadCounter: (byte)(SpreadCounter - 1)));
|
||||
launchRot = rotation;
|
||||
Item.CreateServerEvent(this, new EventData(launch: true, spreadCounter: (byte)(spreadIndex - 1)));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -390,24 +396,22 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character != null && !characterUsable) { return false; }
|
||||
if (item.body == null) { return false; }
|
||||
//can't launch if already launched
|
||||
if (StickTarget != null || IsActive) { return false; }
|
||||
|
||||
float initialRotation = item.body.Rotation;
|
||||
for (int i = 0; i < HitScanCount; i++)
|
||||
{
|
||||
float launchAngle;
|
||||
|
||||
if (StaticSpread)
|
||||
{
|
||||
float staticSpread = Spread / (HitScanCount - 1);
|
||||
// because the position of the item changes as hitscan are fired, we will set an
|
||||
// initial offset on the first hitscan and then increase the item's angle by a set amount as hitscan are fired
|
||||
float offset = i == 0 ? -staticSpread * (HitScanCount -1) : 0f;
|
||||
launchAngle = item.body.Rotation + MathHelper.ToRadians(staticSpread + offset);
|
||||
launchAngle = initialRotation + MathHelper.ToRadians(i - ((float)(HitScanCount - 1) / 2)) * Spread;
|
||||
}
|
||||
else
|
||||
{
|
||||
launchAngle = item.body.Rotation + MathHelper.ToRadians(Spread * GetSpreadFromPool(SpreadCounter));
|
||||
launchAngle = initialRotation + MathHelper.ToRadians(Spread * GetSpreadFromPool());
|
||||
}
|
||||
SpreadCounter++;
|
||||
spreadIndex++;
|
||||
|
||||
Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle));
|
||||
if (Hitscan)
|
||||
|
||||
@@ -243,14 +243,7 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < labels.Length; i++)
|
||||
{
|
||||
labels[i] = i < newLabels.Length ? newLabels[i] : customInterfaceElementList[i].Label;
|
||||
if (Screen.Selected != GameMain.SubEditorScreen)
|
||||
{
|
||||
customInterfaceElementList[i].Label = TextManager.Get(labels[i]).Fallback(labels[i]).Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
customInterfaceElementList[i].Label = labels[i];
|
||||
}
|
||||
customInterfaceElementList[i].Label = labels[i];
|
||||
}
|
||||
UpdateLabelsProjSpecific();
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ namespace Barotrauma.Items.Components
|
||||
Position = item.Position,
|
||||
CastShadows = castShadows,
|
||||
IsBackground = drawBehindSubs,
|
||||
SpriteScale = Vector2.One * item.Scale,
|
||||
SpriteScale = Vector2.One * item.Scale * LightSpriteScale,
|
||||
Range = range
|
||||
};
|
||||
Light.LightSourceParams.Flicker = flicker;
|
||||
@@ -309,13 +309,14 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
Light.ParentSub = item.Submarine;
|
||||
#endif
|
||||
if (item.Container != null && item.GetRootInventoryOwner() is not Character)
|
||||
var ownerCharacter = item.GetRootInventoryOwner() as Character;
|
||||
if ((item.Container != null && ownerCharacter == null) ||
|
||||
(ownerCharacter != null && ownerCharacter.InvisibleTimer > 0.0f))
|
||||
{
|
||||
lightBrightness = 0.0f;
|
||||
SetLightSourceState(false, 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
SetLightSourceTransformProjSpecific();
|
||||
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
|
||||
@@ -56,11 +56,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float resetUserTimer;
|
||||
|
||||
private float aiTargetingGraceTimer;
|
||||
|
||||
private float aiFindTargetTimer;
|
||||
private ISpatialEntity currentTarget;
|
||||
private const float CrewAiFindTargetMaxInterval = 3.0f;
|
||||
private const float CrewAiFindTargetMaxInterval = 1.0f;
|
||||
private const float CrewAIFindTargetMinInverval = 0.2f;
|
||||
|
||||
private int currentLoaderIndex;
|
||||
@@ -299,7 +297,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(3000.0f, IsPropertySaveable.Yes, description: "How close to a target the turret has to be for an AI character to fire it.")]
|
||||
[Serialize(3500.0f, IsPropertySaveable.Yes, description: "How close to a target the turret has to be for an AI character to fire it.")]
|
||||
public float AIRange
|
||||
{
|
||||
get;
|
||||
@@ -422,10 +420,7 @@ namespace Barotrauma.Items.Components
|
||||
// Only make the Turret control the LightComponents that are it's children. So it'd be possible to for example have some extra lights on the turret that don't rotate with it.
|
||||
if (lc?.Parent == this)
|
||||
{
|
||||
if (lightComponents == null)
|
||||
{
|
||||
lightComponents = new List<LightComponent>();
|
||||
}
|
||||
lightComponents ??= new List<LightComponent>();
|
||||
lightComponents.Add(lc);
|
||||
}
|
||||
}
|
||||
@@ -439,6 +434,8 @@ namespace Barotrauma.Items.Components
|
||||
light.Parent = null;
|
||||
light.Rotation = Rotation - item.RotationRad;
|
||||
light.Light.Rotation = -rotation;
|
||||
//turret lights are high-prio (don't want the lights to disappear when you're fighting something)
|
||||
light.Light.PriorityMultiplier *= 10.0f;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -590,10 +587,6 @@ namespace Barotrauma.Items.Components
|
||||
angularVelocity *= -0.5f;
|
||||
}
|
||||
|
||||
if (aiTargetingGraceTimer > 0f)
|
||||
{
|
||||
aiTargetingGraceTimer -= deltaTime;
|
||||
}
|
||||
if (aiFindTargetTimer > 0.0f)
|
||||
{
|
||||
aiFindTargetTimer -= deltaTime;
|
||||
@@ -620,10 +613,18 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
private bool isUseBeingCalled;
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (!characterUsable && character != null) { return false; }
|
||||
return TryLaunch(deltaTime, character);
|
||||
//prevent an infinite loop if launching triggers a StatusEffect that Uses this item
|
||||
if (isUseBeingCalled) { return false; }
|
||||
|
||||
isUseBeingCalled = true;
|
||||
bool wasSuccessful = TryLaunch(deltaTime, character);
|
||||
isUseBeingCalled = false;
|
||||
return wasSuccessful;
|
||||
}
|
||||
|
||||
public float GetPowerRequiredToShoot()
|
||||
@@ -1015,6 +1016,7 @@ namespace Barotrauma.Items.Components
|
||||
float dist = Vector2.DistanceSquared(item.WorldPosition, targetItem.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
if (dist > shootDistance * shootDistance) { continue; }
|
||||
if (!IsTargetItemCloseEnough(targetItem, dist)) { continue; }
|
||||
if (!CheckTurretAngle(targetItem.WorldPosition)) { continue; }
|
||||
target = targetItem;
|
||||
closestDist = dist / priority;
|
||||
@@ -1130,9 +1132,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget && previousTarget.IsDead)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTurretTargetDead").Value,
|
||||
identifier: $"killedtarget{previousTarget.ID}".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 10.0f);
|
||||
if (previousTarget.LastAttacker == null || previousTarget.LastAttacker == character)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTurretTargetDead").Value,
|
||||
identifier: $"killedtarget{previousTarget.ID}".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 5.0f);
|
||||
}
|
||||
character.AIController.SelectTarget(null);
|
||||
}
|
||||
|
||||
@@ -1265,18 +1270,27 @@ namespace Barotrauma.Items.Components
|
||||
Vector2? targetPos = null;
|
||||
float maxDistance = 10000;
|
||||
float shootDistance = AIRange * item.OffsetOnSelectedMultiplier;
|
||||
// use full range only if we're actively firing
|
||||
if (aiTargetingGraceTimer <= 0f)
|
||||
{
|
||||
shootDistance *= 0.75f;
|
||||
}
|
||||
|
||||
float closestDistance = maxDistance * maxDistance;
|
||||
|
||||
bool hadCurrentTarget = currentTarget != null;
|
||||
if (hadCurrentTarget)
|
||||
{
|
||||
if (!IsValidTarget(currentTarget))
|
||||
bool isValidTarget = IsValidTarget(currentTarget);
|
||||
if (isValidTarget)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(item.WorldPosition, currentTarget.WorldPosition);
|
||||
if (dist > closestDistance)
|
||||
{
|
||||
isValidTarget = false;
|
||||
}
|
||||
else if (currentTarget is Item targetItem)
|
||||
{
|
||||
if (!IsTargetItemCloseEnough(targetItem, dist))
|
||||
{
|
||||
isValidTarget = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isValidTarget)
|
||||
{
|
||||
currentTarget = null;
|
||||
aiFindTargetTimer = CrewAIFindTargetMinInverval;
|
||||
@@ -1292,7 +1306,11 @@ namespace Barotrauma.Items.Components
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (enemy.Submarine == character.Submarine) { continue; }
|
||||
if (enemy.Submarine != null && enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; }
|
||||
if (enemy.Submarine != null)
|
||||
{
|
||||
if (enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; }
|
||||
if (enemy.Submarine.Info.IsOutpost) { continue; }
|
||||
}
|
||||
}
|
||||
// Don't aim monsters that are inside any submarine.
|
||||
if (!enemy.IsHuman && enemy.CurrentHull != null) { continue; }
|
||||
@@ -1318,6 +1336,7 @@ namespace Barotrauma.Items.Components
|
||||
float dist = Vector2.DistanceSquared(item.WorldPosition, targetItem.WorldPosition);
|
||||
if (dist > closestDistance) { continue; }
|
||||
if (dist > shootDistance * shootDistance) { continue; }
|
||||
if (!IsTargetItemCloseEnough(targetItem, dist)) { continue; }
|
||||
if (!CheckTurretAngle(targetItem.WorldPosition)) { continue; }
|
||||
targetPos = targetItem.WorldPosition;
|
||||
closestDistance = dist / priority;
|
||||
@@ -1325,14 +1344,7 @@ namespace Barotrauma.Items.Components
|
||||
closestEnemy = null;
|
||||
currentTarget = targetItem;
|
||||
}
|
||||
if (currentTarget == null)
|
||||
{
|
||||
aiFindTargetTimer = CrewAIFindTargetMinInverval;
|
||||
}
|
||||
else
|
||||
{
|
||||
aiFindTargetTimer = CrewAiFindTargetMaxInterval;
|
||||
}
|
||||
aiFindTargetTimer = currentTarget == null ? CrewAiFindTargetMaxInterval : CrewAIFindTargetMinInverval;
|
||||
}
|
||||
else if (currentTarget != null)
|
||||
{
|
||||
@@ -1346,6 +1358,10 @@ namespace Barotrauma.Items.Components
|
||||
if (targetCharacter.Submarine != null && targetCharacter.CurrentHull != null && targetCharacter.Submarine != item.Submarine && !targetCharacter.CanSeeTarget(Item))
|
||||
{
|
||||
targetPos = targetCharacter.CurrentHull.WorldPosition;
|
||||
if (closestDistance > maxDistance * maxDistance)
|
||||
{
|
||||
ResetTarget();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1365,12 +1381,17 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (closestDist > shootDistance * shootDistance)
|
||||
{
|
||||
// Not close enough to shoot.
|
||||
currentTarget = null;
|
||||
closestEnemy = null;
|
||||
targetPos = null;
|
||||
aiFindTargetTimer = CrewAIFindTargetMinInverval;
|
||||
ResetTarget();
|
||||
}
|
||||
}
|
||||
void ResetTarget()
|
||||
{
|
||||
// Not close enough to shoot.
|
||||
currentTarget = null;
|
||||
closestEnemy = null;
|
||||
targetPos = null;
|
||||
}
|
||||
}
|
||||
else if (targetPos == null && item.Submarine != null && Level.Loaded != null)
|
||||
{
|
||||
@@ -1520,10 +1541,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
character.SetInput(InputType.Shoot, true, true);
|
||||
}
|
||||
aiTargetingGraceTimer = 5f;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsTargetItemCloseEnough(Item target, float sqrDist) => float.IsPositiveInfinity(target.Prefab.AITurretTargetingMaxDistance) || sqrDist < MathUtils.Pow2(target.Prefab.AITurretTargetingMaxDistance);
|
||||
|
||||
/// <summary>
|
||||
/// Turret doesn't consume grid power, directly takes from the batteries on its grid instead.
|
||||
/// </summary>
|
||||
@@ -1553,6 +1575,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (targetItem.ParentInventory != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1568,6 +1594,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (item.Submarine != null)
|
||||
{
|
||||
if (item.Submarine.Info.IsOutpost) { return false; }
|
||||
// Check that the target is not in the friendly team, e.g. pirate or a hostile player sub (PvP).
|
||||
return !target.IsOnFriendlyTeam(item.Submarine.TeamID) && TargetHumans;
|
||||
}
|
||||
@@ -1660,7 +1687,7 @@ namespace Barotrauma.Items.Components
|
||||
return angle >= minRotation && angle <= maxRotation;
|
||||
}
|
||||
|
||||
private bool CheckTurretAngle(Vector2 target) => CheckTurretAngle(-MathUtils.VectorToAngle(target - item.WorldPosition));
|
||||
public bool CheckTurretAngle(Vector2 target) => CheckTurretAngle(-MathUtils.VectorToAngle(target - item.WorldPosition));
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
|
||||
@@ -288,7 +288,7 @@ namespace Barotrauma.Items.Components
|
||||
public bool AutoEquipWhenFull { get; private set; }
|
||||
public bool DisplayContainedStatus { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can the item be used (assuming it has components that are usable in some way) when worn."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can the item be used (assuming it has components that are usable in some way) when worn.")]
|
||||
public bool AllowUseWhenWorn { get; set; }
|
||||
|
||||
public readonly int Variants;
|
||||
|
||||
@@ -742,13 +742,13 @@ namespace Barotrauma
|
||||
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));
|
||||
existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(), user, CharacterInventory.AnySlot, createNetworkEvent));
|
||||
}
|
||||
else
|
||||
{
|
||||
swapSuccessful =
|
||||
(existingItems.All(existingItem => otherInventory.TryPutItem(existingItem, otherIndex, false, false, user, createNetworkEvent)) ||
|
||||
existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(), user, CharacterInventory.anySlot, createNetworkEvent))
|
||||
existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(), user, CharacterInventory.AnySlot, createNetworkEvent))
|
||||
&&
|
||||
stackedItems.Distinct().All(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent));
|
||||
|
||||
|
||||
@@ -651,11 +651,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
public bool AllowStealing
|
||||
private bool allowStealing;
|
||||
[Serialize(true, IsPropertySaveable.Yes, alwaysUseInstanceValues: true,
|
||||
description: $"Determined by where/how the item originally spawned. If ItemPrefab.AllowStealing is true, stealing the item is always allowed.")]
|
||||
public bool AllowStealing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get { return allowStealing || Prefab.AllowStealingAlways; }
|
||||
set { allowStealing = value; }
|
||||
}
|
||||
|
||||
private string originalOutpost;
|
||||
@@ -1585,6 +1587,17 @@ namespace Barotrauma
|
||||
return tags.Contains(tag) || base.Prefab.Tags.Contains(tag);
|
||||
}
|
||||
|
||||
public bool HasIdentifierOrTags(IEnumerable<Identifier> identifiersOrTags)
|
||||
{
|
||||
if (identifiersOrTags == null) { return false; }
|
||||
if (identifiersOrTags.Contains(Prefab.Identifier)) { return true; }
|
||||
foreach (Identifier tag in identifiersOrTags)
|
||||
{
|
||||
if (HasTag(tag)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ReplaceTag(string tag, string newTag)
|
||||
{
|
||||
ReplaceTag(tag.ToIdentifier(), newTag.ToIdentifier());
|
||||
@@ -1756,7 +1769,7 @@ namespace Barotrauma
|
||||
return new AttackResult(damageAmount, null);
|
||||
}
|
||||
|
||||
private void SetCondition(float value, bool isNetworkEvent)
|
||||
private void SetCondition(float value, bool isNetworkEvent, bool executeEffects = true)
|
||||
{
|
||||
if (!isNetworkEvent)
|
||||
{
|
||||
@@ -1779,16 +1792,22 @@ namespace Barotrauma
|
||||
//Flag connections to be updated as device is broken
|
||||
flagChangedConnections(connections);
|
||||
#if CLIENT
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
ic.PlaySound(ActionType.OnBroken);
|
||||
ic.StopSounds(ActionType.OnActive);
|
||||
if (executeEffects)
|
||||
{
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
ic.PlaySound(ActionType.OnBroken);
|
||||
ic.StopSounds(ActionType.OnActive);
|
||||
}
|
||||
}
|
||||
if (Screen.Selected == GameMain.SubEditorScreen) { return; }
|
||||
#endif
|
||||
// Have to set the previous condition here or OnBroken status effects that reduce the condition will keep triggering the status effects, resulting in a stack overflow.
|
||||
SetPreviousCondition();
|
||||
ApplyStatusEffects(ActionType.OnBroken, 1.0f, null);
|
||||
if (executeEffects)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnBroken, 1.0f, null);
|
||||
}
|
||||
}
|
||||
else if (condition > 0.0f && prevCondition <= 0.0f)
|
||||
{
|
||||
@@ -1872,15 +1891,15 @@ namespace Barotrauma
|
||||
if (!(GameMain.NetworkMember is { IsServer: true })) { return; }
|
||||
if (!conditionUpdatePending) { return; }
|
||||
|
||||
CreateStatusEvent();
|
||||
CreateStatusEvent(loadingRound: false);
|
||||
lastSentCondition = condition;
|
||||
sendConditionUpdateTimer = NetConfig.ItemConditionUpdateInterval;
|
||||
conditionUpdatePending = false;
|
||||
}
|
||||
|
||||
public void CreateStatusEvent()
|
||||
public void CreateStatusEvent(bool loadingRound)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new ItemStatusEventData());
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new ItemStatusEventData(loadingRound));
|
||||
}
|
||||
|
||||
private bool isActive = true;
|
||||
|
||||
@@ -71,9 +71,9 @@ namespace Barotrauma
|
||||
{
|
||||
public EventType EventType => EventType.ItemStat;
|
||||
|
||||
public readonly Dictionary<ItemStatManager.TalentStatIdentifier, float> Stats;
|
||||
public readonly Dictionary<TalentStatIdentifier, float> Stats;
|
||||
|
||||
public SetItemStatEventData(Dictionary<ItemStatManager.TalentStatIdentifier, float> stats)
|
||||
public SetItemStatEventData(Dictionary<TalentStatIdentifier, float> stats)
|
||||
{
|
||||
Stats = stats;
|
||||
}
|
||||
@@ -82,6 +82,13 @@ namespace Barotrauma
|
||||
private readonly struct ItemStatusEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Status;
|
||||
|
||||
public readonly bool LoadingRound;
|
||||
|
||||
public ItemStatusEventData(bool loadingRound)
|
||||
{
|
||||
LoadingRound = loadingRound;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct AssignCampaignInteractionEventData : IEventData
|
||||
|
||||
@@ -332,6 +332,8 @@ namespace Barotrauma
|
||||
public readonly bool TransferOnlyOnePerContainer;
|
||||
public readonly bool AllowTransfersHere = true;
|
||||
|
||||
public readonly float MinLevelDifficulty, MaxLevelDifficulty;
|
||||
|
||||
public PreferredContainer(XElement element)
|
||||
{
|
||||
Primary = XMLExtensions.GetAttributeIdentifierArray(element, "primary", Array.Empty<Identifier>()).ToImmutableHashSet();
|
||||
@@ -347,6 +349,9 @@ namespace Barotrauma
|
||||
TransferOnlyOnePerContainer = element.GetAttributeBool("TransferOnlyOnePerContainer", TransferOnlyOnePerContainer);
|
||||
AllowTransfersHere = element.GetAttributeBool("AllowTransfersHere", AllowTransfersHere);
|
||||
|
||||
MinLevelDifficulty = element.GetAttributeFloat(nameof(MinLevelDifficulty), float.MinValue);
|
||||
MaxLevelDifficulty = element.GetAttributeFloat(nameof(MaxLevelDifficulty), float.MaxValue);
|
||||
|
||||
if (element.GetAttribute("spawnprobability") == null)
|
||||
{
|
||||
//if spawn probability is not defined but amount is, assume the probability is 1
|
||||
@@ -670,6 +675,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool AllowSellingWhenBroken { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool AllowStealingAlways { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool Indestructible { get; private set; }
|
||||
|
||||
@@ -806,6 +814,9 @@ namespace Barotrauma
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "How much the bots prioritize shooting this item with slow turrets, like railguns? Defaults to 1. Not used if AITurretPriority is 0. Distance to the target affects the decision making.")]
|
||||
public float AISlowTurretPriority { get; private set; }
|
||||
|
||||
[Serialize(float.PositiveInfinity, IsPropertySaveable.No, description: "The max distance at which the bots are allowed to target the items. Defaults to infinity.")]
|
||||
public float AITurretTargetingMaxDistance { get; private set; }
|
||||
|
||||
protected override Identifier DetermineIdentifier(XElement element)
|
||||
{
|
||||
Identifier identifier = base.DetermineIdentifier(element);
|
||||
|
||||
@@ -5,29 +5,48 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[NetworkSerialize]
|
||||
internal readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, Option<UInt32> UniqueCharacterId) : INetSerializableStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// Stackable identifiers feature a unique ID to allow multiple stats applied by the same talent from different characters to coexist.
|
||||
/// </summary>
|
||||
public static TalentStatIdentifier CreateStackable(ItemTalentStats stat, Identifier talentIdentifier, UInt32 characterId)
|
||||
=> new(stat, talentIdentifier, Option<UInt32>.Some(characterId));
|
||||
|
||||
/// <summary>
|
||||
/// Unstackable identifiers do not have a unique ID causing them to be identical to other stats applied by the same talent from different characters and thus only one of them will be applied.
|
||||
/// <see cref="ItemStatManager.ApplyStat"/> will always use the highest value for unstackable stats.
|
||||
/// </summary>
|
||||
public static TalentStatIdentifier CreateUnstackable(ItemTalentStats stat, Identifier talentIdentifier)
|
||||
=> new(stat, talentIdentifier, Option.None);
|
||||
}
|
||||
|
||||
internal sealed class ItemStatManager
|
||||
{
|
||||
private Item item;
|
||||
|
||||
public ItemStatManager(Item item)
|
||||
{
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
public readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, UInt32 CharacterID) : INetSerializableStruct;
|
||||
|
||||
private readonly Dictionary<TalentStatIdentifier, float> talentStats = new();
|
||||
private readonly Item item;
|
||||
|
||||
public void ApplyStat(ItemTalentStats stat, float value, CharacterTalent talent)
|
||||
public ItemStatManager(Item item) => this.item = item;
|
||||
|
||||
public void ApplyStat(ItemTalentStats stat, bool stackable, float value, CharacterTalent talent)
|
||||
{
|
||||
if (talent.Character?.ID is not { } characterId ||
|
||||
talent.Prefab?.Identifier is not { } talentIdentifier)
|
||||
talent.Prefab?.Identifier is not { } talentIdentifier) { return; }
|
||||
|
||||
var identifier = stackable
|
||||
? TalentStatIdentifier.CreateStackable(stat, talentIdentifier, characterId)
|
||||
: TalentStatIdentifier.CreateUnstackable(stat, talentIdentifier);
|
||||
|
||||
if (!stackable)
|
||||
{
|
||||
return;
|
||||
if (talentStats.TryGetValue(identifier, out float existingValue))
|
||||
{
|
||||
// Always use the highest value for non-stackable stats
|
||||
if (existingValue > value) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
TalentStatIdentifier identifier = new TalentStatIdentifier(stat, talentIdentifier, characterId);
|
||||
talentStats[identifier] = value;
|
||||
|
||||
#if SERVER
|
||||
@@ -38,21 +57,19 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
// Used for getting the value value from network packet
|
||||
public void ApplyStat(TalentStatIdentifier identifier, float value)
|
||||
{
|
||||
talentStats[identifier] = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Used for setting the value value from network packet; bypassing all validity checks.
|
||||
/// </summary>
|
||||
public void ApplyStatDirect(TalentStatIdentifier identifier, float value) => talentStats[identifier] = value;
|
||||
|
||||
public float GetAdjustedValue(ItemTalentStats stat, float originalValue)
|
||||
{
|
||||
float total = originalValue;
|
||||
|
||||
foreach (var (key, value) in talentStats)
|
||||
{
|
||||
if (key.Stat == stat)
|
||||
{
|
||||
total *= value;
|
||||
}
|
||||
if (key.Stat != stat) { continue; }
|
||||
total *= value;
|
||||
}
|
||||
|
||||
return total;
|
||||
|
||||
@@ -8,44 +8,85 @@ using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by various features to define different kinds of relations between items:
|
||||
/// for example, which item a character must have equipped to interact with some item in some way,
|
||||
/// which items can go inside a container, or which kind of item the target of a status effect must have for the effect to execute.
|
||||
/// </summary>
|
||||
class RelatedItem
|
||||
{
|
||||
public enum RelationType
|
||||
{
|
||||
None,
|
||||
/// <summary>
|
||||
/// The item must be contained inside the item this relation is defined in.
|
||||
/// Can for example by used to make an item usable only when there's a specific kind of item inside it.
|
||||
/// </summary>
|
||||
Contained,
|
||||
/// <summary>
|
||||
/// The user must have equipped the item (i.e. held or worn).
|
||||
/// </summary>
|
||||
Equipped,
|
||||
/// <summary>
|
||||
/// The user must have picked up the item (i.e. the item needs to be in the user's inventory).
|
||||
/// </summary>
|
||||
Picked,
|
||||
/// <summary>
|
||||
/// The item this relation is defined in must be inside a specific kind of container.
|
||||
/// Can for example by used to make an item do something when it's inside some other type of item.
|
||||
/// </summary>
|
||||
Container
|
||||
}
|
||||
|
||||
public bool IsOptional { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should an empty inventory be considered valid? Can be used to, for example, make an item do something if there's a specific item, or nothing, inside it.
|
||||
/// </summary>
|
||||
public bool MatchOnEmpty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should only an empty inventory be considered valid? Can be used to, for example, make an item do something when there's nothing inside it.
|
||||
/// </summary>
|
||||
public bool RequireEmpty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only valid for the RequiredItems of an ItemComponent. Can be used to ignore the requirement in the submarine editor,
|
||||
/// making it easier to for example make rewire things that require some special tool to rewire.
|
||||
/// </summary>
|
||||
public bool IgnoreInEditor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Identifier(s) or tag(s) of the items that are NOT considered valid.
|
||||
/// Can be used to, for example, exclude some specific items when using tags that apply to multiple items.
|
||||
/// </summary>
|
||||
public ImmutableHashSet<Identifier> ExcludedIdentifiers { get; private set; }
|
||||
|
||||
private RelationType type;
|
||||
|
||||
public List<StatusEffect> statusEffects;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Only valid for the RequiredItems of an ItemComponent. A message displayed if the required item isn't found (e.g. a notification about lack of ammo or fuel).
|
||||
/// </summary>
|
||||
public LocalizedString Msg;
|
||||
|
||||
/// <summary>
|
||||
/// Only valid for the RequiredItems of an ItemComponent. The localization tag of a message displayed if the required item isn't found (e.g. a notification about lack of ammo or fuel).
|
||||
/// </summary>
|
||||
public Identifier MsgTag;
|
||||
|
||||
/// <summary>
|
||||
/// Should broken (0 condition) items be excluded
|
||||
/// Should broken (0 condition) items be excluded?
|
||||
/// </summary>
|
||||
public bool ExcludeBroken { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should full condition (100%) items be excluded
|
||||
/// Should full condition (100%) items be excluded?
|
||||
/// </summary>
|
||||
public bool ExcludeFullCondition { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Are item variants considered valid?
|
||||
/// </summary>
|
||||
public bool AllowVariants { get; private set; } = true;
|
||||
|
||||
public RelationType Type
|
||||
@@ -59,19 +100,34 @@ namespace Barotrauma
|
||||
public int TargetSlot = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the position defined in ItemContainer.
|
||||
/// Overrides the position defined in ItemContainer. Only valid when used in the Containable definitions of an ItemContainer.
|
||||
/// </summary>
|
||||
public Vector2? ItemPos;
|
||||
|
||||
/// <summary>
|
||||
/// Only affects when ItemContainer.hideItems is false. Doesn't override the value.
|
||||
/// Only valid when used in the Containable definitions of an ItemContainer.
|
||||
/// Only affects when ItemContainer.hideItems is false. Doesn't override the value.
|
||||
/// </summary>
|
||||
public bool Hide;
|
||||
|
||||
/// <summary>
|
||||
/// Only valid when used in the Containable definitions of an ItemContainer.
|
||||
/// Can be used to override the rotation of specific items in the container.
|
||||
/// </summary>
|
||||
public float Rotation;
|
||||
|
||||
/// <summary>
|
||||
/// Only valid when used in the Containable definitions of an ItemContainer.
|
||||
/// Can be used to force specific items to stay active inside the container (such as flashlights attached to a gun).
|
||||
/// </summary>
|
||||
public bool SetActive;
|
||||
|
||||
/// <summary>
|
||||
/// Only valid for the RequiredItems of an ItemComponent. Can be used to make the requirement optional,
|
||||
/// meaning that you don't need to have the item to interact with something, but having it may still affect what the interaction does (such as using a crowbar on a door).
|
||||
/// </summary>
|
||||
public bool IsOptional { get; set; }
|
||||
|
||||
public string JoinedIdentifiers
|
||||
{
|
||||
get { return string.Join(",", Identifiers); }
|
||||
@@ -83,6 +139,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifier(s) or tag(s) of the items that are considered valid.
|
||||
/// </summary>
|
||||
public ImmutableHashSet<Identifier> Identifiers { get; private set; }
|
||||
|
||||
public string JoinedExcludedIdentifiers
|
||||
|
||||
Reference in New Issue
Block a user