Build 1.1.4.0
This commit is contained in:
@@ -507,9 +507,9 @@ namespace Barotrauma.Items.Components
|
||||
wire.RemoveConnection(DockingTarget.item);
|
||||
|
||||
powerConnection.TryAddLink(wire);
|
||||
wire.Connect(powerConnection, false, false);
|
||||
wire.TryConnect(powerConnection, addNode: false);
|
||||
recipient.TryAddLink(wire);
|
||||
wire.Connect(recipient, false, false);
|
||||
wire.TryConnect(recipient, addNode: false);
|
||||
|
||||
//Flag connections to be updated
|
||||
Powered.ChangedConnections.Add(powerConnection);
|
||||
@@ -543,8 +543,8 @@ namespace Barotrauma.Items.Components
|
||||
System.Diagnostics.Debug.Assert(doorBody == null);
|
||||
|
||||
doorBody = GameMain.World.CreateRectangle(
|
||||
DockingTarget.Door.Body.width,
|
||||
DockingTarget.Door.Body.height,
|
||||
DockingTarget.Door.Body.Width,
|
||||
DockingTarget.Door.Body.Height,
|
||||
1.0f,
|
||||
position);
|
||||
doorBody.UserData = DockingTarget.Door;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FarseerPhysics.Dynamics;
|
||||
#if CLIENT
|
||||
using Barotrauma.Lights;
|
||||
#endif
|
||||
@@ -13,6 +14,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Door : Pickable, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
private static readonly HashSet<Door> doorList = new HashSet<Door>();
|
||||
|
||||
public static IReadOnlyCollection<Door> DoorList { get { return doorList; } }
|
||||
|
||||
private Gap linkedGap;
|
||||
private bool isOpen;
|
||||
|
||||
@@ -89,6 +94,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public PhysicsBody Body { get; private set; }
|
||||
|
||||
//the fixture that's part of the submarine's collider (= fixture that things outside the sub can collide with if the door is outside hulls)
|
||||
public Fixture OutsideSubmarineFixture;
|
||||
|
||||
private float RepairThreshold
|
||||
{
|
||||
get { return item.GetComponent<Repairable>() == null ? 0.0f : item.MaxCondition; }
|
||||
@@ -162,9 +170,14 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
isOpen = value;
|
||||
OpenState = (isOpen) ? 1.0f : 0.0f;
|
||||
OpenState = isOpen ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
public bool IsClosed => !IsOpen;
|
||||
|
||||
public bool IsFullyOpen => IsOpen && OpenState >= 1.0f;
|
||||
|
||||
public bool IsFullyClosed => IsClosed && OpenState <= 0f;
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "If the door has integrated buttons, it can be opened by interacting with it directly (instead of using buttons wired to it).")]
|
||||
public bool HasIntegratedButtons { get; private set; }
|
||||
@@ -226,6 +239,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
IsActive = true;
|
||||
doorList.Add(this);
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
@@ -394,11 +408,20 @@ namespace Barotrauma.Items.Components
|
||||
if (isClosing)
|
||||
{
|
||||
if (OpenState < 0.9f) { PushCharactersAway(); }
|
||||
if (CheckSubmarinesInDoorWay())
|
||||
{
|
||||
PredictedState = null;
|
||||
isOpen = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool wasEnabled = Body.Enabled;
|
||||
Body.Enabled = Impassable || openState < 1.0f;
|
||||
if (OutsideSubmarineFixture != null)
|
||||
{
|
||||
OutsideSubmarineFixture.CollidesWith = Body.Enabled ? SubmarineBody.CollidesWith : Category.None;
|
||||
}
|
||||
if (wasEnabled && !Body.Enabled && IsHorizontal)
|
||||
{
|
||||
//when opening a hatch, force characters above it to refresh the floor position
|
||||
@@ -442,6 +465,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
PushCharactersAway();
|
||||
}
|
||||
if (OutsideSubmarineFixture != null && Body.Enabled)
|
||||
{
|
||||
OutsideSubmarineFixture.CollidesWith = SubmarineBody.CollidesWith;
|
||||
}
|
||||
#if CLIENT
|
||||
UpdateConvexHulls();
|
||||
#endif
|
||||
@@ -462,10 +489,16 @@ namespace Barotrauma.Items.Components
|
||||
ce = ce.Next;
|
||||
}
|
||||
}
|
||||
|
||||
if (OutsideSubmarineFixture != null)
|
||||
{
|
||||
OutsideSubmarineFixture.CollidesWith = Category.None;
|
||||
}
|
||||
if (linkedGap != null)
|
||||
{
|
||||
linkedGap.Open = 1.0f;
|
||||
}
|
||||
|
||||
IsOpen = false;
|
||||
#if CLIENT
|
||||
if (convexHull != null) { convexHull.Enabled = false; }
|
||||
@@ -488,11 +521,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
RefreshLinkedGap();
|
||||
#if CLIENT
|
||||
Vector2[] corners = GetConvexHullCorners(Rectangle.Empty);
|
||||
|
||||
convexHull = new ConvexHull(corners, Color.Black, item);
|
||||
if (Window != Rectangle.Empty) convexHull2 = new ConvexHull(corners, Color.Black, item);
|
||||
|
||||
convexHull = new ConvexHull(Rectangle.Empty, !IsHorizontal, item);
|
||||
if (Window != Rectangle.Empty) { convexHull2 = new ConvexHull(Rectangle.Empty, !IsHorizontal, item); }
|
||||
UpdateConvexHulls();
|
||||
#endif
|
||||
}
|
||||
@@ -540,6 +570,36 @@ namespace Barotrauma.Items.Components
|
||||
convexHull?.Remove();
|
||||
convexHull2?.Remove();
|
||||
#endif
|
||||
|
||||
doorList.Remove(this);
|
||||
}
|
||||
|
||||
private bool CheckSubmarinesInDoorWay()
|
||||
{
|
||||
if (linkedGap != null && linkedGap.IsRoomToRoom) { return false; }
|
||||
|
||||
Rectangle doorRect = item.WorldRect;
|
||||
if (IsHorizontal)
|
||||
{
|
||||
doorRect.Width = (int)(item.Rect.Width * (1.0f - openState));
|
||||
}
|
||||
else
|
||||
{
|
||||
doorRect.Height = (int)(item.Rect.Height * (1.0f - openState));
|
||||
}
|
||||
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub == item.Submarine || sub.DockedTo.Contains(item.Submarine)) { continue; }
|
||||
Rectangle worldBorders = sub.Borders;
|
||||
worldBorders.Location += sub.WorldPosition.ToPoint();
|
||||
if (!Submarine.RectsOverlap(worldBorders, doorRect)) { continue; }
|
||||
foreach (Hull hull in sub.GetHulls(alsoFromConnectedSubs: false))
|
||||
{
|
||||
if (Submarine.RectsOverlap(hull.WorldRect, doorRect)) { return true; }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool itemPosErrorShown;
|
||||
@@ -563,7 +623,6 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 currSize = IsHorizontal ?
|
||||
new Vector2(item.Rect.Width * (1.0f - openState), doorSprite.size.Y * item.Scale) :
|
||||
new Vector2(doorSprite.size.X * item.Scale, item.Rect.Height * (1.0f - openState));
|
||||
|
||||
Vector2 simSize = ConvertUnits.ToSimUnits(currSize);
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-6
@@ -67,11 +67,18 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(true, IsPropertySaveable.Yes, "")]
|
||||
public bool CanSpawn { get; set; } = true;
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, "")]
|
||||
public bool PreloadCharacter { get; set; }
|
||||
|
||||
private float spawnTimer;
|
||||
private float? spawnTimerGoal;
|
||||
|
||||
private int spawnedAmount = 0;
|
||||
|
||||
private Character? preloadedCharacter;
|
||||
|
||||
private bool preloadInitiated;
|
||||
|
||||
public EntitySpawnerComponent(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
@@ -103,12 +110,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.OnItemLoaded();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (PreloadCharacter && !Screen.Selected.IsEditor && !preloadInitiated)
|
||||
{
|
||||
SpawnCharacter(Vector2.Zero, onSpawn: (Character c) =>
|
||||
{
|
||||
preloadedCharacter = c;
|
||||
c.DisabledByEvent = true;
|
||||
});
|
||||
preloadInitiated = true;
|
||||
return;
|
||||
}
|
||||
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
item.SendSignal(CanSpawn ? "1" : "0", "state_out");
|
||||
@@ -269,10 +285,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SpeciesName))
|
||||
{
|
||||
Identifier[] allSpecies = SpeciesName.Split(',').Select(s => s.Trim()).ToIdentifiers().ToArray();
|
||||
Identifier species = allSpecies.GetRandomUnsynced();
|
||||
Entity.Spawner?.AddCharacterToSpawnQueue(species, pos);
|
||||
spawnedAmount++;
|
||||
if (preloadedCharacter != null)
|
||||
{
|
||||
preloadedCharacter.DisabledByEvent = false;
|
||||
preloadedCharacter.TeleportTo(pos);
|
||||
preloadedCharacter = null;
|
||||
spawnedAmount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
SpawnCharacter(pos);
|
||||
spawnedAmount++;
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(ItemIdentifier))
|
||||
{
|
||||
@@ -291,5 +315,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnCharacter(Vector2 pos, Action<Character>? onSpawn = null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SpeciesName))
|
||||
{
|
||||
Identifier[] allSpecies = SpeciesName.Split(',').Select(s => s.Trim()).ToIdentifiers().ToArray();
|
||||
Identifier species = allSpecies.GetRandomUnsynced();
|
||||
Entity.Spawner?.AddCharacterToSpawnQueue(species, pos, onSpawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,7 +226,7 @@ namespace Barotrauma.Items.Components
|
||||
Pusher = null;
|
||||
if (element.GetAttributeBool("blocksplayers", false))
|
||||
{
|
||||
Pusher = new PhysicsBody(item.body.width, item.body.height, item.body.radius,
|
||||
Pusher = new PhysicsBody(item.body.Width, item.body.Height, item.body.Radius,
|
||||
item.body.Density,
|
||||
BodyType.Dynamic,
|
||||
Physics.CollisionItemBlocking,
|
||||
@@ -427,10 +427,11 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
//cannot hold and wear an item at the same time
|
||||
//(unless the slot in which it's held and worn are equal - e.g. a suit with built-in tool or weapon on one hand)
|
||||
var wearable = item.GetComponent<Wearable>();
|
||||
if (wearable != null)
|
||||
if (wearable != null && !wearable.AllowedSlots.SequenceEqual(allowedSlots))
|
||||
{
|
||||
//cannot hold and wear an item at the same time
|
||||
wearable.Unequip(character);
|
||||
}
|
||||
|
||||
@@ -558,10 +559,16 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool OnPicked(Character picker)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
if (!picker.Inventory.CanBeAutoMovedToCorrectSlots(item))
|
||||
{
|
||||
picker.Inventory.FlashAllowedSlots(item, Color.Red);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
bool wasAttached = IsAttached;
|
||||
if (base.OnPicked(picker))
|
||||
{
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
trigger = new PhysicsBody(body.width, body.height, body.radius,
|
||||
trigger = new PhysicsBody(body.Width, body.Height, body.Radius,
|
||||
body.Density,
|
||||
BodyType.Static,
|
||||
Physics.CollisionWall,
|
||||
|
||||
@@ -445,7 +445,7 @@ namespace Barotrauma.Items.Components
|
||||
targetItem.Condition / targetItem.MaxCondition,
|
||||
emptyColor: GUIStyle.HealthBarColorLow,
|
||||
fullColor: GUIStyle.HealthBarColorHigh,
|
||||
textTag: targetItem.Name);
|
||||
textTag: targetItem.Prefab.ShowNameInHealthBar ? targetItem.Name : string.Empty);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+36
-32
@@ -5,9 +5,7 @@ using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -98,6 +96,9 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
private readonly IReadOnlySet<Identifier> suitableProjectiles;
|
||||
|
||||
|
||||
private enum ChargingState
|
||||
{
|
||||
Inactive,
|
||||
@@ -130,12 +131,11 @@ namespace Barotrauma.Items.Components
|
||||
// TODO: should define this in xml if we have ranged weapons that don't require aim to use
|
||||
item.RequireAimToUse = true;
|
||||
characterUsable = true;
|
||||
|
||||
suitableProjectiles = element.GetAttributeIdentifierArray(nameof(suitableProjectiles), Array.Empty<Identifier>()).ToHashSet();
|
||||
if (ReloadSkillRequirement > 0 && ReloadNoSkill <= reload)
|
||||
{
|
||||
DebugConsole.AddWarning($"Invalid XML at {item.Name}: ReloadNoSkill is lower or equal than it's reload skill, despite having ReloadSkillRequirement.");
|
||||
}
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -143,7 +143,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
ReloadTimer = Math.Min(reload, 1.0f);
|
||||
//clamp above 1 to prevent rapid-firing by swapping weapons
|
||||
ReloadTimer = Math.Max(Math.Min(reload, 1.0f), ReloadTimer);
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
@@ -259,7 +260,8 @@ 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) * Rand.Range(-0.5f, 0.5f);
|
||||
float spread = GetSpread(character) * Projectile.GetSpreadFromPool();
|
||||
|
||||
var lastProjectile = LastProjectile;
|
||||
if (lastProjectile != projectile)
|
||||
{
|
||||
@@ -275,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 * Rand.Range(-10.0f, 10.0f));
|
||||
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * 20.0f * Projectile.GetSpreadFromPool());
|
||||
}
|
||||
Item.RemoveContained(projectile.Item);
|
||||
}
|
||||
@@ -294,39 +296,41 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public Projectile FindProjectile(bool triggerOnUseOnContainers = false)
|
||||
{
|
||||
var containedItems = item.OwnInventory?.AllItemsMod;
|
||||
if (containedItems == null) { return null; }
|
||||
|
||||
foreach (Item item in containedItems)
|
||||
foreach (ItemContainer container in item.GetComponents<ItemContainer>())
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
Projectile projectile = item.GetComponent<Projectile>();
|
||||
if (projectile != null) { return projectile; }
|
||||
}
|
||||
|
||||
//projectile not found, see if one of the contained items contains projectiles
|
||||
foreach (Item it in containedItems)
|
||||
{
|
||||
if (it == null) { continue; }
|
||||
var containedSubItems = it.OwnInventory?.AllItemsMod;
|
||||
if (containedSubItems == null) { continue; }
|
||||
foreach (Item subItem in containedSubItems)
|
||||
foreach (Item containedItem in container.Inventory.AllItemsMod)
|
||||
{
|
||||
if (subItem == null) { continue; }
|
||||
Projectile projectile = subItem.GetComponent<Projectile>();
|
||||
//apply OnUse statuseffects to the container in case it has to react to it somehow
|
||||
//(play a sound, spawn more projectiles, reduce condition...)
|
||||
if (triggerOnUseOnContainers && subItem.Condition > 0.0f)
|
||||
if (containedItem == null) { continue; }
|
||||
Projectile projectile = containedItem.GetComponent<Projectile>();
|
||||
if (IsSuitableProjectile(projectile)) { return projectile; }
|
||||
|
||||
//projectile not found, see if the contained item contains projectiles
|
||||
var containedSubItems = containedItem.OwnInventory?.AllItemsMod;
|
||||
if (containedSubItems == null) { continue; }
|
||||
foreach (Item subItem in containedSubItems)
|
||||
{
|
||||
subItem.GetComponent<ItemContainer>()?.Item.ApplyStatusEffects(ActionType.OnUse, 1.0f);
|
||||
}
|
||||
if (projectile != null) { return projectile; }
|
||||
if (subItem == null) { continue; }
|
||||
Projectile subProjectile = subItem.GetComponent<Projectile>();
|
||||
//apply OnUse statuseffects to the container in case it has to react to it somehow
|
||||
//(play a sound, spawn more projectiles, reduce condition...)
|
||||
if (triggerOnUseOnContainers && subItem.Condition > 0.0f)
|
||||
{
|
||||
subItem.GetComponent<ItemContainer>()?.Item.ApplyStatusEffects(ActionType.OnUse, 1.0f);
|
||||
}
|
||||
if (IsSuitableProjectile(subProjectile)) { return subProjectile; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsSuitableProjectile(Projectile projectile)
|
||||
{
|
||||
if (projectile?.Item == null) { return false; }
|
||||
if (!suitableProjectiles.Any()) { return true; }
|
||||
return suitableProjectiles.Any(s => projectile.Item.Prefab.Identifier == s || projectile.Item.HasTag(s));
|
||||
}
|
||||
|
||||
partial void LaunchProjSpecific();
|
||||
}
|
||||
class AbilityRangedWeapon : AbilityObject, IAbilityItem
|
||||
|
||||
@@ -100,6 +100,9 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can the item hit broken doors.")]
|
||||
public bool HitBrokenDoors { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Should the tool ignore characters? Enabled e.g. for fire extinguisher.")]
|
||||
public bool IgnoreCharacters { get; set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "The probability of starting a fire somewhere along the ray fired from the barrel (for example, 0.1 = 10% chance to start a fire during a second of use).")]
|
||||
public float FireProbability { get; set; }
|
||||
|
||||
@@ -313,7 +316,11 @@ namespace Barotrauma.Items.Components
|
||||
private readonly List<FireSource> fireSourcesInRange = new List<FireSource>();
|
||||
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
|
||||
{
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair;
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair;
|
||||
if (!IgnoreCharacters)
|
||||
{
|
||||
collisionCategories |= Physics.CollisionCharacter;
|
||||
}
|
||||
|
||||
//if the item can cut off limbs, activate nearby bodies to allow the raycast to hit them
|
||||
if (statusEffectLists != null)
|
||||
@@ -703,7 +710,7 @@ namespace Barotrauma.Items.Components
|
||||
private float repairTimer;
|
||||
private Gap previousGap;
|
||||
private readonly float repairTimeOut = 5;
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (!(objective.OperateTarget is Gap leak))
|
||||
{
|
||||
@@ -901,17 +908,16 @@ namespace Barotrauma.Items.Components
|
||||
// A general purpose system could be better, but it would most likely require changes in the way we define the status effects in xml.
|
||||
foreach (ISerializableEntity target in currentTargets)
|
||||
{
|
||||
if (!(target is Door door)) { continue; }
|
||||
if (target is not Door door) { continue; }
|
||||
if (!door.CanBeWelded || !door.Item.IsInteractable(user)) { continue; }
|
||||
for (int i = 0; i < effect.propertyNames.Length; i++)
|
||||
foreach (var propertyEffect in effect.PropertyEffects)
|
||||
{
|
||||
Identifier propertyName = effect.propertyNames[i];
|
||||
if (propertyName != "stuck") { continue; }
|
||||
if (door.SerializableProperties == null || !door.SerializableProperties.TryGetValue(propertyName, out SerializableProperty property)) { continue; }
|
||||
if (propertyEffect.propertyName != "stuck") { continue; }
|
||||
if (door.SerializableProperties == null || !door.SerializableProperties.TryGetValue(propertyEffect.propertyName, out SerializableProperty property)) { continue; }
|
||||
object value = property.GetValue(target);
|
||||
if (door.Stuck > 0)
|
||||
{
|
||||
bool isCutting = effect.propertyEffects[i].GetType() == typeof(float) && (float)effect.propertyEffects[i] < 0;
|
||||
bool isCutting = propertyEffect.value is float and < 0;
|
||||
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White,
|
||||
textTag: isCutting ? "progressbar.cutting" : "progressbar.welding");
|
||||
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public const float WaterDragCoefficient = 0.5f;
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
//actual throwing logic is handled in Update
|
||||
@@ -59,6 +61,7 @@ namespace Barotrauma.Items.Components
|
||||
base.Drop(dropper);
|
||||
throwState = ThrowState.None;
|
||||
throwAngle = ThrowAngleStart;
|
||||
Item.ResetWaterDragCoefficient();
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
@@ -97,6 +100,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform;
|
||||
midAir = false;
|
||||
Item.ResetWaterDragCoefficient();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -188,6 +192,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
item.Drop(CurrentThrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
|
||||
item.WaterDragCoefficient = WaterDragCoefficient;
|
||||
item.body.ApplyLinearImpulse(throwVector * ThrowForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
|
||||
//disable platform collisions until the item comes back to rest again
|
||||
|
||||
@@ -111,8 +111,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool drawable = true;
|
||||
|
||||
[Serialize(PropertyConditional.Comparison.And, IsPropertySaveable.No)]
|
||||
public PropertyConditional.Comparison IsActiveConditionalComparison
|
||||
#warning TODO: misnomer - should be IsActiveConditionalLogicalOperator
|
||||
[Serialize(PropertyConditional.LogicalOperatorType.And, IsPropertySaveable.No)]
|
||||
public PropertyConditional.LogicalOperatorType IsActiveConditionalComparison
|
||||
{
|
||||
get;
|
||||
set;
|
||||
@@ -245,17 +246,10 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(0, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
public int ManuallySelectedSound { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by status effects or conditionals to the speed of the item
|
||||
/// </summary>
|
||||
public float Speed
|
||||
{
|
||||
get
|
||||
{
|
||||
return item.Speed;
|
||||
}
|
||||
}
|
||||
public float Speed => item.Speed;
|
||||
|
||||
public readonly bool InheritStatusEffects;
|
||||
|
||||
@@ -346,14 +340,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
case "activeconditional":
|
||||
case "isactive":
|
||||
IsActiveConditionals = IsActiveConditionals ?? new List<PropertyConditional>();
|
||||
foreach (XAttribute attribute in subElement.Attributes())
|
||||
{
|
||||
if (PropertyConditional.IsValid(attribute))
|
||||
{
|
||||
IsActiveConditionals.Add(new PropertyConditional(attribute));
|
||||
}
|
||||
}
|
||||
IsActiveConditionals ??= new List<PropertyConditional>();
|
||||
IsActiveConditionals.AddRange(PropertyConditional.FromXElement(subElement));
|
||||
break;
|
||||
case "requireditem":
|
||||
case "requireditems":
|
||||
@@ -450,7 +438,7 @@ namespace Barotrauma.Items.Components
|
||||
public virtual void Drop(Character dropper) { }
|
||||
|
||||
/// <returns>true if the operation was completed</returns>
|
||||
public virtual bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
public virtual bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1033,7 +1021,7 @@ namespace Barotrauma.Items.Components
|
||||
prevRequiredItems[newRequiredItem.Type].Find(ri => ri.JoinedIdentifiers == newRequiredItem.JoinedIdentifiers) : null;
|
||||
if (prevRequiredItem != null)
|
||||
{
|
||||
newRequiredItem.statusEffects = prevRequiredItem.statusEffects;
|
||||
newRequiredItem.StatusEffects = prevRequiredItem.StatusEffects;
|
||||
newRequiredItem.Msg = prevRequiredItem.Msg;
|
||||
newRequiredItem.IsOptional = prevRequiredItem.IsOptional;
|
||||
newRequiredItem.IgnoreInEditor = prevRequiredItem.IgnoreInEditor;
|
||||
|
||||
@@ -12,20 +12,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ItemContainer : ItemComponent, IDrawableComponent
|
||||
{
|
||||
class ActiveContainedItem
|
||||
{
|
||||
public readonly Item Item;
|
||||
public readonly StatusEffect StatusEffect;
|
||||
public readonly bool ExcludeBroken;
|
||||
public readonly bool ExcludeFullCondition;
|
||||
public ActiveContainedItem(Item item, StatusEffect statusEffect, bool excludeBroken, bool excludeFullCondition)
|
||||
{
|
||||
Item = item;
|
||||
StatusEffect = statusEffect;
|
||||
ExcludeBroken = excludeBroken;
|
||||
ExcludeFullCondition = excludeFullCondition;
|
||||
}
|
||||
}
|
||||
readonly record struct ActiveContainedItem(Item Item, StatusEffect StatusEffect, bool ExcludeBroken, bool ExcludeFullCondition);
|
||||
|
||||
readonly record struct DrawableContainedItem(Item Item, bool Hide, Vector2? ItemPos, float Rotation);
|
||||
|
||||
class SlotRestrictions
|
||||
{
|
||||
@@ -63,7 +52,9 @@ namespace Barotrauma.Items.Components
|
||||
public readonly ItemInventory Inventory;
|
||||
|
||||
private readonly List<ActiveContainedItem> activeContainedItems = new List<ActiveContainedItem>();
|
||||
|
||||
|
||||
private readonly List<DrawableContainedItem> drawableContainedItems = new List<DrawableContainedItem>();
|
||||
|
||||
private List<ushort>[] itemIds;
|
||||
|
||||
//how many items can be contained
|
||||
@@ -351,8 +342,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void OnItemContained(Item containedItem)
|
||||
{
|
||||
item.SetContainedItemPositions();
|
||||
|
||||
int index = Inventory.FindIndex(containedItem);
|
||||
if (index >= 0 && index < slotRestrictions.Length)
|
||||
{
|
||||
@@ -362,7 +351,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (var containableItem in slotRestrictions[index].ContainableItems)
|
||||
{
|
||||
if (!containableItem.MatchesItem(containedItem)) { continue; }
|
||||
foreach (StatusEffect effect in containableItem.statusEffects)
|
||||
foreach (StatusEffect effect in containableItem.StatusEffects)
|
||||
{
|
||||
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken, containableItem.ExcludeFullCondition));
|
||||
}
|
||||
@@ -370,6 +359,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
var relatedItem = FindContainableItem(containedItem);
|
||||
drawableContainedItems.RemoveAll(d => d.Item == containedItem);
|
||||
drawableContainedItems.Add(new DrawableContainedItem(containedItem,
|
||||
Hide: relatedItem?.Hide ?? false,
|
||||
ItemPos: relatedItem?.ItemPos,
|
||||
Rotation: relatedItem?.Rotation ?? 0.0f));
|
||||
drawableContainedItems.Sort((DrawableContainedItem it1, DrawableContainedItem it2) => Inventory.FindIndex(it1.Item).CompareTo(Inventory.FindIndex(it2.Item)));
|
||||
|
||||
if (item.GetComponent<Planter>() != null)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "null") + ":GardeningPlanted:" + containedItem.Prefab.Identifier);
|
||||
@@ -384,6 +381,7 @@ namespace Barotrauma.Items.Components
|
||||
// Set the contained items active if there's an item inserted inside the container. Enables e.g. the rifle flashlight when it's attached to the rifle (put inside of it).
|
||||
SetContainedActive(true);
|
||||
}
|
||||
item.SetContainedItemPositions();
|
||||
CharacterHUD.RecreateHudTextsIfFocused(item, containedItem);
|
||||
OnContainedItemsChanged.Invoke(this);
|
||||
}
|
||||
@@ -396,6 +394,7 @@ namespace Barotrauma.Items.Components
|
||||
public void OnItemRemoved(Item containedItem)
|
||||
{
|
||||
activeContainedItems.RemoveAll(i => i.Item == containedItem);
|
||||
drawableContainedItems.RemoveAll(i => i.Item == containedItem);
|
||||
//deactivate if the inventory is empty
|
||||
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
|
||||
CharacterHUD.RecreateHudTextsIfFocused(item, containedItem);
|
||||
@@ -483,11 +482,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (Item item in Inventory.AllItemsMod)
|
||||
{
|
||||
item.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, ownerCharacter);
|
||||
item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter);
|
||||
item.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, ownerCharacter, useTarget: ownerCharacter);
|
||||
item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter, useTarget: ownerCharacter);
|
||||
item.GetComponent<GeneticMaterial>()?.Equip(ownerCharacter);
|
||||
autoInjectCooldown = AutoInjectInterval;
|
||||
}
|
||||
autoInjectCooldown = AutoInjectInterval;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,10 +511,18 @@ namespace Barotrauma.Items.Components
|
||||
if (activeContainedItem.ExcludeFullCondition && contained.IsFullCondition) { continue; }
|
||||
StatusEffect effect = activeContainedItem.StatusEffect;
|
||||
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, item.AllPropertyObjects);
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
|
||||
{
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, contained.AllPropertyObjects);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Character) && item.ParentInventory?.Owner is Character character)
|
||||
{
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, character);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
@@ -759,54 +766,50 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
int i = 0;
|
||||
Vector2 currentItemPos = transformedItemPos;
|
||||
foreach (Item contained in Inventory.AllItems)
|
||||
foreach (DrawableContainedItem contained in drawableContainedItems)
|
||||
{
|
||||
Vector2 itemPos = currentItemPos;
|
||||
var relatedItem = FindContainableItem(contained);
|
||||
if (relatedItem != null)
|
||||
if (contained.ItemPos.HasValue)
|
||||
{
|
||||
if (relatedItem.ItemPos.HasValue)
|
||||
Vector2 pos = contained.ItemPos.Value;
|
||||
if (item.body != null)
|
||||
{
|
||||
Vector2 pos = relatedItem.ItemPos.Value;
|
||||
if (item.body != null)
|
||||
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
pos.X *= item.body.Dir;
|
||||
itemPos = Vector2.Transform(pos, transform) + item.body.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemPos = pos;
|
||||
// This code is aped based on above. Not tested.
|
||||
if (item.FlippedX)
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
pos.X *= item.body.Dir;
|
||||
itemPos = Vector2.Transform(pos, transform) + item.body.Position;
|
||||
itemPos.X = -itemPos.X;
|
||||
itemPos.X += item.Rect.Width;
|
||||
}
|
||||
else
|
||||
if (item.FlippedY)
|
||||
{
|
||||
itemPos = pos;
|
||||
// This code is aped based on above. Not tested.
|
||||
if (item.FlippedX)
|
||||
{
|
||||
itemPos.X = -itemPos.X;
|
||||
itemPos.X += item.Rect.Width;
|
||||
}
|
||||
if (item.FlippedY)
|
||||
{
|
||||
itemPos.Y = -itemPos.Y;
|
||||
itemPos.Y -= item.Rect.Height;
|
||||
}
|
||||
itemPos += new Vector2(item.Rect.X, item.Rect.Y);
|
||||
if (Math.Abs(item.RotationRad) > 0.01f)
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(item.RotationRad);
|
||||
itemPos = Vector2.Transform(itemPos - item.Position, transform) + item.Position;
|
||||
}
|
||||
itemPos.Y = -itemPos.Y;
|
||||
itemPos.Y -= item.Rect.Height;
|
||||
}
|
||||
itemPos += new Vector2(item.Rect.X, item.Rect.Y);
|
||||
if (Math.Abs(item.RotationRad) > 0.01f)
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(item.RotationRad);
|
||||
itemPos = Vector2.Transform(itemPos - item.Position, transform) + item.Position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (contained.body != null)
|
||||
if (contained.Item.body != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(itemPos);
|
||||
float rotation = itemRotation;
|
||||
if (relatedItem != null && relatedItem.Rotation != 0)
|
||||
if (contained.Rotation != 0)
|
||||
{
|
||||
rotation = MathHelper.ToRadians(relatedItem.Rotation);
|
||||
rotation = MathHelper.ToRadians(contained.Rotation);
|
||||
}
|
||||
if (item.body != null)
|
||||
{
|
||||
@@ -817,29 +820,29 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
rotation += -item.RotationRad;
|
||||
}
|
||||
contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, rotation);
|
||||
contained.body.SetPrevTransform(contained.body.SimPosition, contained.body.Rotation);
|
||||
contained.body.UpdateDrawPosition();
|
||||
contained.Item.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, rotation);
|
||||
contained.Item.body.SetPrevTransform(contained.Item.body.SimPosition, contained.Item.body.Rotation);
|
||||
contained.Item.body.UpdateDrawPosition();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.Log("SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace.CleanupStackTrace());
|
||||
GameAnalyticsManager.AddErrorEventOnce("ItemContainer.SetContainedItemPositions.InvalidPosition:" + contained.Name,
|
||||
GameAnalyticsManager.AddErrorEventOnce("ItemContainer.SetContainedItemPositions.InvalidPosition:" + contained.Item.Name,
|
||||
GameAnalyticsManager.ErrorSeverity.Error,
|
||||
"SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
contained.body.Submarine = item.Submarine;
|
||||
contained.Item.body.Submarine = item.Submarine;
|
||||
}
|
||||
|
||||
contained.Rect =
|
||||
contained.Item.Rect =
|
||||
new Rectangle(
|
||||
(int)(itemPos.X - contained.Rect.Width / 2.0f),
|
||||
(int)(itemPos.Y + contained.Rect.Height / 2.0f),
|
||||
contained.Rect.Width, contained.Rect.Height);
|
||||
(int)(itemPos.X - contained.Item.Rect.Width / 2.0f),
|
||||
(int)(itemPos.Y + contained.Item.Rect.Height / 2.0f),
|
||||
contained.Item.Rect.Width, contained.Item.Rect.Height);
|
||||
|
||||
contained.Submarine = item.Submarine;
|
||||
contained.CurrentHull = item.CurrentHull;
|
||||
contained.SetContainedItemPositions();
|
||||
contained.Item.Submarine = item.Submarine;
|
||||
contained.Item.CurrentHull = item.CurrentHull;
|
||||
contained.Item.SetContainedItemPositions();
|
||||
|
||||
i++;
|
||||
if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f)
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, MaxOverVoltageFactor);
|
||||
float currForce = force * voltageFactor;
|
||||
float condition = item.Condition / item.MaxCondition;
|
||||
float condition = item.MaxCondition <= 0.0f ? 0.0f : item.Condition / item.MaxCondition;
|
||||
// Broken engine makes more noise.
|
||||
float noise = Math.Abs(currForce) * MathHelper.Lerp(1.5f, 1f, condition);
|
||||
UpdateAITargets(noise);
|
||||
|
||||
@@ -267,7 +267,7 @@ namespace Barotrauma.Items.Components
|
||||
itemList.Enabled = true;
|
||||
if (amountInput != null)
|
||||
{
|
||||
amountInput.Enabled = true;
|
||||
amountInput.Enabled = amountTextMax.Enabled;
|
||||
}
|
||||
RefreshActivateButtonText();
|
||||
#endif
|
||||
|
||||
@@ -235,7 +235,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null) { return false; }
|
||||
|
||||
@@ -671,7 +671,7 @@ namespace Barotrauma.Items.Components
|
||||
return picker != null;
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
character.AIController.SteeringManager.Reset();
|
||||
|
||||
@@ -272,7 +272,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private static readonly Dictionary<string, List<Character>> targetGroups = new Dictionary<string, List<Character>>();
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (currentMode == Mode.Passive || !aiPingCheckPending) { return false; }
|
||||
|
||||
|
||||
@@ -720,7 +720,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
if (objective.Override)
|
||||
@@ -813,7 +813,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
sonar?.AIOperate(deltaTime, character, objective);
|
||||
sonar?.CrewAIOperate(deltaTime, character, objective);
|
||||
if (!MaintainPos && showIceSpireWarning && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogicespirespottedsonar").Value, null, 0.0f, "icespirespottedsonar".ToIdentifier(), 60.0f);
|
||||
|
||||
@@ -303,7 +303,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
@@ -13,6 +14,20 @@ 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();
|
||||
}
|
||||
|
||||
public static float GetSpreadFromPool()
|
||||
{
|
||||
return spreadPool[SpreadCounter];
|
||||
}
|
||||
|
||||
struct HitscanResult
|
||||
{
|
||||
public Fixture Fixture;
|
||||
@@ -41,10 +56,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public const float WaterDragCoefficient = 0.1f;
|
||||
|
||||
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
|
||||
|
||||
private bool removePending;
|
||||
|
||||
public static byte SpreadCounter { get; private set; }
|
||||
|
||||
//continuous collision detection is used while the projectile is moving faster than this
|
||||
const float ContinuousCollisionThreshold = 5.0f;
|
||||
|
||||
@@ -192,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;
|
||||
@@ -280,6 +299,8 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
SpreadCounter = (byte)(item.ID % SpreadCounterWrapAround);
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
partial void InitProjSpecific(ContentXElement element);
|
||||
@@ -292,13 +313,13 @@ namespace Barotrauma.Items.Components
|
||||
switch (item.body.BodyShape)
|
||||
{
|
||||
case PhysicsBody.Shape.Circle:
|
||||
Attack.DamageRange = item.body.radius;
|
||||
Attack.DamageRange = item.body.Radius;
|
||||
break;
|
||||
case PhysicsBody.Shape.Capsule:
|
||||
Attack.DamageRange = item.body.height / 2 + item.body.radius;
|
||||
Attack.DamageRange = item.body.Height / 2 + item.body.Radius;
|
||||
break;
|
||||
case PhysicsBody.Shape.Rectangle:
|
||||
Attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length();
|
||||
Attack.DamageRange = new Vector2(item.body.Width / 2.0f, item.body.Height / 2.0f).Length();
|
||||
break;
|
||||
}
|
||||
Attack.DamageRange = ConvertUnits.ToDisplayUnits(Attack.DamageRange);
|
||||
@@ -358,8 +379,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));
|
||||
launchRot = rotation;
|
||||
Item.CreateServerEvent(this, new EventData(launch: true, spreadCounter: (byte)(SpreadCounter - 1)));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -368,23 +389,23 @@ 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 * Rand.Range(-0.5f, 0.5f));
|
||||
launchAngle = initialRotation + MathHelper.ToRadians(Spread * GetSpreadFromPool());
|
||||
}
|
||||
SpreadCounter++;
|
||||
|
||||
Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle));
|
||||
if (Hitscan)
|
||||
@@ -401,8 +422,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.body.SetTransform(item.body.SimPosition, launchAngle);
|
||||
float modifiedLaunchImpulse = (LaunchImpulse + launchImpulseModifier) * (1 + Rand.Range(-ImpulseSpread, ImpulseSpread));
|
||||
DoLaunch(launchDir * modifiedLaunchImpulse * item.body.Mass);
|
||||
System.Diagnostics.Debug.WriteLine("launch: " + modifiedLaunchImpulse + " - " + item.body.LinearVelocity);
|
||||
DoLaunch(launchDir * modifiedLaunchImpulse);
|
||||
}
|
||||
}
|
||||
User = character;
|
||||
@@ -423,15 +443,26 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
item.Drop(null, createNetworkEvent: false);
|
||||
Item.WaterDragCoefficient = WaterDragCoefficient;
|
||||
|
||||
launchPos = item.SimPosition;
|
||||
|
||||
item.body.Enabled = true;
|
||||
item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.95f);
|
||||
if (item.body.BodyType == BodyType.Kinematic)
|
||||
{
|
||||
item.body.LinearVelocity = impulse;
|
||||
}
|
||||
else
|
||||
{
|
||||
impulse *= item.body.Mass;
|
||||
item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.95f);
|
||||
}
|
||||
|
||||
item.body.FarseerBody.OnCollision += OnProjectileCollision;
|
||||
item.body.FarseerBody.IsBullet = true;
|
||||
|
||||
EnableProjectileCollisions();
|
||||
|
||||
IsActive = true;
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
@@ -447,6 +478,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 simPositon = item.SimPosition;
|
||||
Vector2 rayStartWorld = item.WorldPosition;
|
||||
item.Drop(null);
|
||||
Item.WaterDragCoefficient = WaterDragCoefficient;
|
||||
|
||||
item.body.Enabled = true;
|
||||
//set the velocity of the body because the OnProjectileCollision method
|
||||
@@ -505,6 +537,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
var h = hits[i];
|
||||
item.SetTransform(h.Point, rotation);
|
||||
item.UpdateTransform();
|
||||
if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero))
|
||||
{
|
||||
hitCount++;
|
||||
@@ -560,6 +593,8 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
if (fixture.Body.UserData is VineTile) { return true; }
|
||||
if (fixture.CollidesWith == Category.None) { return true; }
|
||||
|
||||
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body.UserData is Hull || fixture.UserData is Hull) { return true; }
|
||||
|
||||
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
|
||||
@@ -611,6 +646,7 @@ namespace Barotrauma.Items.Components
|
||||
return -1;
|
||||
}
|
||||
if (fixture.Body.UserData is VineTile) { return -1; }
|
||||
if (fixture.CollidesWith == Category.None) { return -1; }
|
||||
if (fixture.Body.UserData is Item item)
|
||||
{
|
||||
if (item.Condition <= 0) { return -1; }
|
||||
@@ -669,6 +705,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
Item.ResetWaterDragCoefficient();
|
||||
if (dropper != null)
|
||||
{
|
||||
DisableProjectileCollisions();
|
||||
@@ -755,6 +792,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (User != null && User.Removed) { User = null; return false; }
|
||||
if (IgnoredBodies != null && IgnoredBodies.Contains(target.Body)) { return false; }
|
||||
if (originalCollisionCategories == Category.None && originalCollisionTargets == Category.None) { return false; }
|
||||
//ignore character colliders (the projectile only hits limbs)
|
||||
if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
|
||||
{
|
||||
@@ -840,8 +878,24 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (target.Body.UserData is Submarine sub)
|
||||
{
|
||||
Vector2 dir = item.body.LinearVelocity.LengthSquared() < 0.001f ?
|
||||
contact.Manifold.LocalNormal : Vector2.Normalize(item.body.LinearVelocity);
|
||||
//hit an item in a different sub -> no need to ignore, we can process the impact with this info
|
||||
//(if it wasn't, we'll move the projectile to that sub's coordinate space and let it hit what it hits there)
|
||||
if (Launcher?.Submarine != sub && target.UserData is Item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector2 normalizedVel;
|
||||
Vector2 dir;
|
||||
if (item.body.LinearVelocity.LengthSquared() < 0.001f)
|
||||
{
|
||||
normalizedVel = Vector2.Zero;
|
||||
dir = contact.Manifold.LocalNormal;
|
||||
}
|
||||
else
|
||||
{
|
||||
normalizedVel = dir = Vector2.Normalize(item.body.LinearVelocity);
|
||||
}
|
||||
|
||||
//do a raycast in the sub's coordinate space to see if it hit a structure
|
||||
var wallBody = Submarine.PickBody(
|
||||
@@ -850,7 +904,7 @@ namespace Barotrauma.Items.Components
|
||||
collisionCategory: Physics.CollisionWall);
|
||||
if (wallBody?.FixtureList?.First() != null && (wallBody.UserData is Structure || wallBody.UserData is Item) &&
|
||||
//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)
|
||||
Vector2.Dot((item.body.SimPosition + normalizedVel) - launchPos, dir) > 0)
|
||||
{
|
||||
target = wallBody.FixtureList.First();
|
||||
if (hits.Contains(target.Body))
|
||||
@@ -886,7 +940,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
AttackResult attackResult = new AttackResult();
|
||||
Character character = null;
|
||||
if (target.Body.UserData is Submarine submarine)
|
||||
if (target.Body.UserData is Submarine submarine && target.UserData is not Barotrauma.Item)
|
||||
{
|
||||
item.Move(-submarine.Position);
|
||||
item.Submarine = submarine;
|
||||
@@ -911,9 +965,11 @@ namespace Barotrauma.Items.Components
|
||||
if (Attack != null) { attackResult = Attack.DoDamageToLimb(User ?? Attacker, limb, item.WorldPosition, 1.0f); }
|
||||
if (limb.character != null) { character = limb.character; }
|
||||
}
|
||||
else if ((target.Body.UserData as Item ?? (target.Body.UserData as ItemComponent)?.Item) is Item targetItem)
|
||||
else if ((target.Body.UserData as Item ?? (target.Body.UserData as ItemComponent)?.Item ?? target.UserData as Item) is Item targetItem)
|
||||
{
|
||||
if (targetItem.Removed) { return false; }
|
||||
//hit the external collider of an item (turret?) of the same sub -> ignore
|
||||
if (target.UserData is Item && targetItem.Submarine != null && targetItem.Submarine == Launcher?.Submarine) { return false; }
|
||||
if (Attack != null && (targetItem.Prefab.DamagedByProjectiles || DamageDoors && targetItem.GetComponent<Door>() != null) && targetItem.Condition > 0)
|
||||
{
|
||||
attackResult = Attack.DoDamage(User ?? Attacker, targetItem, item.WorldPosition, 1.0f);
|
||||
@@ -925,7 +981,7 @@ namespace Barotrauma.Items.Components
|
||||
targetItem.Condition / targetItem.MaxCondition,
|
||||
emptyColor: GUIStyle.HealthBarColorLow,
|
||||
fullColor: GUIStyle.HealthBarColorHigh,
|
||||
textTag: targetItem.Name);
|
||||
textTag: targetItem.Prefab.ShowNameInHealthBar ? targetItem.Name : string.Empty);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1091,10 +1147,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void EnableProjectileCollisions()
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
|
||||
if (!IgnoreProjectilesWhileActive)
|
||||
if (item.body.CollisionCategories != Category.None)
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
|
||||
}
|
||||
if (item.Prefab.DamagedByProjectiles && !IgnoreProjectilesWhileActive)
|
||||
{
|
||||
if (item.body.CollisionCategories == Category.None) { item.body.CollisionCategories = Physics.CollisionCharacter; }
|
||||
item.body.CollidesWith |= Physics.CollisionProjectile;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Abilities;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -17,6 +16,9 @@ namespace Barotrauma.Items.Components
|
||||
private float deteriorationTimer;
|
||||
private float deteriorateAlwaysResetTimer;
|
||||
|
||||
private int updateDeteriorationCounter;
|
||||
private const int UpdateDeteriorationInterval = 10;
|
||||
|
||||
private int prevSentConditionValue;
|
||||
private string conditionSignal;
|
||||
|
||||
@@ -232,6 +234,7 @@ namespace Barotrauma.Items.Components
|
||||
public float RepairDegreeOfSuccess(Character character, List<Skill> skills)
|
||||
{
|
||||
if (skills.Count == 0) { return 1.0f; }
|
||||
if (character == null) { return 0.0f; }
|
||||
|
||||
float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
|
||||
float average = skillSum / skills.Count;
|
||||
@@ -241,6 +244,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void RepairBoost(bool qteSuccess)
|
||||
{
|
||||
if (CurrentFixer == null) { return; }
|
||||
if (qteSuccess)
|
||||
{
|
||||
item.Condition += RepairDegreeOfSuccess(CurrentFixer, requiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f);
|
||||
@@ -404,26 +408,11 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (!ShouldDeteriorate()) { return; }
|
||||
if (item.Condition > 0.0f)
|
||||
updateDeteriorationCounter++;
|
||||
if (updateDeteriorationCounter >= UpdateDeteriorationInterval)
|
||||
{
|
||||
if (deteriorationTimer > 0.0f)
|
||||
{
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
deteriorationTimer -= deltaTime * GetDeteriorationDelayMultiplier();
|
||||
#if SERVER
|
||||
if (deteriorationTimer <= 0.0f) { item.CreateServerEvent(this); }
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.ConditionPercentage > MinDeteriorationCondition)
|
||||
{
|
||||
float deteriorationSpeed = item.StatManager.GetAdjustedValue(ItemTalentStats.DetoriationSpeed, DeteriorationSpeed);
|
||||
item.Condition -= deteriorationSpeed * deltaTime;
|
||||
}
|
||||
UpdateDeterioration(deltaTime * UpdateDeteriorationInterval);
|
||||
updateDeteriorationCounter = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -559,6 +548,30 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDeterioration(float deltaTime)
|
||||
{
|
||||
if (item.Condition <= 0.0f) { return; }
|
||||
if (!ShouldDeteriorate()) { return; }
|
||||
|
||||
if (deteriorationTimer > 0.0f)
|
||||
{
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
deteriorationTimer -= deltaTime * GetDeteriorationDelayMultiplier();
|
||||
#if SERVER
|
||||
if (deteriorationTimer <= 0.0f) { item.CreateServerEvent(this); }
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.ConditionPercentage > MinDeteriorationCondition)
|
||||
{
|
||||
float deteriorationSpeed = item.StatManager.GetAdjustedValue(ItemTalentStats.DetoriationSpeed, DeteriorationSpeed);
|
||||
item.Condition -= deteriorationSpeed * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
private float GetMaxRepairConditionMultiplier(Character character)
|
||||
{
|
||||
if (character == null) { return 1.0f; }
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public readonly List<StatusEffect> Effects;
|
||||
|
||||
public readonly List<ushort> LoadedWireIds;
|
||||
public readonly List<(ushort wireId, int? connectionIndex)> LoadedWires;
|
||||
|
||||
//The grid the connection is a part of
|
||||
public GridInfo Grid;
|
||||
@@ -151,16 +151,20 @@ namespace Barotrauma.Items.Components
|
||||
IsPower = Name == "power_in" || Name == "power" || Name == "power_out";
|
||||
|
||||
|
||||
LoadedWireIds = new List<ushort>();
|
||||
LoadedWires = new List<(ushort wireId, int? connectionIndex)>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "link":
|
||||
int id = subElement.GetAttributeInt("w", 0);
|
||||
int? i = null;
|
||||
if (subElement.GetAttribute("i") != null)
|
||||
{
|
||||
i = subElement.GetAttributeInt("i", 0);
|
||||
}
|
||||
if (id < 0) { id = 0; }
|
||||
if (LoadedWireIds.Count < MaxWires) { LoadedWireIds.Add(idRemap.GetOffsetId(id)); }
|
||||
|
||||
if (LoadedWires.Count < MaxWires) { LoadedWires.Add((idRemap.GetOffsetId(id), i)); }
|
||||
break;
|
||||
case "statuseffect":
|
||||
Effects ??= new List<StatusEffect>();
|
||||
@@ -351,22 +355,29 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void InitializeFromLoaded()
|
||||
{
|
||||
if (LoadedWireIds.Count == 0) { return; }
|
||||
if (LoadedWires.Count == 0) { return; }
|
||||
|
||||
for (int i = 0; i < LoadedWireIds.Count; i++)
|
||||
foreach ((ushort wireId, int? connectionIndex) in LoadedWires)
|
||||
{
|
||||
if (!(Entity.FindEntityByID(LoadedWireIds[i]) is Item wireItem)) { continue; }
|
||||
if (Entity.FindEntityByID(wireId) is not Item wireItem) { continue; }
|
||||
|
||||
var wire = wireItem.GetComponent<Wire>();
|
||||
if (wire != null && TryAddLink(wire))
|
||||
{
|
||||
if (wire.Item.body != null) wire.Item.body.Enabled = false;
|
||||
wire.Connect(this, false, false);
|
||||
if (wire.Item.body != null) { wire.Item.body.Enabled = false; }
|
||||
if (connectionIndex.HasValue)
|
||||
{
|
||||
wire.Connect(this, connectionIndex.Value, addNode: false, sendNetworkEvent: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
wire.TryConnect(this, addNode: false, sendNetworkEvent: false);
|
||||
}
|
||||
wire.FixNodeEnds();
|
||||
recipientsDirty = true;
|
||||
}
|
||||
}
|
||||
LoadedWireIds.Clear();
|
||||
LoadedWires.Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -377,7 +388,8 @@ namespace Barotrauma.Items.Components
|
||||
foreach (var wire in wires.OrderBy(w => w.Item.ID))
|
||||
{
|
||||
newElement.Add(new XElement("link",
|
||||
new XAttribute("w", wire.Item.ID.ToString())));
|
||||
new XAttribute("w", wire.Item.ID.ToString()),
|
||||
new XAttribute("i", wire.Connections[0] == this ? 0 : 1)));
|
||||
}
|
||||
|
||||
parentElement.Add(newElement);
|
||||
|
||||
@@ -295,8 +295,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (int i = 0; i < loadedConnections.Count && i < Connections.Count; i++)
|
||||
{
|
||||
Connections[i].LoadedWireIds.Clear();
|
||||
Connections[i].LoadedWireIds.AddRange(loadedConnections[i].LoadedWireIds);
|
||||
Connections[i].LoadedWires.Clear();
|
||||
Connections[i].LoadedWires.AddRange(loadedConnections[i].LoadedWires);
|
||||
}
|
||||
|
||||
disconnectedWireIds = element.GetAttributeUshortArray("disconnectedwires", Array.Empty<ushort>()).ToList();
|
||||
|
||||
+7
-11
@@ -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();
|
||||
}
|
||||
@@ -304,9 +297,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
#if SERVER
|
||||
//make sure the clients know about the states of the checkboxes and text fields
|
||||
if (item.Submarine == null || !item.Submarine.Loading)
|
||||
if (customInterfaceElementList.Any())
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
if (item.Submarine == null || !item.Submarine.Loading)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -326,7 +322,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
foreach (StatusEffect effect in btnElement.StatusEffects)
|
||||
{
|
||||
item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
|
||||
item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, character: item.ParentInventory?.Owner as Character);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-15
@@ -1,6 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
#if CLIENT
|
||||
@@ -13,6 +12,9 @@ namespace Barotrauma.Items.Components
|
||||
partial class LightComponent : Powered, IServerSerializable, IDrawableComponent
|
||||
{
|
||||
private Color lightColor;
|
||||
/// <summary>
|
||||
/// The current brightness of the light source, affected by powerconsumption/voltage
|
||||
/// </summary>
|
||||
private float lightBrightness;
|
||||
private float blinkFrequency;
|
||||
private float pulseFrequency, pulseAmount;
|
||||
@@ -94,7 +96,7 @@ namespace Barotrauma.Items.Components
|
||||
if (isOn == value && IsActive == value) { return; }
|
||||
|
||||
IsActive = isOn = value;
|
||||
SetLightSourceState(value);
|
||||
SetLightSourceState(value, value ? lightBrightness : 0.0f);
|
||||
OnStateChanged();
|
||||
}
|
||||
}
|
||||
@@ -174,7 +176,7 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
if (Light != null)
|
||||
{
|
||||
Light.Color = IsOn ? lightColor.Multiply(lightBrightness) : Color.Transparent;
|
||||
Light.Color = IsOn ? lightColor.Multiply(lightColorMultiplier) : Color.Transparent;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -187,7 +189,7 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Should the light sprite be drawn on the item using alpha blending, in addition to being rendered in the light map? Can be used to make the light sprite stand out more.")]
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Should the light sprite be drawn on the item using alpha blending, in addition to being rendered in the light map? Can be used to make the light sprite stand out more.")]
|
||||
public bool AlphaBlend
|
||||
{
|
||||
get;
|
||||
@@ -214,7 +216,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (base.IsActive == value) { return; }
|
||||
base.IsActive = isOn = value;
|
||||
SetLightSourceState(value);
|
||||
SetLightSourceState(value, value ? lightBrightness : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,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;
|
||||
@@ -245,7 +247,7 @@ namespace Barotrauma.Items.Components
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
SetLightSourceState(IsActive);
|
||||
SetLightSourceState(IsActive, lightBrightness);
|
||||
turret = item.GetComponent<Turret>();
|
||||
#if CLIENT
|
||||
Drawable = AlphaBlend && Light.LightSprite != null;
|
||||
@@ -258,6 +260,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
#if CLIENT
|
||||
if (item.HiddenInGame)
|
||||
{
|
||||
Light.Enabled = false;
|
||||
}
|
||||
#endif
|
||||
CheckIfNeedsUpdate();
|
||||
}
|
||||
|
||||
@@ -273,7 +281,8 @@ namespace Barotrauma.Items.Components
|
||||
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
|
||||
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
|
||||
{
|
||||
SetLightSourceState(true);
|
||||
lightBrightness = 1.0f;
|
||||
SetLightSourceState(true, lightBrightness);
|
||||
SetLightSourceTransformProjSpecific();
|
||||
base.IsActive = false;
|
||||
isOn = true;
|
||||
@@ -300,18 +309,21 @@ 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))
|
||||
{
|
||||
SetLightSourceState(false);
|
||||
lightBrightness = 0.0f;
|
||||
SetLightSourceState(false, 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
SetLightSourceTransformProjSpecific();
|
||||
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
if (body != null && !body.Enabled)
|
||||
{
|
||||
SetLightSourceState(false);
|
||||
lightBrightness = 0.0f;
|
||||
SetLightSourceState(false, 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -338,7 +350,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
SetLightSourceState(false);
|
||||
SetLightSourceState(false, 0.0f);
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
@@ -370,7 +382,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
LightColor = XMLExtensions.ParseColor(signal.value, false);
|
||||
#if CLIENT
|
||||
SetLightSourceState(Light.Enabled);
|
||||
SetLightSourceState(Light.Enabled, lightColorMultiplier);
|
||||
#endif
|
||||
prevColorSignal = signal.value;
|
||||
}
|
||||
@@ -388,7 +400,7 @@ namespace Barotrauma.Items.Components
|
||||
target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness);
|
||||
}
|
||||
|
||||
partial void SetLightSourceState(bool enabled, float? brightness = null);
|
||||
partial void SetLightSourceState(bool enabled, float brightness);
|
||||
|
||||
public void SetLightSourceTransform()
|
||||
{
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(DecimalCount = 3), Serialize(0.01f, IsPropertySaveable.Yes, description: "How fast the objects within the detector's range have to be moving (in m/s).", alwaysUseInstanceValues: true)]
|
||||
[Editable(DecimalCount = 3), Serialize(0.1f, IsPropertySaveable.Yes, description: "How fast the objects within the detector's range have to be moving (in m/s).", alwaysUseInstanceValues: true)]
|
||||
public float MinimumVelocity
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private const int MaxMessages = 60;
|
||||
|
||||
private List<TerminalMessage> messageHistory = new List<TerminalMessage>(MaxMessages);
|
||||
private readonly List<TerminalMessage> messageHistory = new List<TerminalMessage>(MaxMessages);
|
||||
|
||||
public LocalizedString DisplayedWelcomeMessage
|
||||
{
|
||||
@@ -67,6 +67,12 @@ namespace Barotrauma.Items.Components
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "The terminal will use a monospace font if this box is ticked.", alwaysUseInstanceValues: true)]
|
||||
public bool UseMonospaceFont { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool AutoHideScrollbar { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
public bool WelcomeMessageDisplayed { get; set; }
|
||||
|
||||
private Color textColor = Color.LimeGreen;
|
||||
|
||||
[Editable, Serialize("50,205,50,255", IsPropertySaveable.Yes, description: "Color of the terminal text.", alwaysUseInstanceValues: true)]
|
||||
@@ -85,6 +91,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize("> ", IsPropertySaveable.Yes)]
|
||||
public string LineStartSymbol { get; set; }
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.No)]
|
||||
public bool Readonly { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool AutoScrollToBottom { get; set; }
|
||||
|
||||
private string OutputValue { get; set; }
|
||||
|
||||
private string prevColorSignal;
|
||||
@@ -143,14 +158,14 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
|
||||
base.OnItemLoaded();
|
||||
if (!DisplayedWelcomeMessage.IsNullOrEmpty())
|
||||
if (!DisplayedWelcomeMessage.IsNullOrEmpty() && !WelcomeMessageDisplayed)
|
||||
{
|
||||
ShowOnDisplay(DisplayedWelcomeMessage.Value, addToHistory: !isSubEditor, TextColor);
|
||||
DisplayedWelcomeMessage = "";
|
||||
//remove welcome message if a game session is running so it doesn't reappear on successive rounds
|
||||
//disable welcome message if a game session is running so it doesn't reappear on successive rounds
|
||||
if (GameMain.GameSession != null && !isSubEditor)
|
||||
{
|
||||
welcomeMessage = null;
|
||||
WelcomeMessageDisplayed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,14 +162,50 @@ namespace Barotrauma.Items.Components
|
||||
SetConnectedDirty();
|
||||
}
|
||||
|
||||
public bool Connect(Connection newConnection, bool addNode = true, bool sendNetworkEvent = false)
|
||||
/// <summary>
|
||||
/// Tries to add the given connection to this wire. Note that this only affects the wire -
|
||||
/// adding the wire to the connection is done in <see cref="Connection.ConnectWire(Wire)"/>
|
||||
/// </summary>
|
||||
|
||||
public bool TryConnect(Connection newConnection, bool addNode = true, bool sendNetworkEvent = false)
|
||||
{
|
||||
if (connections[0] == null)
|
||||
{
|
||||
return Connect(newConnection, 0, addNode, sendNetworkEvent);
|
||||
}
|
||||
else if (connections[1] == null)
|
||||
{
|
||||
return Connect(newConnection, 1, addNode, sendNetworkEvent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add the given connection to this wire. Note that this only affects the wire -
|
||||
/// adding the wire to the connection is done in <see cref="Connection.ConnectWire(Wire)"/>
|
||||
/// </summary>
|
||||
/// <param name="connectionIndex">Which end of the wire to add the connection to? 0 or 1.
|
||||
/// Normally doesn't make a difference, but matters if we're copying/loading a wire,
|
||||
/// in which case the 1st node should be located at the same item as the 1st connection.</param>
|
||||
/// <returns></returns>
|
||||
public bool Connect(Connection newConnection, int connectionIndex, bool addNode = true, bool sendNetworkEvent = false)
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (connections[i] == newConnection) { return false; }
|
||||
}
|
||||
|
||||
if (!connections.Any(c => c == null)) { return false; }
|
||||
if (connectionIndex < 0 || connectionIndex > 1)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while connecting a wire to {newConnection.Item}: {connectionIndex} is not a valid index.");
|
||||
return false;
|
||||
}
|
||||
if (connections[connectionIndex] != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while connecting a wire to {newConnection.Item}: a wire is already connected to the index {connectionIndex}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
@@ -183,70 +219,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
newConnection.ConnectionPanel.DisconnectedWires.Remove(this);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
connections[connectionIndex] = newConnection;
|
||||
FixNodeEnds();
|
||||
|
||||
if (addNode)
|
||||
{
|
||||
if (connections[i] != null) { continue; }
|
||||
|
||||
connections[i] = newConnection;
|
||||
FixNodeEnds();
|
||||
|
||||
if (!addNode) { break; }
|
||||
|
||||
Submarine refSub = newConnection.Item.Submarine;
|
||||
if (refSub == null)
|
||||
{
|
||||
Structure attachTarget = Structure.GetAttachTarget(newConnection.Item.WorldPosition);
|
||||
if (attachTarget == null && !(newConnection.Item.GetComponent<Holdable>()?.Attached ?? false))
|
||||
{
|
||||
connections[i] = null;
|
||||
continue;
|
||||
}
|
||||
refSub = attachTarget?.Submarine;
|
||||
}
|
||||
|
||||
Vector2 nodePos = refSub == null ?
|
||||
newConnection.Item.Position :
|
||||
newConnection.Item.Position - refSub.HiddenSubPosition;
|
||||
|
||||
if (nodes.Count > 0 && nodes[0] == nodePos) { break; }
|
||||
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { break; }
|
||||
|
||||
//make sure we place the node at the correct end of the wire (the end that's closest to the new node pos)
|
||||
int newNodeIndex = 0;
|
||||
if (nodes.Count > 1)
|
||||
{
|
||||
if (connections[0] != null && connections[0] != newConnection)
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
||||
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
else if (connections[1] != null && connections[1] != newConnection)
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
||||
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
else if (Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
|
||||
if (newNodeIndex == 0 && nodes.Count > 1)
|
||||
{
|
||||
nodes.Insert(0, nodePos);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodes.Add(nodePos);
|
||||
}
|
||||
|
||||
break;
|
||||
AddNode(newConnection, connectionIndex);
|
||||
}
|
||||
|
||||
SetConnectedDirty();
|
||||
@@ -258,7 +236,7 @@ namespace Barotrauma.Items.Components
|
||||
if (ic == this) { continue; }
|
||||
ic.Drop(null);
|
||||
}
|
||||
if (item.Container != null) { item.Container.RemoveContained(this.item); }
|
||||
item.Container?.RemoveContained(item);
|
||||
if (item.body != null) { item.body.Enabled = false; }
|
||||
|
||||
IsActive = false;
|
||||
@@ -286,6 +264,63 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddNode(Connection newConnection, int selectedIndex)
|
||||
{
|
||||
Submarine refSub = newConnection.Item.Submarine;
|
||||
if (refSub == null)
|
||||
{
|
||||
Structure attachTarget = Structure.GetAttachTarget(newConnection.Item.WorldPosition);
|
||||
if (attachTarget == null && !(newConnection.Item.GetComponent<Holdable>()?.Attached ?? false))
|
||||
{
|
||||
connections[selectedIndex] = null;
|
||||
return;
|
||||
}
|
||||
refSub = attachTarget?.Submarine;
|
||||
}
|
||||
|
||||
Vector2 nodePos = refSub == null ?
|
||||
newConnection.Item.Position :
|
||||
newConnection.Item.Position - refSub.HiddenSubPosition;
|
||||
|
||||
if (nodes.Count > 0 && nodes[0] == nodePos) { return; }
|
||||
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { return; }
|
||||
|
||||
//make sure we place the node at the correct end of the wire (the end that's closest to the new node pos)
|
||||
int newNodeIndex = 0;
|
||||
if (nodes.Count > 1)
|
||||
{
|
||||
if (connections[0] != null && connections[0] != newConnection)
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
||||
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
else if (connections[1] != null && connections[1] != newConnection)
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
||||
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
else if (Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
|
||||
if (newNodeIndex == 0 && nodes.Count > 1)
|
||||
{
|
||||
nodes.Insert(0, nodePos);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodes.Add(nodePos);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
if (shouldClearConnections) { ClearConnections(character); }
|
||||
|
||||
@@ -67,7 +67,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
return GameMain.GameSession?.RoundDuration ?? 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
public bool ApplyEffectsToCharactersInsideSub { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
public bool MoveOutsideSub { get; set; }
|
||||
|
||||
private readonly LevelTrigger.TriggererType triggeredBy;
|
||||
private readonly HashSet<Entity> triggerers = new HashSet<Entity>();
|
||||
@@ -124,7 +130,7 @@ namespace Barotrauma.Items.Components
|
||||
PhysicsBody.FarseerBody.SetIsSensor(true);
|
||||
PhysicsBody.FarseerBody.OnCollision += OnCollision;
|
||||
PhysicsBody.FarseerBody.OnSeparation += OnSeparation;
|
||||
RadiusInDisplayUnits = ConvertUnits.ToDisplayUnits(PhysicsBody.radius);
|
||||
RadiusInDisplayUnits = ConvertUnits.ToDisplayUnits(PhysicsBody.Radius);
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
@@ -137,7 +143,7 @@ namespace Barotrauma.Items.Components
|
||||
private bool OnCollision(Fixture sender, Fixture other, Contact contact)
|
||||
{
|
||||
if (!(LevelTrigger.GetEntity(other) is Entity entity)) { return false; }
|
||||
if (!LevelTrigger.IsTriggeredByEntity(entity, triggeredBy, mustBeOnSpecificSub: (true, item.Submarine))) { return false; }
|
||||
if (!LevelTrigger.IsTriggeredByEntity(entity, triggeredBy, mustBeOnSpecificSub: (!MoveOutsideSub, item.Submarine))) { return false; }
|
||||
triggerers.Add(entity);
|
||||
return true;
|
||||
}
|
||||
@@ -162,6 +168,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.Submarine != null && MoveOutsideSub)
|
||||
{
|
||||
item.SetTransform(ConvertUnits.ToSimUnits(item.WorldPosition), item.Rotation);
|
||||
item.CurrentHull = null;
|
||||
item.Submarine = null;
|
||||
PhysicsBody.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
|
||||
PhysicsBody.Submarine = item.Submarine;
|
||||
}
|
||||
|
||||
LevelTrigger.RemoveInActiveTriggerers(PhysicsBody, triggerers);
|
||||
|
||||
if (triggerOnce)
|
||||
@@ -201,6 +216,13 @@ namespace Barotrauma.Items.Components
|
||||
else if (triggerer is Submarine submarine)
|
||||
{
|
||||
LevelTrigger.ApplyAttacks(attacks, item.WorldPosition, deltaTime);
|
||||
foreach (Character c2 in Character.CharacterList)
|
||||
{
|
||||
if (c2.Submarine == submarine)
|
||||
{
|
||||
LevelTrigger.ApplyAttacks(attacks, c2, item.WorldPosition, deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.Abs(Force) < 0.01f)
|
||||
|
||||
@@ -59,8 +59,9 @@ namespace Barotrauma.Items.Components
|
||||
private float aiTargetingGraceTimer;
|
||||
|
||||
private float aiFindTargetTimer;
|
||||
private Character currentTarget;
|
||||
const float aiFindTargetInterval = 5.0f;
|
||||
private ISpatialEntity currentTarget;
|
||||
private const float CrewAiFindTargetMaxInterval = 3.0f;
|
||||
private const float CrewAIFindTargetMinInverval = 0.2f;
|
||||
|
||||
private int currentLoaderIndex;
|
||||
|
||||
@@ -73,6 +74,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private List<LightComponent> lightComponents;
|
||||
|
||||
private readonly bool isSlowTurret;
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
@@ -317,6 +320,42 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description:"Should the turret operate automatically using AI targeting? Comes with some optional random movement that can be adjusted below."), Editable]
|
||||
public bool AutoOperate { get; set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes, description: "[Auto Operate] How much the turret should adjust the aim off the target randomly instead of tracking the target perfectly? In Degrees."), Editable]
|
||||
public float RandomAimAmount { get; set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes, description: "[Auto Operate] How often the turret should adjust the aim randomly instead of tracking the target perfectly? Minimum wait time, in seconds."), Editable]
|
||||
public float RandomAimMinTime { get; set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes, description: "[Auto Operate] How often the turret should adjust the aim randomly instead of tracking the target perfectly? Maximum wait time, in seconds."), Editable]
|
||||
public float RandomAimMaxTime { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret move randomly while idle?"), Editable]
|
||||
public bool RandomMovement { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret have a delay while targeting targets or always aim prefectly?"), Editable]
|
||||
public bool AimDelay { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret target characters in general?"), Editable]
|
||||
public bool TargetCharacters { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret target all monsters?"), Editable]
|
||||
public bool TargetMonsters { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret target all humans (or creatures in the same group, like pets)?"), Editable]
|
||||
public bool TargetHumans { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret target other submarines?"), Editable]
|
||||
public bool TargetSubmarines { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret target items?"), Editable]
|
||||
public bool TargetItems { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "[Auto Operate] Group or SpeciesName that the AI ignores when the turret is operated automatically."), Editable]
|
||||
public Identifier FriendlyTag { get; private set; }
|
||||
|
||||
public Turret(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -346,6 +385,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
item.IsShootable = true;
|
||||
item.RequireAimToUse = false;
|
||||
isSlowTurret = item.HasTag("slowturret");
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -560,6 +600,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
UpdateLightComponents();
|
||||
|
||||
if (AutoOperate)
|
||||
{
|
||||
UpdateAutoOperate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateLightComponents()
|
||||
@@ -658,13 +703,20 @@ namespace Barotrauma.Items.Components
|
||||
loaderBroken = true;
|
||||
continue;
|
||||
}
|
||||
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (tryUseProjectileContainer(linkedItem)) { break; }
|
||||
}
|
||||
tryUseProjectileContainer(item);
|
||||
|
||||
bool tryUseProjectileContainer(Item containerItem)
|
||||
{
|
||||
ItemContainer projectileContainer = containerItem.GetComponent<ItemContainer>();
|
||||
if (projectileContainer != null)
|
||||
{
|
||||
linkedItem.Use(deltaTime, null);
|
||||
containerItem.Use(deltaTime, null);
|
||||
projectiles = GetLoadedProjectiles();
|
||||
if (projectiles.Any()) { break; }
|
||||
if (projectiles.Any()) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (projectiles.Count == 0 && !LaunchWithoutProjectile)
|
||||
@@ -833,9 +885,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
float spread = MathHelper.ToRadians(Spread) * Rand.Range(-0.5f, 0.5f);
|
||||
projectile.SetTransform(
|
||||
ConvertUnits.ToSimUnits(GetRelativeFiringPosition()),
|
||||
-(launchRotation ?? rotation) + spread);
|
||||
|
||||
Vector2 launchPos = ConvertUnits.ToSimUnits(GetRelativeFiringPosition());
|
||||
|
||||
//check if there's some other sub between the turret's origin and the launch pos,
|
||||
//and if so, launch at the intersection of the turret and the sub to prevent the projectile from spawning inside the other sub
|
||||
Body pickedBody = Submarine.PickBody(ConvertUnits.ToSimUnits(item.WorldPosition), launchPos, null, Physics.CollisionWall, allowInsideFixture: true,
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
return f.Body.UserData is not Submarine sub || sub != item.Submarine;
|
||||
});
|
||||
if (pickedBody != null)
|
||||
{
|
||||
launchPos = Submarine.LastPickedPosition;
|
||||
}
|
||||
projectile.SetTransform(launchPos, -(launchRotation ?? rotation) + spread);
|
||||
projectile.UpdateTransform();
|
||||
projectile.Submarine = projectile.body?.Submarine;
|
||||
|
||||
@@ -895,17 +959,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private float waitTimer;
|
||||
private float disorderTimer;
|
||||
private float randomAimTimer;
|
||||
|
||||
private float prevTargetRotation;
|
||||
private float updateTimer;
|
||||
private bool updatePending;
|
||||
public void ThalamusOperate(WreckAI ai, float deltaTime, bool targetHumans, bool targetOtherCreatures, bool targetSubmarines, bool ignoreDelay)
|
||||
{
|
||||
if (ai == null) { return; }
|
||||
|
||||
public void UpdateAutoOperate(float deltaTime, Identifier friendlyTag = default)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
if (friendlyTag.IsEmpty)
|
||||
{
|
||||
friendlyTag = FriendlyTag;
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
@@ -924,7 +992,7 @@ namespace Barotrauma.Items.Components
|
||||
updateTimer -= deltaTime;
|
||||
}
|
||||
|
||||
if (!ignoreDelay && waitTimer > 0)
|
||||
if (AimDelay && waitTimer > 0)
|
||||
{
|
||||
waitTimer -= deltaTime;
|
||||
return;
|
||||
@@ -934,40 +1002,48 @@ namespace Barotrauma.Items.Components
|
||||
float shootDistance = AIRange;
|
||||
ISpatialEntity target = null;
|
||||
float closestDist = shootDistance * shootDistance;
|
||||
if (targetHumans || targetOtherCreatures)
|
||||
if (TargetCharacters)
|
||||
{
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
if (character == null || character.Removed || character.IsDead) { continue; }
|
||||
if (character.Params.Group == ai.Config.Entity) { continue; }
|
||||
bool isHuman = character.IsHuman || character.Params.Group == CharacterPrefab.HumanSpeciesName;
|
||||
if (isHuman)
|
||||
{
|
||||
if (!targetHumans)
|
||||
{
|
||||
// Don't target humans if not defined to.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (!targetOtherCreatures)
|
||||
{
|
||||
// Don't target other creatures if not defined to.
|
||||
continue;
|
||||
}
|
||||
if (!IsValidTarget(character)) { continue; }
|
||||
float priority = isSlowTurret ? character.Params.AISlowTurretPriority : character.Params.AITurretPriority;
|
||||
if (priority <= 0) { continue; }
|
||||
if (!IsValidTargetForAutoOperate(character, friendlyTag)) { continue; }
|
||||
float dist = Vector2.DistanceSquared(character.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
if (!CheckTurretAngle(character.WorldPosition)) { continue; }
|
||||
target = character;
|
||||
closestDist = dist;
|
||||
closestDist = dist / priority;
|
||||
}
|
||||
}
|
||||
if (targetSubmarines)
|
||||
if (TargetItems)
|
||||
{
|
||||
foreach (Item targetItem in Item.ItemList)
|
||||
{
|
||||
if (!IsValidTarget(targetItem)) { continue; }
|
||||
float priority = isSlowTurret ? targetItem.Prefab.AISlowTurretPriority : targetItem.Prefab.AITurretPriority;
|
||||
if (priority <= 0) { continue; }
|
||||
float dist = Vector2.DistanceSquared(item.WorldPosition, targetItem.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
if (dist > shootDistance * shootDistance) { continue; }
|
||||
if (!CheckTurretAngle(targetItem.WorldPosition)) { continue; }
|
||||
target = targetItem;
|
||||
closestDist = dist / priority;
|
||||
}
|
||||
}
|
||||
if (TargetSubmarines)
|
||||
{
|
||||
if (target == null || target.Submarine != null)
|
||||
{
|
||||
closestDist = maxDistance * maxDistance;
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (sub == Item.Submarine) { continue; }
|
||||
if (item.Submarine != null)
|
||||
{
|
||||
if (Character.IsOnFriendlyTeam(item.Submarine.TeamID, sub.TeamID)) { continue; }
|
||||
}
|
||||
float dist = Vector2.DistanceSquared(sub.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
closestSub = sub;
|
||||
@@ -981,34 +1057,41 @@ namespace Barotrauma.Items.Components
|
||||
if (!closestSub.IsEntityFoundOnThisSub(hull, true)) { continue; }
|
||||
float dist = Vector2.DistanceSquared(hull.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
// Don't check the angle, because it doesn't work on Thalamus spike. The angle check wouldn't be very important here anyway.
|
||||
target = hull;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ignoreDelay)
|
||||
|
||||
if (target == null && RandomMovement)
|
||||
{
|
||||
if (target == null)
|
||||
// Random movement while there's no target
|
||||
waitTimer = Rand.Value(Rand.RandSync.Unsynced) < 0.98f ? 0f : Rand.Range(5f, 20f);
|
||||
targetRotation = Rand.Range(minRotation, maxRotation);
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (AimDelay)
|
||||
{
|
||||
if (RandomAimAmount > 0)
|
||||
{
|
||||
// Random movement
|
||||
waitTimer = Rand.Value(Rand.RandSync.Unsynced) < 0.98f ? 0f : Rand.Range(5f, 20f);
|
||||
targetRotation = Rand.Range(minRotation, maxRotation);
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
if (disorderTimer < 0)
|
||||
{
|
||||
// Random disorder
|
||||
disorderTimer = Rand.Range(0f, 3f);
|
||||
waitTimer = Rand.Range(0.25f, 1f);
|
||||
targetRotation = MathUtils.WrapAngleTwoPi(targetRotation += Rand.Range(-1f, 1f));
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
disorderTimer -= deltaTime;
|
||||
if (randomAimTimer < 0)
|
||||
{
|
||||
// Random disorder or other flaw in the targeting.
|
||||
randomAimTimer = Rand.Range(RandomAimMinTime, RandomAimMaxTime);
|
||||
waitTimer = Rand.Range(0.25f, 1f);
|
||||
float randomAim = MathHelper.ToRadians(RandomAimAmount);
|
||||
targetRotation = MathUtils.WrapAngleTwoPi(targetRotation += Rand.Range(-randomAim, randomAim));
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
randomAimTimer -= deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target == null) { return; }
|
||||
@@ -1043,11 +1126,11 @@ namespace Barotrauma.Items.Components
|
||||
start -= target.Submarine.SimPosition;
|
||||
end -= target.Submarine.SimPosition;
|
||||
Body transformedTarget = CheckLineOfSight(start, end);
|
||||
shoot = CanShoot(transformedTarget, user: null, ai, targetSubmarines) && (worldTarget == null || CanShoot(worldTarget, user: null, ai, targetSubmarines));
|
||||
shoot = CanShoot(transformedTarget, user: null, friendlyTag, TargetSubmarines) && (worldTarget == null || CanShoot(worldTarget, user: null, friendlyTag, TargetSubmarines));
|
||||
}
|
||||
else
|
||||
{
|
||||
shoot = CanShoot(worldTarget, user: null, ai, targetSubmarines);
|
||||
shoot = CanShoot(worldTarget, user: null, friendlyTag, TargetSubmarines);
|
||||
}
|
||||
if (shoot)
|
||||
{
|
||||
@@ -1055,7 +1138,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget && previousTarget.IsDead)
|
||||
{
|
||||
@@ -1205,18 +1288,19 @@ namespace Barotrauma.Items.Components
|
||||
bool hadCurrentTarget = currentTarget != null;
|
||||
if (hadCurrentTarget)
|
||||
{
|
||||
if (currentTarget.Removed || currentTarget.IsDead)
|
||||
if (!IsValidTarget(currentTarget))
|
||||
{
|
||||
currentTarget = null;
|
||||
aiFindTargetTimer = CrewAIFindTargetMinInverval;
|
||||
}
|
||||
}
|
||||
|
||||
if (aiFindTargetTimer <= 0.0f || currentTarget == null)
|
||||
if (aiFindTargetTimer <= 0.0f)
|
||||
{
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
// Ignore dead, friendly, and those that are inside the same sub
|
||||
if (enemy.IsDead || !enemy.Enabled) { continue; }
|
||||
if (!IsValidTarget(enemy)) { continue; }
|
||||
float priority = isSlowTurret ? enemy.Params.AISlowTurretPriority : enemy.Params.AITurretPriority;
|
||||
if (priority <= 0) { continue; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (enemy.Submarine == character.Submarine) { continue; }
|
||||
@@ -1233,30 +1317,53 @@ namespace Barotrauma.Items.Components
|
||||
// We shouldn't check the angle when a long creature is traveling outside of the shooting range, because doing so would not allow us to shoot the limbs that might be close enough to shoot at.
|
||||
if (!CheckTurretAngle(enemy.WorldPosition)) { continue; }
|
||||
}
|
||||
targetPos = enemy.WorldPosition;
|
||||
closestEnemy = enemy;
|
||||
closestDistance = dist;
|
||||
closestDistance = dist / priority;
|
||||
currentTarget = closestEnemy;
|
||||
}
|
||||
currentTarget = closestEnemy;
|
||||
aiFindTargetTimer = aiFindTargetInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
closestEnemy = currentTarget;
|
||||
}
|
||||
|
||||
if (closestEnemy != null)
|
||||
{
|
||||
targetPos = closestEnemy.WorldPosition;
|
||||
//if the enemy is inside another sub, aim at the room they're in to make it less obvious that the enemy "knows" exactly where the target is
|
||||
if (closestEnemy.Submarine != null && closestEnemy.CurrentHull != null && closestEnemy.Submarine != item.Submarine && !closestEnemy.CanSeeTarget(Item))
|
||||
foreach (Item targetItem in Item.ItemList)
|
||||
{
|
||||
targetPos = closestEnemy.CurrentHull.WorldPosition;
|
||||
if (!IsValidTarget(targetItem)) { continue; }
|
||||
float priority = isSlowTurret ? targetItem.Prefab.AISlowTurretPriority : targetItem.Prefab.AITurretPriority;
|
||||
if (priority <= 0) { continue; }
|
||||
float dist = Vector2.DistanceSquared(item.WorldPosition, targetItem.WorldPosition);
|
||||
if (dist > closestDistance) { continue; }
|
||||
if (dist > shootDistance * shootDistance) { continue; }
|
||||
if (!CheckTurretAngle(targetItem.WorldPosition)) { continue; }
|
||||
targetPos = targetItem.WorldPosition;
|
||||
closestDistance = dist / priority;
|
||||
// Override the target character so that we can target the item instead.
|
||||
closestEnemy = null;
|
||||
currentTarget = targetItem;
|
||||
}
|
||||
if (currentTarget == null)
|
||||
{
|
||||
aiFindTargetTimer = CrewAIFindTargetMinInverval;
|
||||
}
|
||||
else
|
||||
{
|
||||
aiFindTargetTimer = CrewAiFindTargetMaxInterval;
|
||||
}
|
||||
}
|
||||
else if (currentTarget != null)
|
||||
{
|
||||
targetPos = currentTarget.WorldPosition;
|
||||
}
|
||||
bool iceSpireSpotted = false;
|
||||
// Adjust the target character position (limb or submarine)
|
||||
if (currentTarget is Character targetCharacter)
|
||||
{
|
||||
//if the enemy is inside another sub, aim at the room they're in to make it less obvious that the enemy "knows" exactly where the target is
|
||||
if (targetCharacter.Submarine != null && targetCharacter.CurrentHull != null && targetCharacter.Submarine != item.Submarine && !targetCharacter.CanSeeTarget(Item))
|
||||
{
|
||||
targetPos = targetCharacter.CurrentHull.WorldPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Target the closest limb. Doesn't make much difference with smaller creatures, but enables the bots to shoot longer abyss creatures like the endworm. Otherwise they just target the main body = head.
|
||||
float closestDist = closestDistance;
|
||||
foreach (Limb limb in closestEnemy.AnimController.Limbs)
|
||||
foreach (Limb limb in targetCharacter.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.Hidden) { continue; }
|
||||
@@ -1270,13 +1377,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (closestDist > shootDistance * shootDistance)
|
||||
{
|
||||
// Not close enough to shoot
|
||||
// Not close enough to shoot.
|
||||
currentTarget = null;
|
||||
closestEnemy = null;
|
||||
targetPos = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (item.Submarine != null && Level.Loaded != null)
|
||||
else if (targetPos == null && item.Submarine != null && Level.Loaded != null)
|
||||
{
|
||||
// Check ice spires
|
||||
shootDistance = AIRange * item.OffsetOnSelectedMultiplier;
|
||||
@@ -1286,50 +1394,49 @@ namespace Barotrauma.Items.Components
|
||||
if (wall is not DestructibleLevelWall destructibleWall || destructibleWall.Destroyed) { continue; }
|
||||
foreach (var cell in wall.Cells)
|
||||
{
|
||||
if (cell.DoesDamage)
|
||||
if (!cell.DoesDamage) { continue; }
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
closestPoint = intersection;
|
||||
if (!CheckTurretAngle(closestPoint)) { continue; }
|
||||
}
|
||||
float dist = Vector2.Distance(closestPoint, item.WorldPosition);
|
||||
|
||||
//add one px to make sure the visibility raycast doesn't miss the cell due to the end position being right at the edge of the cell
|
||||
closestPoint += (closestPoint - item.WorldPosition) / Math.Max(dist, 1);
|
||||
|
||||
if (dist > AIRange + 1000) { continue; }
|
||||
float dot = 0;
|
||||
if (!MathUtils.NearlyEqual(item.Submarine.Velocity, Vector2.Zero))
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
float dist = Vector2.Distance(closestPoint, item.WorldPosition);
|
||||
|
||||
//add one px to make sure the visibility raycast doesn't miss the cell due to the end position being right at the edge of the cell
|
||||
closestPoint += (closestPoint - item.WorldPosition) / Math.Max(dist, 1);
|
||||
|
||||
if (dist > AIRange + 1000) { continue; }
|
||||
float dot = 0;
|
||||
if (!MathUtils.NearlyEqual(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;
|
||||
iceSpireSpotted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1345,13 +1452,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget == null && !hadCurrentTarget)
|
||||
{
|
||||
if (CreatureMetrics.Instance.RecentlyEncountered.Contains(closestEnemy.SpeciesName) || closestEnemy.IsHuman)
|
||||
if (CreatureMetrics.RecentlyEncountered.Contains(closestEnemy.SpeciesName) || closestEnemy.IsHuman)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNewTargetSpotted").Value,
|
||||
identifier: "newtargetspotted".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
else if (CreatureMetrics.Instance.Encountered.Contains(closestEnemy.SpeciesName))
|
||||
else if (CreatureMetrics.Encountered.Contains(closestEnemy.SpeciesName))
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogIdentifiedTargetSpotted", "[speciesname]", closestEnemy.DisplayName).Value,
|
||||
identifier: "identifiedtargetspotted".ToIdentifier(),
|
||||
@@ -1364,17 +1471,17 @@ namespace Barotrauma.Items.Components
|
||||
minDurationBetweenSimilar: 5.0f);
|
||||
}
|
||||
}
|
||||
else if (!CreatureMetrics.Instance.Encountered.Contains(closestEnemy.SpeciesName))
|
||||
else if (!CreatureMetrics.Encountered.Contains(closestEnemy.SpeciesName))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted").Value,
|
||||
identifier: "unidentifiedtargetspotted".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 5.0f);
|
||||
}
|
||||
character.AddEncounter(closestEnemy);
|
||||
CreatureMetrics.AddEncounter(closestEnemy.SpeciesName);
|
||||
}
|
||||
character.AIController.SelectTarget(closestEnemy.AiTarget);
|
||||
}
|
||||
else if (closestEnemy == null && character.IsOnPlayerTeam)
|
||||
else if (iceSpireSpotted && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogIceSpireSpotted").Value,
|
||||
identifier: "icespirespotted".ToIdentifier(),
|
||||
@@ -1437,7 +1544,55 @@ namespace Barotrauma.Items.Components
|
||||
return 0;
|
||||
}
|
||||
|
||||
private bool CanShoot(Body targetBody, Character user = null, WreckAI ai = null, bool targetSubmarines = true)
|
||||
// Not exahustive, but helps to get rid of some code duplication
|
||||
private static bool IsValidTarget(ISpatialEntity target)
|
||||
{
|
||||
if (target == null) { return false; }
|
||||
if (target is Character targetCharacter)
|
||||
{
|
||||
if (!targetCharacter.Enabled || targetCharacter.Removed || targetCharacter.IsDead || targetCharacter.AITurretPriority <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (target is Item targetItem)
|
||||
{
|
||||
if (targetItem.Removed || targetItem.Condition <= 0 || !targetItem.Prefab.IsAITurretTarget || targetItem.Prefab.AITurretPriority <= 0 || targetItem.HiddenInGame)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (targetItem.Submarine != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsValidTargetForAutoOperate(Character target, Identifier friendlyTag)
|
||||
{
|
||||
if (!friendlyTag.IsEmpty)
|
||||
{
|
||||
if (target.SpeciesName.Equals(friendlyTag) || target.Group.Equals(friendlyTag)) { return false; }
|
||||
}
|
||||
bool isHuman = target.IsHuman || target.Group == CharacterPrefab.HumanSpeciesName;
|
||||
if (isHuman)
|
||||
{
|
||||
if (item.Submarine != null)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
return TargetHumans;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shouldn't check the team here, because all the enemies are in the same team (None).
|
||||
return TargetMonsters;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanShoot(Body targetBody, Character user = null, Identifier friendlyTag = default, bool targetSubmarines = true)
|
||||
{
|
||||
if (targetBody == null) { return false; }
|
||||
Character targetCharacter = null;
|
||||
@@ -1449,7 +1604,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
targetCharacter = limb.character;
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
if (targetCharacter != null && !targetCharacter.Removed)
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
@@ -1458,27 +1613,25 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (ai != null)
|
||||
else if (!IsValidTargetForAutoOperate(targetCharacter, friendlyTag))
|
||||
{
|
||||
if (targetCharacter.Params.Group == ai.Config.Entity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Note that Thalamus runs this even when AutoOperate is false.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (targetBody.UserData is ISpatialEntity e)
|
||||
{
|
||||
if (e is Structure s && s.Indestructible) { return false; }
|
||||
Submarine sub = e.Submarine ?? e as Submarine;
|
||||
if (e is Structure { Indestructible: true }) { return false; }
|
||||
if (!targetSubmarines && e is Submarine) { return false; }
|
||||
if (sub == null) { return false; }
|
||||
Submarine sub = e.Submarine ?? e as Submarine;
|
||||
if (sub == null) { return true; }
|
||||
if (sub == Item.Submarine) { return false; }
|
||||
if (sub.Info.IsOutpost || sub.Info.IsWreck || sub.Info.IsBeacon) { return false; }
|
||||
if (sub.TeamID == Item.Submarine.TeamID) { return false; }
|
||||
}
|
||||
else if (!(targetBody.UserData is Voronoi2.VoronoiCell cell && cell.IsDestructible))
|
||||
else if (targetBody.UserData is not Voronoi2.VoronoiCell { IsDestructible: true })
|
||||
{
|
||||
// Hit something else, probably a level wall
|
||||
return false;
|
||||
@@ -1489,7 +1642,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Body CheckLineOfSight(Vector2 start, Vector2 end)
|
||||
{
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionProjectile;
|
||||
Body pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true,
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
@@ -527,14 +527,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (picker.Removed)
|
||||
if (picker == null || picker.Removed)
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
item.SetTransform(picker.SimPosition, 0.0f);
|
||||
|
||||
//if the item is also being held, let the Holdable component control the position
|
||||
if (item.GetComponent<Holdable>() is not { IsActive: true })
|
||||
{
|
||||
item.SetTransform(picker.SimPosition, 0.0f);
|
||||
}
|
||||
item.ApplyStatusEffects(ActionType.OnWearing, deltaTime, picker);
|
||||
|
||||
#if CLIENT
|
||||
|
||||
Reference in New Issue
Block a user