v1.0.13.1 (first post-1.0 patch)

This commit is contained in:
Regalis11
2023-05-10 15:07:17 +03:00
parent 96fb49ba14
commit ee1db852b1
272 changed files with 5738 additions and 2413 deletions
@@ -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;