Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop
This commit is contained in:
@@ -105,15 +105,21 @@ namespace Barotrauma
|
||||
|
||||
string slotString = subElement.GetAttributeString("slot", "None");
|
||||
InvSlotType slot = Enum.TryParse(slotString, ignoreCase: true, out InvSlotType s) ? s : InvSlotType.None;
|
||||
Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, this, ignoreLimbSlots: subElement.GetAttributeBool("forcetoslot", false), slot: slot, onSpawned: (Item item) =>
|
||||
|
||||
bool forceToSlot = subElement.GetAttributeBool("forcetoslot", false);
|
||||
int amount = subElement.GetAttributeInt("amount", 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
if (item != null && item.ParentInventory != this)
|
||||
Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, this, ignoreLimbSlots: forceToSlot, slot: slot, onSpawned: (Item item) =>
|
||||
{
|
||||
string errorMsg = $"Failed to spawn the initial item \"{item.Prefab.Identifier}\" in the inventory of \"{character.SpeciesName}\".";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("CharacterInventory:FailedToSpawnInitialItem", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
}
|
||||
});
|
||||
if (item != null && item.ParentInventory != this)
|
||||
{
|
||||
string errorMsg = $"Failed to spawn the initial item \"{item.Prefab.Identifier}\" in the inventory of \"{character.SpeciesName}\".";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("CharacterInventory:FailedToSpawnInitialItem", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,21 +178,6 @@ namespace Barotrauma
|
||||
(SlotTypes[i] == InvSlotType.Any || slots[i].Items.Count < 1);
|
||||
}
|
||||
|
||||
public bool CanBeAutoMovedToCorrectSlots(Item item)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
foreach (var allowedSlot in item.AllowedSlots)
|
||||
{
|
||||
InvSlotType slotsFree = InvSlotType.None;
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && slots[i].Empty()) { slotsFree |= SlotTypes[i]; }
|
||||
}
|
||||
if (allowedSlot == slotsFree) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void RemoveItem(Item item)
|
||||
{
|
||||
RemoveItem(item, tryEquipFromSameStack: false);
|
||||
|
||||
@@ -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()
|
||||
@@ -369,6 +383,8 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool isClosing = false;
|
||||
if ((!IsStuck && !IsJammed) || !isOpen)
|
||||
{
|
||||
@@ -394,11 +410,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 +467,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
PushCharactersAway();
|
||||
}
|
||||
if (OutsideSubmarineFixture != null && Body.Enabled)
|
||||
{
|
||||
OutsideSubmarineFixture.CollidesWith = SubmarineBody.CollidesWith;
|
||||
}
|
||||
#if CLIENT
|
||||
UpdateConvexHulls();
|
||||
#endif
|
||||
@@ -462,10 +491,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; }
|
||||
@@ -540,6 +575,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 +628,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)
|
||||
|
||||
+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,
|
||||
|
||||
@@ -446,7 +446,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(projectile.SpreadCounter);
|
||||
|
||||
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(projectile.SpreadCounter));
|
||||
}
|
||||
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))
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -247,17 +247,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;
|
||||
|
||||
@@ -452,7 +445,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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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);
|
||||
@@ -486,8 +485,8 @@ namespace Barotrauma.Items.Components
|
||||
item.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, ownerCharacter);
|
||||
item.ApplyStatusEffects(ActionType.OnUse, 1.0f, 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);
|
||||
|
||||
@@ -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,21 @@ 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(int seed)
|
||||
{
|
||||
if (seed < 0) { seed = -seed; }
|
||||
return spreadPool[seed % SpreadCounterWrapAround];
|
||||
}
|
||||
|
||||
struct HitscanResult
|
||||
{
|
||||
public Fixture Fixture;
|
||||
@@ -41,10 +57,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public const float WaterDragCoefficient = 0.1f;
|
||||
|
||||
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
|
||||
|
||||
private bool removePending;
|
||||
|
||||
public byte SpreadCounter { get; private set; }
|
||||
|
||||
//continuous collision detection is used while the projectile is moving faster than this
|
||||
const float ContinuousCollisionThreshold = 5.0f;
|
||||
|
||||
@@ -280,6 +300,8 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
SpreadCounter = (byte)(item.ID % SpreadCounterWrapAround);
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
partial void InitProjSpecific(ContentXElement element);
|
||||
@@ -292,13 +314,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);
|
||||
@@ -359,7 +381,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
#if SERVER
|
||||
launchRot = rotation;
|
||||
Item.CreateServerEvent(this, new EventData(launch: true));
|
||||
Item.CreateServerEvent(this, new EventData(launch: true, spreadCounter: (byte)(SpreadCounter - 1)));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -383,8 +405,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
launchAngle = item.body.Rotation + MathHelper.ToRadians(Spread * Rand.Range(-0.5f, 0.5f));
|
||||
launchAngle = item.body.Rotation + MathHelper.ToRadians(Spread * GetSpreadFromPool(SpreadCounter));
|
||||
}
|
||||
SpreadCounter++;
|
||||
|
||||
Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle));
|
||||
if (Hitscan)
|
||||
@@ -401,8 +424,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 +445,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 +480,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 +539,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 +595,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 +648,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 +707,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
Item.ResetWaterDragCoefficient();
|
||||
if (dropper != null)
|
||||
{
|
||||
DisableProjectileCollisions();
|
||||
@@ -755,6 +794,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,6 +880,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (target.Body.UserData is Submarine sub)
|
||||
{
|
||||
//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 dir = item.body.LinearVelocity.LengthSquared() < 0.001f ?
|
||||
contact.Manifold.LocalNormal : Vector2.Normalize(item.body.LinearVelocity);
|
||||
|
||||
@@ -886,7 +933,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 +958,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 +974,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 +1140,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; }
|
||||
|
||||
@@ -304,9 +304,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 +329,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-12
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -302,7 +311,8 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
if (item.Container != null && item.GetRootInventoryOwner() is not Character)
|
||||
{
|
||||
SetLightSourceState(false);
|
||||
lightBrightness = 0.0f;
|
||||
SetLightSourceState(false, 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -311,7 +321,8 @@ namespace Barotrauma.Items.Components
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
if (body != null && !body.Enabled)
|
||||
{
|
||||
SetLightSourceState(false);
|
||||
lightBrightness = 0.0f;
|
||||
SetLightSourceState(false, 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -338,7 +349,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 +381,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 +399,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -895,17 +947,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 +980,7 @@ namespace Barotrauma.Items.Components
|
||||
updateTimer -= deltaTime;
|
||||
}
|
||||
|
||||
if (!ignoreDelay && waitTimer > 0)
|
||||
if (AimDelay && waitTimer > 0)
|
||||
{
|
||||
waitTimer -= deltaTime;
|
||||
return;
|
||||
@@ -934,40 +990,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 +1045,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 +1114,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 +1126,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 +1276,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 +1305,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 +1365,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 +1382,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 +1440,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 +1459,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 +1532,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 +1592,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
targetCharacter = limb.character;
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
if (targetCharacter != null && !targetCharacter.Removed)
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
@@ -1458,27 +1601,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 +1630,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) =>
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -141,7 +141,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (items.Contains(item)) { return; }
|
||||
items.Add(item);
|
||||
|
||||
//keep lowest-condition items at the top of the stack
|
||||
int index = 0;
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
if (items[i].Condition > item.Condition)
|
||||
{
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
items.Insert(index, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -100,7 +100,18 @@ namespace Barotrauma
|
||||
private bool hasComponentsToDraw;
|
||||
|
||||
public PhysicsBody body;
|
||||
private float waterDragCoefficient;
|
||||
private readonly float originalWaterDragCoefficient;
|
||||
private float? overrideWaterDragCoefficient;
|
||||
public float WaterDragCoefficient
|
||||
{
|
||||
get => overrideWaterDragCoefficient ?? originalWaterDragCoefficient;
|
||||
set => overrideWaterDragCoefficient = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the override value -> falls back to using the original value defined in the xml.
|
||||
/// </summary>
|
||||
public void ResetWaterDragCoefficient() => overrideWaterDragCoefficient = null;
|
||||
|
||||
public readonly XElement StaticBodyConfig;
|
||||
|
||||
@@ -143,6 +154,8 @@ namespace Barotrauma
|
||||
private readonly bool[] hasStatusEffectsOfType = new bool[Enum.GetValues(typeof(ActionType)).Length];
|
||||
private readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
|
||||
|
||||
public Action OnInteract;
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; protected set; }
|
||||
|
||||
private bool? hasInGameEditableProperties;
|
||||
@@ -424,8 +437,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Color? HighlightColor;
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by status effects or conditionals to check whether the item is contained inside something
|
||||
/// </summary>
|
||||
@@ -460,6 +471,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Color? HighlightColor;
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
|
||||
/// <summary>
|
||||
@@ -472,7 +485,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (AiTarget != null)
|
||||
{
|
||||
AiTarget.SonarLabel = !string.IsNullOrEmpty(value) && value.Length > 200 ? value.Substring(200) : value;
|
||||
string trimmedStr = !string.IsNullOrEmpty(value) && value.Length > 250 ? value.Substring(250) : value;
|
||||
AiTarget.SonarLabel = TextManager.Get(trimmedStr).Fallback(trimmedStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -949,7 +963,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!Physics.TryParseCollisionCategory(collisionCategoryStr, out Category cat))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid collision category in item \"" + Name+"\" (" + collisionCategoryStr + ")");
|
||||
DebugConsole.ThrowError("Invalid collision category in item \"" + Name + "\" (" + collisionCategoryStr + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -988,6 +1002,7 @@ namespace Barotrauma
|
||||
case "infectedsprite":
|
||||
case "damagedinfectedsprite":
|
||||
case "swappableitem":
|
||||
case "skillrequirementhint":
|
||||
break;
|
||||
case "staticbody":
|
||||
StaticBodyConfig = subElement;
|
||||
@@ -1056,8 +1071,7 @@ namespace Barotrauma
|
||||
if (body != null)
|
||||
{
|
||||
body.Submarine = submarine;
|
||||
waterDragCoefficient = bodyElement.GetAttributeFloat("waterdragcoefficient",
|
||||
GetComponent<Projectile>() != null || GetComponent<Throwable>() != null ? 0.1f : 1.0f);
|
||||
originalWaterDragCoefficient = bodyElement.GetAttributeFloat("waterdragcoefficient", 5.0f);
|
||||
}
|
||||
|
||||
//cache connections into a dictionary for faster lookups
|
||||
@@ -1656,7 +1670,7 @@ namespace Barotrauma
|
||||
|
||||
if (effect.TargetSlot > -1)
|
||||
{
|
||||
if (OwnInventory.FindIndex(containedItem) != effect.TargetSlot) { continue; }
|
||||
if (!OwnInventory.GetItemsAt(effect.TargetSlot).Contains(containedItem)) { continue; }
|
||||
}
|
||||
|
||||
hasTargets = true;
|
||||
@@ -1711,8 +1725,15 @@ namespace Barotrauma
|
||||
{
|
||||
targets.AddRange(character.AnimController.Limbs.ToList());
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Limb) && limb == null && effect.targetLimbs != null)
|
||||
{
|
||||
foreach (var characterLimb in character.AnimController.Limbs)
|
||||
{
|
||||
if (effect.targetLimbs.Contains(characterLimb.type)) { targets.Add(characterLimb); }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Limb) && limb != null)
|
||||
{
|
||||
targets.Add(limb);
|
||||
}
|
||||
@@ -1727,7 +1748,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Indestructible || InvulnerableToDamage) { return new AttackResult(); }
|
||||
|
||||
float damageAmount = attack.GetItemDamage(deltaTime);
|
||||
float damageAmount = attack.GetItemDamage(deltaTime, Prefab.ItemDamageMultiplier);
|
||||
Condition -= damageAmount;
|
||||
|
||||
if (damageAmount >= Prefab.OnDamagedThreshold)
|
||||
@@ -1975,7 +1996,10 @@ namespace Barotrauma
|
||||
|
||||
if (Math.Abs(body.LinearVelocity.X) > 0.01f || Math.Abs(body.LinearVelocity.Y) > 0.01f || transformDirty)
|
||||
{
|
||||
UpdateTransform();
|
||||
if (body.CollisionCategories != Category.None)
|
||||
{
|
||||
UpdateTransform();
|
||||
}
|
||||
if (CurrentHull == null && Level.Loaded != null && body.SimPosition.Y < ConvertUnits.ToSimUnits(Level.MaxEntityDepth))
|
||||
{
|
||||
Spawner?.AddItemToRemoveQueue(this);
|
||||
@@ -1994,8 +2018,7 @@ namespace Barotrauma
|
||||
if (needsWaterCheck)
|
||||
{
|
||||
bool wasInWater = inWater;
|
||||
inWater = IsInWater();
|
||||
bool waterProof = WaterProof;
|
||||
inWater = IsInWater() && !WaterProof;
|
||||
if (inWater)
|
||||
{
|
||||
//the item has gone through the surface of the water
|
||||
@@ -2010,15 +2033,19 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Item container = this.Container;
|
||||
while (!waterProof && container != null)
|
||||
while (container != null)
|
||||
{
|
||||
waterProof = container.WaterProof;
|
||||
if (container.WaterProof)
|
||||
{
|
||||
inWater = false;
|
||||
break;
|
||||
}
|
||||
container = container.Container;
|
||||
}
|
||||
}
|
||||
if (hasWaterStatusEffects && condition > 0.0f)
|
||||
{
|
||||
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||
ApplyStatusEffects(inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2144,7 +2171,7 @@ namespace Barotrauma
|
||||
Vector2 frontVel = body.FarseerBody.GetLinearVelocityFromLocalPoint(localFront);
|
||||
|
||||
float speed = frontVel.Length();
|
||||
float drag = speed * speed * waterDragCoefficient * volume * Physics.NeutralDensity;
|
||||
float drag = speed * speed * WaterDragCoefficient * volume * Physics.NeutralDensity;
|
||||
//very small drag on active projectiles to prevent affecting their trajectories much
|
||||
if (body.FarseerBody.IsBullet) { drag *= 0.1f; }
|
||||
Vector2 dragVec = -frontVel / speed * drag;
|
||||
@@ -2631,12 +2658,14 @@ namespace Barotrauma
|
||||
if (user == Character.Controlled) { GUI.ForceMouseOn(null); }
|
||||
if (tempRequiredSkill != null) { requiredSkill = tempRequiredSkill; }
|
||||
#endif
|
||||
if (ic.CanBeSelected && !(ic is Door)) { selected = true; }
|
||||
if (ic.CanBeSelected && ic is not Door) { selected = true; }
|
||||
}
|
||||
}
|
||||
|
||||
if (!picked) { return false; }
|
||||
|
||||
OnInteract?.Invoke();
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
if (user.SelectedItem == this)
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Security.Cryptography;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
readonly struct SkillRequirementHint
|
||||
{
|
||||
public readonly Identifier Skill;
|
||||
public readonly float Level;
|
||||
public readonly LocalizedString SkillName;
|
||||
|
||||
public LocalizedString GetFormattedText(int skillLevel, string levelColorTag) =>
|
||||
$"{SkillName} {Level} (‖color:{levelColorTag}‖{skillLevel}‖color:end‖)";
|
||||
|
||||
public SkillRequirementHint(ContentXElement element)
|
||||
{
|
||||
Skill = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
Level = element.GetAttributeFloat("level", 0);
|
||||
SkillName = TextManager.Get("skillname." + Skill);
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct DeconstructItem
|
||||
{
|
||||
public readonly Identifier ItemIdentifier;
|
||||
@@ -443,6 +460,8 @@ namespace Barotrauma
|
||||
//Containers (by identifiers or tags) that this item should be placed in. These are preferences, which are not enforced.
|
||||
public ImmutableArray<PreferredContainer> PreferredContainers { get; private set; }
|
||||
|
||||
public ImmutableArray<SkillRequirementHint> SkillRequirementHints { get; private set; }
|
||||
|
||||
public SwappableItem SwappableItem
|
||||
{
|
||||
get;
|
||||
@@ -660,6 +679,9 @@ namespace Barotrauma
|
||||
[Serialize(1f, IsPropertySaveable.No)]
|
||||
public float ExplosionDamageMultiplier { get; private set; }
|
||||
|
||||
[Serialize(1f, IsPropertySaveable.No)]
|
||||
public float ItemDamageMultiplier { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool DamagedByProjectiles { get; private set; }
|
||||
|
||||
@@ -772,6 +794,18 @@ namespace Barotrauma
|
||||
[Serialize(1f, IsPropertySaveable.No, description: "How much the bots prioritize this item when they seek for items. For example, bots prioritize less exosuit than the other diving suits. Defaults to 1. Note that there's also a specific CombatPriority for items that can be used as weapons.")]
|
||||
public float BotPriority { get; private set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool ShowNameInHealthBar { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description:"Should the bots shoot at this item with turret or not? Disabled by default.")]
|
||||
public bool IsAITurretTarget { get; private set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "How much the bots prioritize shooting this item with turrets? Defaults to 1. Distance to the target affects the decision making.")]
|
||||
public float AITurretPriority { get; private set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "How much the bots prioritize shooting this item with slow turrets, like railguns? Defaults to 1. Not used if AITurretPriority is 0. Distance to the target affects the decision making.")]
|
||||
public float AISlowTurretPriority { get; private set; }
|
||||
|
||||
protected override Identifier DetermineIdentifier(XElement element)
|
||||
{
|
||||
Identifier identifier = base.DetermineIdentifier(element);
|
||||
@@ -874,6 +908,15 @@ namespace Barotrauma
|
||||
SerializableProperty.DeserializeProperties(this, ConfigElement);
|
||||
|
||||
LoadDescription(ConfigElement);
|
||||
var skillRequirementHints = new List<SkillRequirementHint>();
|
||||
foreach (var skillRequirementHintElement in ConfigElement.GetChildElements("SkillRequirementHint"))
|
||||
{
|
||||
skillRequirementHints.Add(new SkillRequirementHint(skillRequirementHintElement));
|
||||
}
|
||||
if (skillRequirementHints.Any())
|
||||
{
|
||||
SkillRequirementHints = skillRequirementHints.ToImmutableArray();
|
||||
}
|
||||
|
||||
var allowDroppingOnSwapWith = ConfigElement.GetAttributeIdentifierArray("allowdroppingonswapwith", Array.Empty<Identifier>());
|
||||
AllowDroppingOnSwapWith = allowDroppingOnSwapWith.ToImmutableHashSet();
|
||||
@@ -1167,7 +1210,10 @@ namespace Barotrauma
|
||||
public bool CanBeBoughtFrom(Location.StoreInfo store, out PriceInfo priceInfo)
|
||||
{
|
||||
priceInfo = GetPriceInfo(store);
|
||||
return priceInfo is { CanBeBought: true } && (store?.Location.LevelData?.Difficulty ?? 0) >= priceInfo.MinLevelDifficulty;
|
||||
return
|
||||
priceInfo is { CanBeBought: true } &&
|
||||
(store?.Location.LevelData?.Difficulty ?? 0) >= priceInfo.MinLevelDifficulty &&
|
||||
(!priceInfo.MinReputation.Any() || priceInfo.MinReputation.Any(p => store?.Location.Faction?.Prefab.Identifier == p.Key || store?.Location.SecondaryFaction?.Prefab.Identifier == p.Key));
|
||||
}
|
||||
|
||||
public bool CanBeBoughtFrom(Location location)
|
||||
@@ -1179,6 +1225,15 @@ namespace Barotrauma
|
||||
if (priceInfo == null) { continue; }
|
||||
if (!priceInfo.CanBeBought) { continue; }
|
||||
if (location.LevelData.Difficulty < priceInfo.MinLevelDifficulty) { continue; }
|
||||
if (priceInfo.MinReputation.Any())
|
||||
{
|
||||
if (!priceInfo.MinReputation.Any(p =>
|
||||
location?.Faction?.Prefab.Identifier == p.Key ||
|
||||
location?.SecondaryFaction?.Prefab.Identifier == p.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1335,11 +1390,43 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public Identifier VariantOf { get; }
|
||||
|
||||
public ItemPrefab ParentPrefab { get; set; }
|
||||
|
||||
public void InheritFrom(ItemPrefab parent)
|
||||
{
|
||||
ConfigElement = originalElement.CreateVariantXML(parent.ConfigElement).FromPackage(ConfigElement.ContentPackage);
|
||||
ConfigElement = originalElement.CreateVariantXML(parent.ConfigElement, CheckXML).FromPackage(ConfigElement.ContentPackage);
|
||||
ParseConfigElement(parent);
|
||||
|
||||
void CheckXML(XElement originalElement, XElement variantElement, XElement result)
|
||||
{
|
||||
if (result == null) { return; }
|
||||
if (result.Name.ToIdentifier() == "RequiredItem" &&
|
||||
result.Parent?.Name.ToIdentifier() == "Fabricate")
|
||||
{
|
||||
int originalAmount = originalElement.GetAttributeInt("amount", 1);
|
||||
Identifier originalIdentifier = originalElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
if (variantElement == null)
|
||||
{
|
||||
//if the variant defines some fabrication requirements, we probably don't want to inherit anything extra from the base item?
|
||||
if (this.originalElement.GetChildElement("Fabricate")?.GetChildElement("RequiredItem") != null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in item variant \"{Identifier}\": " +
|
||||
$"the item inherits the fabrication requirement of x{originalAmount} \"{originalIdentifier}\" from the base item \"{parent.Identifier}\". " +
|
||||
$"If this is not intentional, you can use empty <RequiredItem /> elements in the item variant to remove any excess inherited fabrication requirements.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Identifier resultIdentifier = result.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
if (originalAmount > 1 && variantElement.GetAttribute("amount") == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in item variant \"{Identifier}\": " +
|
||||
$"the base item \"{parent.Identifier}\" requires x{originalAmount} \"{originalIdentifier}\" to fabricate. " +
|
||||
$"The variant only overrides the required item, not the amount, resulting in a requirement of x{originalAmount} \"{resultIdentifier}\". "+
|
||||
"Specify the amount in the variant to fix this.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Only affects when ItemContainer.hideItems is false. Doesn't override the value.
|
||||
/// </summary>
|
||||
public bool? Hide;
|
||||
public bool Hide;
|
||||
|
||||
public float Rotation;
|
||||
|
||||
@@ -197,11 +197,14 @@ namespace Barotrauma
|
||||
bool isEmpty = parentItem.OwnInventory.IsEmpty();
|
||||
if (RequireEmpty && !isEmpty) { return false; }
|
||||
if (MatchOnEmpty && isEmpty) { return true; }
|
||||
foreach (Item contained in parentItem.ContainedItems)
|
||||
foreach (var container in parentItem.GetComponents<Items.Components.ItemContainer>())
|
||||
{
|
||||
if (TargetSlot > -1 && parentItem.OwnInventory.FindIndex(contained) != TargetSlot) { continue; }
|
||||
if ((!ExcludeBroken || contained.Condition > 0.0f) && (!ExcludeFullCondition || !contained.IsFullCondition) && MatchesItem(contained)) { return true; }
|
||||
if (CheckContained(contained)) { return true; }
|
||||
foreach (Item contained in container.Inventory.AllItems)
|
||||
{
|
||||
if (TargetSlot > -1 && parentItem.OwnInventory.FindIndex(contained) != TargetSlot) { continue; }
|
||||
if ((!ExcludeBroken || contained.Condition > 0.0f) && (!ExcludeFullCondition || !contained.IsFullCondition) && MatchesItem(contained)) { return true; }
|
||||
if (CheckContained(contained)) { return true; }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -221,9 +224,9 @@ namespace Barotrauma
|
||||
new XAttribute("rotation", Rotation),
|
||||
new XAttribute("setactive", SetActive));
|
||||
|
||||
if (Hide.HasValue)
|
||||
if (Hide)
|
||||
{
|
||||
element.Add(new XAttribute(nameof(Hide), Hide.Value));
|
||||
element.Add(new XAttribute(nameof(Hide), true));
|
||||
}
|
||||
if (ItemPos.HasValue)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user