Unstable 0.1500.0.0

This commit is contained in:
Markus Isberg
2021-08-26 21:08:21 +09:00
parent 265a2e7ab3
commit 501e02c026
245 changed files with 9775 additions and 2034 deletions
@@ -204,8 +204,8 @@ namespace Barotrauma.Items.Components
target.InitializeLinks();
if (!item.linkedTo.Contains(target.item)) item.linkedTo.Add(target.item);
if (!target.item.linkedTo.Contains(item)) target.item.linkedTo.Add(item);
if (!item.linkedTo.Contains(target.item)) { item.linkedTo.Add(target.item); }
if (!target.item.linkedTo.Contains(item)) { target.item.linkedTo.Add(item); }
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
@@ -291,7 +291,7 @@ namespace Barotrauma.Items.Components
List<MapEntity> removedEntities = item.linkedTo.Where(e => e.Removed).ToList();
foreach (MapEntity removed in removedEntities) item.linkedTo.Remove(removed);
foreach (MapEntity removed in removedEntities) { item.linkedTo.Remove(removed); }
if (!item.linkedTo.Any(e => e is Hull) && !DockingTarget.item.linkedTo.Any(e => e is Hull))
{
@@ -306,9 +306,8 @@ namespace Barotrauma.Items.Components
if (myWayPoint != null && targetWayPoint != null)
{
myWayPoint.FindHull();
myWayPoint.linkedTo.Add(targetWayPoint);
targetWayPoint.FindHull();
targetWayPoint.linkedTo.Add(myWayPoint);
myWayPoint.ConnectTo(targetWayPoint);
}
}
}
@@ -597,8 +596,9 @@ namespace Barotrauma.Items.Components
{
hullRects[i].X -= expand;
hullRects[i].Width += expand * 2;
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
hulls[i].AddToGrid(subs[i]);
hulls[i].FreeID();
@@ -716,8 +716,9 @@ namespace Barotrauma.Items.Components
{
hullRects[i].Y += expand;
hullRects[i].Height += expand * 2;
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
hulls[i].AddToGrid(subs[i]);
hulls[i].FreeID();
@@ -873,8 +874,10 @@ namespace Barotrauma.Items.Components
{
myWayPoint.FindHull();
myWayPoint.linkedTo.Remove(targetWayPoint);
myWayPoint.OnLinksChanged?.Invoke(myWayPoint);
targetWayPoint.FindHull();
targetWayPoint.linkedTo.Remove(myWayPoint);
targetWayPoint.OnLinksChanged?.Invoke(targetWayPoint);
}
}
@@ -1058,7 +1061,7 @@ namespace Barotrauma.Items.Components
}
}
if (!item.linkedTo.Any()) return;
if (!item.linkedTo.Any()) { return; }
List<MapEntity> linked = new List<MapEntity>(item.linkedTo);
foreach (MapEntity entity in linked)
@@ -475,6 +475,21 @@ namespace Barotrauma.Items.Components
}
}
public override void ReceiveSignal(Signal signal, Connection connection)
{
switch (connection.Name)
{
case "activate":
case "use":
case "trigger_in":
if (signal.value != "0")
{
item.Use(1.0f, null);
}
break;
}
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
@@ -33,7 +33,7 @@ namespace Barotrauma.Items.Components
private bool attachable, attached, attachedByDefault;
private Voronoi2.VoronoiCell attachTargetCell;
private readonly PhysicsBody body;
private PhysicsBody body;
public PhysicsBody Pusher
{
get;
@@ -780,7 +780,19 @@ namespace Barotrauma.Items.Components
DeattachFromWall();
}
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
attachTargetCell = null;
if (Pusher != null)
{
GameMain.World.Remove(Pusher.FarseerBody);
Pusher = null;
}
body = null;
}
public override XElement Save(XElement parentElement)
{
if (!attachable)
@@ -1,11 +1,29 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class IdCard : Pickable
{
[Serialize(CharacterTeamType.None, true, alwaysUseInstanceValues: true)]
public CharacterTeamType TeamID
{
get;
set;
}
[Serialize(0, true, alwaysUseInstanceValues: true)]
public int SubmarineSpecificID
{
get;
set;
}
private JobPrefab cachedJobPrefab;
private string cachedName;
public IdCard(Item item, XElement element) : base(item, element)
{
@@ -20,6 +38,8 @@ namespace Barotrauma.Items.Components
item.AddTag("jobid:" + info.Job.Prefab.Identifier);
}
TeamID = info.TeamID;
var head = info.Head;
if (info != null && head != null)
@@ -50,5 +70,48 @@ namespace Barotrauma.Items.Components
base.Unequip(character);
character.Info?.CheckDisguiseStatus(true, this);
}
public JobPrefab GetJob()
{
if (cachedJobPrefab != null)
{
return cachedJobPrefab;
}
foreach (string tag in item.GetTags())
{
if (tag.StartsWith("jobid:"))
{
string jobIdentifier = tag.Split(':').Last();
if (JobPrefab.Get(jobIdentifier) is { } jobPrefab)
{
cachedJobPrefab = jobPrefab;
return jobPrefab;
}
}
}
return null;
}
public string GetName()
{
if (cachedName != null)
{
return cachedName;
}
foreach (string tag in item.GetTags())
{
if (tag.StartsWith("name:"))
{
string ownerName = tag.Split(':').Last();
cachedName = ownerName;
return ownerName;
}
}
return null;
}
}
}
@@ -61,7 +61,7 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
Attack = new Attack(subElement, item.Name + ", MeleeWeapon");
Attack = new Attack(subElement, item.Name + ", MeleeWeapon", item);
Attack.DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent());
}
item.IsShootable = true;
@@ -95,7 +95,7 @@ namespace Barotrauma.Items.Components
if (hitPos < MathHelper.PiOver4) { return false; }
ActivateNearbySleepingCharacters();
reloadTimer = reload;
reloadTimer = reload / (1 + character.GetStatValue(StatTypes.MeleeAttackSpeed));
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall;
@@ -5,6 +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;
@@ -52,6 +53,21 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(0f, true, description: "The time required for a charge-type turret to charge up before able to fire.")]
public float MaxChargeTime
{
get;
private set;
}
private enum ChargingState
{
Inactive,
WindingUp,
WindingDown,
}
private ChargingState currentChargingState;
public Vector2 TransformedBarrelPos
{
get
@@ -62,7 +78,10 @@ namespace Barotrauma.Items.Components
return Vector2.Transform(flippedPos, bodyTransform);
}
}
private float currentChargeTime;
private bool tryingToCharge;
public RangedWeapon(Item item, XElement element)
: base(item, element)
{
@@ -88,10 +107,41 @@ namespace Barotrauma.Items.Components
if (ReloadTimer < 0.0f)
{
ReloadTimer = 0.0f;
IsActive = false;
// was this an optimization or related to something else? currently disabled for charge-type weapons
//IsActive = false;
if (MaxChargeTime == 0.0f)
{
IsActive = false;
return;
}
}
float previousChargeTime = currentChargeTime;
float chargeDeltaTime = tryingToCharge ? deltaTime : -deltaTime;
currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f, MaxChargeTime);
tryingToCharge = false;
if (currentChargeTime == 0f)
{
currentChargingState = ChargingState.Inactive;
}
else if (currentChargeTime < previousChargeTime)
{
currentChargingState = ChargingState.WindingDown;
}
else
{
// if we are charging up or at maxed charge, remain winding up
currentChargingState = ChargingState.WindingUp;
}
UpdateProjSpecific(deltaTime);
}
partial void UpdateProjSpecific(float deltaTime);
private float GetSpread(Character user)
{
float degreeOfFailure = 1.0f - DegreeOfSuccess(user);
@@ -102,11 +152,16 @@ namespace Barotrauma.Items.Components
private readonly List<Body> limbBodies = new List<Body>();
public override bool Use(float deltaTime, Character character = null)
{
tryingToCharge = true;
if (character == null || character.Removed) { return false; }
if ((item.RequireAimToUse && !character.IsKeyDown(InputType.Aim)) || ReloadTimer > 0.0f) { return false; }
if (currentChargeTime < MaxChargeTime) { return false; }
IsActive = true;
ReloadTimer = reload;
ReloadTimer = reload / (1 + character.GetStatValue(StatTypes.RangedAttackSpeed));
currentChargeTime = 0f;
character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, item);
if (item.AiTarget != null)
{
@@ -711,7 +711,27 @@ namespace Barotrauma.Items.Components
bool CheckItems(RelatedItem relatedItem, IEnumerable<Item> itemList)
{
bool Predicate(Item it) => it != null && it.Condition > 0.0f && relatedItem.MatchesItem(it);
bool Predicate(Item it)
{
if (it == null || it.Condition <= 0.0f || !relatedItem.MatchesItem(it)) { return false; }
if (item.Submarine != null)
{
var idCard = it.GetComponent<IdCard>();
if (idCard != null)
{
//id cards don't work in enemy subs (except on items that only require the default "idcard" tag)
if (idCard.TeamID != CharacterTeamType.None && idCard.TeamID != item.Submarine.TeamID && relatedItem.Identifiers.Any(id => id != "idcard"))
{
return false;
}
else if (idCard.SubmarineSpecificID != 0 && item.Submarine.SubmarineSpecificIDTag != idCard.SubmarineSpecificID)
{
return false;
}
}
}
return true;
};
bool shouldBreak = false;
bool inEditor = false;
#if CLIENT
@@ -140,6 +140,20 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, false, description: "Should the items be injected into the user.")]
public bool AutoInject
{
get;
set;
}
[Serialize(0.5f, false, description: "The rotation in which the contained sprites are drawn (in degrees).")]
public float AutoInjectThreshold
{
get;
set;
}
[Serialize(false, false)]
public bool RemoveContainedItemsOnDeconstruct { get; set; }
@@ -237,9 +251,23 @@ namespace Barotrauma.Items.Components
SpawnAlwaysContainedItems();
}
if (item.ParentInventory is CharacterInventory)
if (item.ParentInventory is CharacterInventory ownerInventory)
{
item.SetContainedItemPositions();
if (AutoInject)
{
if (ownerInventory?.Owner is Character ownerCharacter &&
ownerCharacter.HealthPercentage / 100f <= AutoInjectThreshold &&
ownerCharacter.HasEquippedItem(item))
{
foreach (Item item in Inventory.AllItemsMod)
{
item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter);
}
}
}
}
else if (item.body != null &&
item.body.Enabled &&
@@ -256,6 +284,7 @@ namespace Barotrauma.Items.Components
foreach (var activeContainedItem in activeContainedItems)
{
Item contained = activeContainedItem.Item;
if (activeContainedItem.ExcludeBroken && contained.Condition <= 0.0f) { continue; }
StatusEffect effect = activeContainedItem.StatusEffect;
@@ -299,6 +328,8 @@ namespace Barotrauma.Items.Components
}
}
}
character.CheckTalents(AbilityEffectType.OnOpenItemContainer, item);
return base.Select(character);
}
@@ -122,6 +122,12 @@ namespace Barotrauma.Items.Components
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f);
Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f);
if (item.GetComponent<Repairable>()?.IsTinkering ?? false)
{
currForce *= 2.5f;
}
//less effective when in a bad condition
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition);
if (item.Submarine.FlippedX) { currForce *= -1; }
@@ -4,8 +4,8 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Xml.Linq;
using Barotrauma.Abilities;
namespace Barotrauma.Items.Components
{
@@ -24,6 +24,12 @@ namespace Barotrauma.Items.Components
private Character user;
public float FabricationSpeedMultiplier
{
get;
set;
}
private ItemContainer inputContainer, outputContainer;
[Serialize(1.0f, true)]
@@ -240,8 +246,10 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem))
var availableIngredients = GetAvailableIngredients();
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user))
{
FabricationSpeedMultiplier = 1f;
CancelFabricating();
return;
}
@@ -278,38 +286,56 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0) { Voltage = 1.0f; }
timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f);
timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f) * FabricationSpeedMultiplier;
FabricationSpeedMultiplier = 1f;
UpdateRequiredTimeProjSpecific();
if (timeUntilReady > 0.0f) { return; }
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
var availableIngredients = GetAvailableIngredients();
foreach (FabricationRecipe.RequiredItem ingredient in fabricatedItem.RequiredItems)
{
for (int i = 0; i < ingredient.Amount; i++)
fabricatedItem.RequiredItems.ForEach(requiredItem => {
for (int usedPrefabsAmount = 0; usedPrefabsAmount < requiredItem.Amount; usedPrefabsAmount++)
{
var availableItem = availableIngredients.FirstOrDefault(it =>
it != null && ingredient.ItemPrefabs.Contains(it.Prefab) &&
it.ConditionPercentage >= ingredient.MinCondition * 100.0f &&
it.ConditionPercentage <= ingredient.MaxCondition * 100.0f);
if (availableItem == null) { continue; }
if (ingredient.UseCondition && availableItem.ConditionPercentage - ingredient.MinCondition * 100 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
{
availableItem.Condition -= availableItem.Prefab.Health * ingredient.MinCondition;
continue;
if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; }
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
var availablePrefab = availablePrefabs.FirstOrDefault(potentialPrefab =>
{
return potentialPrefab.ConditionPercentage >= requiredItem.MinCondition * 100.0f &&
potentialPrefab.ConditionPercentage <= requiredItem.MaxCondition * 100.0f;
});
if (availablePrefab == null) { continue; }
if (requiredItem.UseCondition && availablePrefab.ConditionPercentage - requiredItem.MinCondition * 100 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
{
availablePrefab.Condition -= availablePrefab.Prefab.Health * requiredItem.MinCondition;
continue;
}
availablePrefabs.Remove(availablePrefab);
Entity.Spawner.AddToRemoveQueue(availablePrefab);
inputContainer.Inventory.RemoveItem(availablePrefab);
}
availableIngredients.Remove(availableItem);
Entity.Spawner.AddToRemoveQueue(availableItem);
inputContainer.Inventory.RemoveItem(availableItem);
}
}
});
Character tempUser = user;
int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health);
for (int i = 0; i < fabricatedItem.Amount; i++)
var itemsCreated = new AbilityValue(fabricatedItem.Amount);
foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID))
{
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, (fabricatedItem.TargetItem, itemsCreated));
}
tempUser.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, (fabricatedItem.TargetItem, itemsCreated));
for (int i = 0; i < (int)itemsCreated.Value; i++)
{
if (i < amountFittingContainer)
{
@@ -339,9 +365,13 @@ namespace Barotrauma.Items.Components
foreach (Skill skill in fabricatedItem.RequiredSkills)
{
float userSkill = user.GetSkillLevel(skill.Identifier);
float addedSkill = skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f);
var addedSkillValue = new AbilityValue(0f);
user.CheckTalents(AbilityEffectType.OnItemFabricationSkillGain, addedSkillValue);
user.Info.IncreaseSkillLevel(
skill.Identifier,
skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f),
addedSkill + addedSkillValue.Value,
user.Position + Vector2.UnitY * 150.0f);
}
}
@@ -365,24 +395,36 @@ namespace Barotrauma.Items.Components
partial void UpdateRequiredTimeProjSpecific();
private bool CanBeFabricated(FabricationRecipe fabricableItem)
private bool CanBeFabricated(FabricationRecipe fabricableItem, Dictionary<string, List<Item>> availableIngredients, Character character)
{
if (fabricableItem == null) { return false; }
List<Item> availableIngredients = GetAvailableIngredients();
return CanBeFabricated(fabricableItem, availableIngredients);
}
if (fabricableItem.RequiresRecipe && (character == null || !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier))) { return false; }
private bool CanBeFabricated(FabricationRecipe fabricableItem, IEnumerable<Item> availableIngredients)
{
if (fabricableItem == null) { return false; }
foreach (FabricationRecipe.RequiredItem requiredItem in fabricableItem.RequiredItems)
return fabricableItem.RequiredItems.All(requiredItem =>
{
if (availableIngredients.Count(it => IsItemValidIngredient(it, requiredItem)) < requiredItem.Amount)
int availablePrefabsAmount = 0;
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
{
return false;
if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; }
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
foreach (Item availablePrefab in availablePrefabs)
{
if (availablePrefab.Condition / availablePrefab.Prefab.Health >= requiredItem.MinCondition &&
availablePrefab.Condition / availablePrefab.Prefab.Health <= requiredItem.MaxCondition)
{
availablePrefabsAmount++;
}
if (availablePrefabsAmount >= requiredItem.Amount)
{
return true;
}
}
}
}
return true;
return false;
});
}
private float GetRequiredTime(FabricationRecipe fabricableItem, Character user)
@@ -416,7 +458,7 @@ namespace Barotrauma.Items.Components
/// Get a list of all items available in the input container and linked containers
/// </summary>
/// <returns></returns>
private List<Item> GetAvailableIngredients()
private Dictionary<string, List<Item>> GetAvailableIngredients()
{
List<Item> availableIngredients = new List<Item>();
availableIngredients.AddRange(inputContainer.Inventory.AllItems);
@@ -448,7 +490,19 @@ namespace Barotrauma.Items.Components
}
#endif
return availableIngredients;
Dictionary<string, List<Item>> ingredientsDictionary = new Dictionary<string, List<Item>>();
for (int i = 0; i < availableIngredients.Count; i++)
{
var itemIdentifier = availableIngredients[i].prefab.Identifier;
if (!ingredientsDictionary.ContainsKey(itemIdentifier))
{
ingredientsDictionary[itemIdentifier] = new List<Item>(availableIngredients.Count);
}
ingredientsDictionary[itemIdentifier].Add(availableIngredients[i]);
}
return ingredientsDictionary;
}
/// <summary>
@@ -463,40 +517,41 @@ namespace Barotrauma.Items.Components
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
var availableIngredients = GetAvailableIngredients();
foreach (var requiredItem in targetItem.RequiredItems)
{
targetItem.RequiredItems.ForEach(requiredItem => {
for (int i = 0; i < requiredItem.Amount; i++)
{
var matchingItem = availableIngredients.Find(it => !usedItems.Contains(it) && IsItemValidIngredient(it, requiredItem));
if (matchingItem == null) { continue; }
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
{
if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; }
availableIngredients.Remove(matchingItem);
if (matchingItem.ParentInventory == inputContainer.Inventory)
{
//already in input container, all good
usedItems.Add(matchingItem);
}
else //in another inventory, we need to move the item
{
if (!inputContainer.Inventory.CanBePut(matchingItem))
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
var availablePrefab = availablePrefabs.FirstOrDefault(potentialPrefab =>
{
var unneededItem = inputContainer.Inventory.AllItems.FirstOrDefault(it => !usedItems.Contains(it));
unneededItem?.Drop(null, createNetworkEvent: !isClient);
}
inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: !isClient);
}
}
}
}
return !usedItems.Contains(potentialPrefab) &&
potentialPrefab.ConditionPercentage >= requiredItem.MinCondition * 100.0f &&
potentialPrefab.ConditionPercentage <= requiredItem.MaxCondition * 100.0f;
});
if (availablePrefab == null) { continue; }
private bool IsItemValidIngredient(Item item, FabricationRecipe.RequiredItem requiredItem)
{
return
item != null &&
requiredItem.ItemPrefabs.Contains(item.prefab) &&
item.Condition / item.Prefab.Health >= requiredItem.MinCondition &&
item.Condition / item.Prefab.Health <= requiredItem.MaxCondition;
availablePrefabs.Remove(availablePrefab);
if (availablePrefab.ParentInventory == inputContainer.Inventory)
{
//already in input container, all good
usedItems.Add(availablePrefab);
}
else //in another inventory, we need to move the item
{
if (!inputContainer.Inventory.CanBePut(availablePrefab))
{
var unneededItem = inputContainer.Inventory.AllItems.FirstOrDefault(it => !usedItems.Contains(it));
unneededItem?.Drop(null, createNetworkEvent: !isClient);
}
inputContainer.Inventory.TryPutItem(availablePrefab, user: null, createNetworkEvent: !isClient);
}
}
}
});
}
public override XElement Save(XElement parentElement)
@@ -7,10 +7,15 @@ namespace Barotrauma.Items.Components
{
partial class MiniMap : Powered
{
class HullData
internal class HullData
{
public float? Oxygen;
public float? Water;
public float? HullOxygenAmount,
HullWaterAmount;
public float? ReceivedOxygenAmount,
ReceivedWaterAmount;
public readonly HashSet<IdCard> Cards = new HashSet<IdCard>();
public bool Distort;
public float DistortionTimer;
@@ -45,17 +50,45 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize(true, true, description: "Enable hull status mode.")]
public bool EnableHullStatus
{
get;
set;
}
[Editable, Serialize(true, true, description: "Enable electrical view mode.")]
public bool EnableElectricalView
{
get;
set;
}
[Editable, Serialize(true, true, description: "Enable hull condition mode.")]
public bool EnableHullCondition
{
get;
set;
}
[Editable, Serialize(true, true, description: "Enable item finder mode.")]
public bool EnableItemFinder
{
get;
set;
}
public MiniMap(Item item, XElement element)
: base(item, element)
{
IsActive = true;
hullDatas = new Dictionary<Hull, HullData>();
InitProjSpecific(element);
InitProjSpecific();
}
partial void InitProjSpecific(XElement element);
partial void InitProjSpecific();
public override void Update(float deltaTime, Camera cam)
public override void Update(float deltaTime, Camera cam)
{
//periodically reset all hull data
//(so that outdated hull info won't be shown if detectors stop sending signals)
@@ -65,13 +98,29 @@ namespace Barotrauma.Items.Components
{
if (!hullData.Distort)
{
hullData.Oxygen = null;
hullData.Water = null;
hullData.ReceivedOxygenAmount = null;
hullData.ReceivedWaterAmount = null;
}
}
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
}
#if CLIENT
if (cardRefreshTimer > cardRefreshDelay)
{
if (item.Submarine is { } sub)
{
UpdateIDCards(sub);
}
cardRefreshTimer = 0;
}
else
{
cardRefreshTimer += deltaTime;
}
#endif
currPowerConsumption = powerConsumption;
currPowerConsumption *= MathHelper.Lerp(1.5f, 1.0f, item.Condition / item.MaxCondition);
@@ -81,7 +130,7 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
}
}
public override bool Pick(Character picker)
{
return picker != null;
@@ -107,11 +156,11 @@ namespace Barotrauma.Items.Components
//cheating a bit because water detectors don't actually send the water level
if (source.GetComponent<WaterDetector>() == null)
{
hullData.Water = Rand.Range(0.0f, 1.0f);
hullData.ReceivedWaterAmount = Rand.Range(0.0f, 1.0f);
}
else
{
hullData.Water = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
hullData.ReceivedWaterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
}
break;
case "oxygen_data_in":
@@ -122,7 +171,7 @@ namespace Barotrauma.Items.Components
oxy = Rand.Range(0.0f, 100.0f);
}
hullData.Oxygen = oxy;
hullData.ReceivedOxygenAmount = oxy;
break;
}
}
@@ -105,6 +105,12 @@ namespace Barotrauma.Items.Components
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, 1.0f);
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
if (item.GetComponent<Repairable>()?.IsTinkering ?? false)
{
currFlow *= 2.5f;
}
//less effective when in a bad condition
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition);
@@ -1,4 +1,4 @@
using Barotrauma.Networking;
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
@@ -364,6 +364,19 @@ namespace Barotrauma.Items.Components
float velY = MathHelper.Lerp((neutralBallastLevel * 100 - 50) * 2, -100 * Math.Sign(targetVelocity.Y), Math.Abs(targetVelocity.Y) / 100.0f);
item.SendSignal(new Signal(velY.ToString(CultureInfo.InvariantCulture), sender: user), "velocity_y_out");
// converts the controlled sub's velocity to km/h and sends it.
// TODO: add current_velocity_x and current_velocity_y pins on the navigation terminals and shuttle terminals
// TODO: increase the size of the connection panels of both navigation terminals
if (controlledSub is { } sub)
{
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x");
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y");
item.SendSignal(new Signal(sub.WorldPosition.X.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x");
item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_depth");
}
// if our tactical AI pilot has left, revert back to maintaining position
if (navigateTactically && (user == null || user.SelectedConstruction != item))
{
@@ -370,5 +370,12 @@ namespace Barotrauma.Items.Components
}
}
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
connectedRecipients?.Clear();
connectionDirty?.Clear();
}
}
}
@@ -201,7 +201,7 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
Attack = new Attack(subElement, item.Name + ", Projectile");
Attack = new Attack(subElement, item.Name + ", Projectile", item);
}
InitProjSpecific(element);
}
@@ -103,18 +103,22 @@ namespace Barotrauma.Items.Components
}
}
public bool IsTinkering { get; private set; } = false;
public float RepairIconThreshold
{
get { return RepairThreshold / 2; }
}
public Character CurrentFixer { get; private set; }
private Item currentRepairItem;
public enum FixActions : int
{
None = 0,
Repair = 1,
Sabotage = 2
Sabotage = 2,
Tinker = 3,
}
private FixActions currentFixerAction = FixActions.None;
@@ -161,12 +165,14 @@ namespace Barotrauma.Items.Components
/// <summary>
/// Check if the character manages to succesfully repair the item
/// </summary>
public bool CheckCharacterSuccess(Character character)
public bool CheckCharacterSuccess(Character character, Item bestRepairItem)
{
if (character == null) { return false; }
if (statusEffectLists == null || statusEffectLists.None(s => s.Key == ActionType.OnFailure)) { return true; }
if (bestRepairItem != null && bestRepairItem.Prefab.CannotRepairFail) { return true; }
// unpowered (electrical) items can be repaired without a risk of electrical shock
if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)) &&
item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f) { return true; }
@@ -201,10 +207,11 @@ namespace Barotrauma.Items.Components
}
else
{
Item bestRepairItem = GetBestRepairItem(character);
#if SERVER
if (CurrentFixer != character || currentFixerAction != action)
{
if (!CheckCharacterSuccess(character))
if (!CheckCharacterSuccess(character, bestRepairItem))
{
GameServer.Log($"{GameServer.CharacterLogName(character)} failed to {(action == FixActions.Sabotage ? "sabotage" : "repair")} {item.Name}", ServerLog.MessageType.ItemInteraction);
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID });
@@ -215,11 +222,18 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
#else
if (GameMain.Client == null && (CurrentFixer != character || currentFixerAction != action) && !CheckCharacterSuccess(character)) { return false; }
if (GameMain.Client == null && (CurrentFixer != character || currentFixerAction != action) && !CheckCharacterSuccess(character, bestRepairItem)) { return false; }
#endif
CurrentFixer = character;
currentRepairItem = bestRepairItem;
CurrentFixerAction = action;
return true;
Item GetBestRepairItem(Character character)
{
return character.HeldItems.OrderByDescending(i => i.Prefab.AddedRepairSpeedMultiplier).FirstOrDefault();
}
}
}
@@ -233,8 +247,16 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
#endif
if (currentRepairItem != null)
{
foreach (var ic in currentRepairItem.GetComponents<ItemComponent>())
{
ic.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, character);
}
}
CurrentFixer.AnimController.Anim = AnimController.Animation.None;
CurrentFixer = null;
currentRepairItem = null;
currentFixerAction = FixActions.None;
#if CLIENT
repairSoundChannel?.FadeOutAndDispose();
@@ -266,7 +288,8 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
UpdateProjSpecific(deltaTime);
IsTinkering = false;
if (CurrentFixer == null)
{
if (deteriorateAlwaysResetTimer > 0.0f)
@@ -314,6 +337,20 @@ namespace Barotrauma.Items.Components
return;
}
if (currentFixerAction == FixActions.Tinker)
{
// this is a bit code rotty to interject it here, should be less reliant on returning
if (!CanTinker(CurrentFixer))
{
StopRepairing(CurrentFixer);
}
else
{
IsTinkering = true;
}
return;
}
float successFactor = requiredSkills.Count == 0 ? 1.0f : RepairDegreeOfSuccess(CurrentFixer, requiredSkills);
//item must have been below the repair threshold for the player to get an achievement or XP for repairing it
@@ -327,6 +364,8 @@ namespace Barotrauma.Items.Components
}
float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor);
fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f;
if (currentFixerAction == FixActions.Repair)
{
if (fixDuration <= 0.0f)
@@ -354,6 +393,7 @@ namespace Barotrauma.Items.Components
CurrentFixer.Position + Vector2.UnitY * 100.0f);
}
SteamAchievementManager.OnItemRepaired(item, CurrentFixer);
CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete);
}
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
wasBroken = false;
@@ -399,6 +439,12 @@ namespace Barotrauma.Items.Components
}
}
private bool CanTinker(Character character)
{
if (!character.HasAbilityFlag(AbilityFlags.CanTinker)) { return false; }
return true;
}
partial void UpdateProjSpecific(float deltaTime);
public void AdjustPowerConsumption(ref float powerConsumption)
@@ -350,6 +350,7 @@ namespace Barotrauma.Items.Components
}
}
}
Connections.Clear();
#if CLIENT
rewireSoundChannel?.FadeOutAndDispose();
@@ -12,8 +12,10 @@ namespace Barotrauma.Items.Components
public enum WaveType
{
Pulse,
Sawtooth,
Sine,
Square,
Triangle,
}
private float frequency;
@@ -22,8 +24,11 @@ namespace Barotrauma.Items.Components
[InGameEditable, Serialize(WaveType.Pulse, true, description: "What kind of a signal the item outputs." +
" Pulse: periodically sends out a signal of 1." +
" Sawtooth: sends out a periodic wave that increases linearly from 0 to 1." +
" Sine: sends out a sine wave oscillating between -1 and 1." +
" Square: sends out a signal that alternates between 0 and 1.", alwaysUseInstanceValues: true)]
" Square: sends out a signal that alternates between 0 and 1." +
" Triangle: sends out a wave that alternates between increasing linearly from -1 to 1 and decreasing from 1 to -1.",
alwaysUseInstanceValues: true)]
public WaveType OutputType
{
get;
@@ -63,6 +68,10 @@ namespace Barotrauma.Items.Components
phase -= pulseInterval;
}
break;
case WaveType.Sawtooth:
phase = (phase + deltaTime * frequency) % 1.0f;
item.SendSignal(phase.ToString(CultureInfo.InvariantCulture), "signal_out");
break;
case WaveType.Square:
phase = (phase + deltaTime * frequency) % 1.0f;
item.SendSignal(phase < 0.5f ? "0" : "1", "signal_out");
@@ -71,6 +80,11 @@ namespace Barotrauma.Items.Components
phase = (phase + deltaTime * frequency) % 1.0f;
item.SendSignal(Math.Sin(phase * MathHelper.TwoPi).ToString(CultureInfo.InvariantCulture), "signal_out");
break;
case WaveType.Triangle:
phase = (phase + deltaTime * frequency) % 1.0f;
float output = 4.0f * MathF.Abs(MathUtils.PositiveModulo(phase - 0.25f, 1.0f) - 0.5f) - 1.0f;
item.SendSignal(output.ToString(CultureInfo.InvariantCulture), "signal_out");
break;
}
}
@@ -85,14 +85,15 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (string.IsNullOrWhiteSpace(expression) || regex == null) return;
if (string.IsNullOrWhiteSpace(expression) || regex == null) { return; }
if (!ContinuousOutput && nonContinuousOutputSent) { return; }
if (receivedSignal != previousReceivedSignal && receivedSignal != null)
{
try
{
Match match = regex.Match(receivedSignal);
previousResult = match.Success;
previousResult = match.Success;
previousGroups = UseCaptureGroup && previousResult ? match.Groups : null;
previousReceivedSignal = receivedSignal;
@@ -133,7 +134,7 @@ namespace Barotrauma.Items.Components
{
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(signalOut, "signal_out"); }
}
else if (!nonContinuousOutputSent)
else
{
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(signalOut, "signal_out"); }
nonContinuousOutputSent = true;
@@ -45,6 +45,9 @@ namespace Barotrauma.Items.Components
}
}
[Editable, Serialize(false, true, description: "The terminal will use a monospace font if this box is ticked.", alwaysUseInstanceValues: true)]
public bool UseMonospaceFont { get; set; }
private string OutputValue { get; set; }
public Terminal(Item item, XElement element)
@@ -100,9 +100,11 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull != null)
{
int waterPercentage = MathHelper.Clamp((int)Math.Round(item.CurrentHull.WaterPercentage), 0, 100);
int waterPercentage = MathHelper.Clamp((int)Math.Ceiling(item.CurrentHull.WaterPercentage), 0, 100);
item.SendSignal(waterPercentage.ToString(), "water_%");
}
string highPressureOut = (item.CurrentHull == null || item.CurrentHull.LethalPressure > 5.0f) ? "1" : "0";
item.SendSignal(highPressureOut, "high_pressure");
}
}
}
@@ -544,6 +544,7 @@ namespace Barotrauma.Items.Components
Projectile launchedProjectile = null;
bool loaderBroken = false;
bool isTinkering = false;
for (int i = 0; i < ProjectileCount; i++)
{
var projectiles = GetLoadedProjectiles();
@@ -575,6 +576,7 @@ namespace Barotrauma.Items.Components
projectiles = GetLoadedProjectiles();
if (projectiles.Any()) { break; }
}
}
}
if (projectiles.Count == 0 && !LaunchWithoutProjectile)
@@ -601,10 +603,25 @@ namespace Barotrauma.Items.Components
return false;
}
failedLaunchAttempts = 0;
foreach (MapEntity e in item.linkedTo)
{
if (!(e is Item linkedItem)) { continue; }
if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; }
if (linkedItem.GetComponent<Repairable>() is Repairable repairable && linkedItem.HasTag("turretammosource"))
{
isTinkering = repairable.IsTinkering;
}
}
if (!ignorePower)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float neededPower = powerConsumption;
if (isTinkering)
{
neededPower /= 1.25f;
}
while (neededPower > 0.0001f && batteries.Count > 0)
{
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
@@ -622,7 +639,8 @@ namespace Barotrauma.Items.Components
}
launchedProjectile = projectiles.FirstOrDefault();
if (launchedProjectile?.Item.Container != null)
Item container = launchedProjectile?.Item.Container;
if (container != null)
{
var repairable = launchedProjectile?.Item.Container.GetComponent<Repairable>();
if (repairable != null)
@@ -637,18 +655,22 @@ namespace Barotrauma.Items.Components
{
foreach (Projectile projectile in projectiles)
{
Launch(projectile.Item, character);
Launch(projectile.Item, character, isTinkering: isTinkering);
}
}
else
{
Launch(null, character);
Launch(null, character, isTinkering: isTinkering);
}
if (item.AiTarget != null)
{
item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange;
// Turrets also have a light component, which handles the sight range.
}
if (container != null)
{
ShiftItemsInProjectileContainer(container.GetComponent<ItemContainer>());
}
}
}
@@ -672,9 +694,18 @@ namespace Barotrauma.Items.Components
return true;
}
private void Launch(Item projectile, Character user = null, float? launchRotation = null)
private void Launch(Item projectile, Character user = null, float? launchRotation = null, bool isTinkering = false)
{
reload = reloadTime;
if (isTinkering)
{
reload /= 1.25f;
}
if (user != null)
{
reload /= 1 + user.GetStatValue(StatTypes.TurretAttackSpeed);
}
if (projectile != null)
{
@@ -698,6 +729,10 @@ namespace Barotrauma.Items.Components
if (projectileComponent != null)
{
projectileComponent.Attacker = user;
if (isTinkering)
{
projectileComponent.Attack.DamageMultiplier *= 1.25f;
}
projectileComponent.Use();
projectile.GetComponent<Rope>()?.Attach(item, projectile);
projectileComponent.User = user;
@@ -725,6 +760,26 @@ namespace Barotrauma.Items.Components
partial void LaunchProjSpecific();
private void ShiftItemsInProjectileContainer(ItemContainer container)
{
if (container == null) { return; }
bool moved;
do
{
moved = false;
for (int i = 1; i < container.Capacity; i++)
{
if (container.Inventory.GetItemAt(i) is Item item1 && container.Inventory.CanBePutInSlot(item1, i - 1))
{
if (container.Inventory.TryPutItem(item1, i - 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: true))
{
moved = true;
}
}
}
} while (moved);
}
private float waitTimer;
private float disorderTimer;
@@ -864,57 +919,26 @@ namespace Barotrauma.Items.Components
float turretAngle = -rotation;
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) { return; }
}
Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition);
Vector2 end = ConvertUnits.ToSimUnits(target.WorldPosition);
// Check that there's not other entities that shouldn't be targeted (like a friendly sub) between us and the target.
Body worldTarget = CheckLineOfSight(start, end);
bool shoot;
if (target.Submarine != null)
{
start -= target.Submarine.SimPosition;
end -= target.Submarine.SimPosition;
}
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true,
customPredicate: (Fixture f) =>
{
if (f.UserData is Item i && i.GetComponent<Turret>() != null) { return false; }
return !item.StaticFixtures.Contains(f);
});
if (pickedBody == null) { return; }
Character targetCharacter = null;
if (pickedBody.UserData is Character c)
{
targetCharacter = c;
}
else if (pickedBody.UserData is Limb limb)
{
targetCharacter = limb.character;
}
if (targetCharacter != null)
{
if (targetCharacter.Params.Group.Equals(ai.Config.Entity, StringComparison.OrdinalIgnoreCase))
{
// Don't shoot friendly characters
return;
}
Body transformedTarget = CheckLineOfSight(start, end);
shoot = CanShoot(transformedTarget, user: null, ai, targetSubmarines) && (worldTarget == null || CanShoot(worldTarget, user: null, ai, targetSubmarines));
}
else
{
if (pickedBody.UserData is ISpatialEntity e)
{
Submarine sub = e.Submarine;
if (sub == null) { return; }
if (!targetSubmarines) { return; }
if (sub == Item.Submarine) { return; }
// Don't shoot non-player submarines, i.e. wrecks or outposts.
if (!sub.Info.IsPlayer) { return; }
}
else
{
// Hit something else, probably a level wall
return;
}
shoot = CanShoot(worldTarget, user: null, ai, targetSubmarines);
}
if (shoot)
{
TryLaunch(deltaTime, ignorePower: true);
}
TryLaunch(deltaTime, ignorePower: true);
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
@@ -1067,7 +1091,8 @@ namespace Barotrauma.Items.Components
{
// Ignore dead, friendly, and those that are inside the same sub
if (enemy.IsDead || !enemy.Enabled || enemy.Submarine == character.Submarine) { continue; }
// Don't aim monsters that are inside a submarine.
if (enemy.Submarine != null && enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; }
// Don't aim monsters that are inside any submarine.
if (!enemy.IsHuman && enemy.CurrentHull != null) { continue; }
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition);
@@ -1091,26 +1116,34 @@ namespace Barotrauma.Items.Components
if (closestEnemy != null)
{
// 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.
targetPos = closestEnemy.WorldPosition;
float closestDist = closestDistance;
foreach (Limb limb in closestEnemy.AnimController.Limbs)
//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)
{
if (limb.IsSevered) { continue; }
if (limb.Hidden) { continue; }
if (!CheckTurretAngle(limb.WorldPosition)) { continue; }
float dist = Vector2.DistanceSquared(limb.WorldPosition, item.WorldPosition);
if (dist < closestDist)
{
closestDist = dist;
targetPos = limb.WorldPosition;
}
targetPos = closestEnemy.CurrentHull.WorldPosition;
}
if (closestDist > shootDistance * shootDistance)
else
{
// Not close enough to shoot
closestEnemy = null;
targetPos = null;
// 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)
{
if (limb.IsSevered) { continue; }
if (limb.Hidden) { continue; }
if (!CheckTurretAngle(limb.WorldPosition)) { continue; }
float dist = Vector2.DistanceSquared(limb.WorldPosition, item.WorldPosition);
if (dist < closestDist)
{
closestDist = dist;
targetPos = limb.WorldPosition;
}
}
if (closestDist > shootDistance * shootDistance)
{
// Not close enough to shoot
closestEnemy = null;
targetPos = null;
}
}
}
else if (item.Submarine != null && Level.Loaded != null)
@@ -1158,7 +1191,7 @@ namespace Barotrauma.Items.Components
continue;
}
// Allow targeting farther when heading towards the spire (up to 1000 px)
dist -= MathHelper.Lerp(0, 1000, MathUtils.InverseLerp(minAngle, 1, dot)); ;
dist -= MathHelper.Lerp(0, 1000, MathUtils.InverseLerp(minAngle, 1, dot));
if (dist > closestDistance) { continue; }
targetPos = closestPoint;
closestDistance = dist;
@@ -1222,58 +1255,25 @@ namespace Barotrauma.Items.Components
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > maxAngleError) { return false; }
Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition);
Vector2 end = ConvertUnits.ToSimUnits(targetPos.Value);
if (closestEnemy != null && closestEnemy.Submarine != null)
{
start -= closestEnemy.Submarine.SimPosition;
end -= closestEnemy.Submarine.SimPosition;
}
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true,
customPredicate: (Fixture f) =>
{
if (f.UserData is Item i && i.GetComponent<Turret>() != null) { return false; }
return !item.StaticFixtures.Contains(f);
});
if (pickedBody == null) { return false; }
Character targetCharacter = null;
if (pickedBody.UserData is Character c)
{
targetCharacter = c;
}
else if (pickedBody.UserData is Limb limb)
{
targetCharacter = limb.character;
}
if (targetCharacter != null)
{
if (HumanAIController.IsFriendly(character, targetCharacter))
{
// Don't shoot friendly characters
return false;
}
}
else
{
if (pickedBody.UserData is ISpatialEntity e)
{
Submarine sub = e.Submarine;
if (sub == null) { return false; }
if (sub == Item.Submarine) { return false; }
// Don't shoot non-player submarines, i.e. wrecks or outposts.
if (!sub.Info.IsPlayer) { return false; }
// Don't shoot friendly submarines.
if (sub.TeamID == Item.Submarine.TeamID) { return false; }
}
else if (!(pickedBody.UserData is Voronoi2.VoronoiCell cell && cell.IsDestructible))
{
// Hit something else, probably a level wall
return false;
}
}
if (canShoot)
{
Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition);
Vector2 end = ConvertUnits.ToSimUnits(targetPos.Value);
// Check that there's not other entities that shouldn't be targeted (like a friendly sub) between us and the target.
Body worldTarget = CheckLineOfSight(start, end);
bool shoot;
if (closestEnemy != null && closestEnemy.Submarine != null)
{
start -= closestEnemy.Submarine.SimPosition;
end -= closestEnemy.Submarine.SimPosition;
Body transformedTarget = CheckLineOfSight(start, end);
shoot = CanShoot(transformedTarget, character) && (worldTarget == null || CanShoot(worldTarget, character));
}
else
{
shoot = CanShoot(worldTarget, character);
}
if (!shoot) { return false; }
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f);
@@ -1284,6 +1284,67 @@ namespace Barotrauma.Items.Components
return false;
}
private bool CanShoot(Body targetBody, Character user = null, WreckAI ai = null, bool targetSubmarines = true)
{
if (targetBody == null) { return false; }
Character targetCharacter = null;
if (targetBody.UserData is Character c)
{
targetCharacter = c;
}
else if (targetBody.UserData is Limb limb)
{
targetCharacter = limb.character;
}
if (targetCharacter != null)
{
if (user != null)
{
if (HumanAIController.IsFriendly(user, targetCharacter))
{
return false;
}
}
if (ai != null)
{
if (targetCharacter.Params.Group.Equals(ai.Config.Entity, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
}
else
{
if (targetBody.UserData is ISpatialEntity e)
{
Submarine sub = e.Submarine ?? e as Submarine;
if (!targetSubmarines && e is Submarine) { return false; }
if (sub == null) { return false; }
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))
{
// Hit something else, probably a level wall
return false;
}
}
return true;
}
private Body CheckLineOfSight(Vector2 start, Vector2 end)
{
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
Body pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true,
customPredicate: (Fixture f) =>
{
if (f.UserData is Item i && i.GetComponent<Turret>() != null) { return false; }
return !item.StaticFixtures.Contains(f);
});
return pickedBody;
}
private Vector2 GetRelativeFiringPosition(bool useOffset = true)
{
Vector2 transformedFiringOffset = Vector2.Zero;
@@ -7,6 +7,7 @@ using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Barotrauma.Abilities;
namespace Barotrauma
{
@@ -210,7 +211,9 @@ namespace Barotrauma.Items.Components
private readonly Limb[] limb;
private readonly List<DamageModifier> damageModifiers;
public readonly Dictionary<string, float> SkillModifiers;
public readonly Dictionary<string, float> SkillModifiers = new Dictionary<string, float>();
public readonly Dictionary<StatTypes, float> WearableStatValues = new Dictionary<StatTypes, float>();
public IEnumerable<DamageModifier> DamageModifiers
{
@@ -266,7 +269,6 @@ namespace Barotrauma.Items.Components
this.item = item;
damageModifiers = new List<DamageModifier>();
SkillModifiers = new Dictionary<string, float>();
int spriteCount = element.Elements().Count(x => x.Name.ToString() == "sprite");
Variants = element.GetAttributeInt("variants", 0);
@@ -322,6 +324,18 @@ namespace Barotrauma.Items.Components
SkillModifiers.TryAdd(skillIdentifier, skillValue);
}
break;
case "statvalue":
StatTypes statType = CharacterAbilityGroup.ParseStatType(subElement.GetAttributeString("stattype", ""), Name);
float statValue = subElement.GetAttributeFloat("value", 0f);
if (WearableStatValues.ContainsKey(statType))
{
WearableStatValues[statType] += statValue;
}
else
{
WearableStatValues.TryAdd(statType, statValue);
}
break;
}
}
}
@@ -334,6 +348,7 @@ namespace Barotrauma.Items.Components
}
picker = character;
for (int i = 0; i < wearableSprites.Length; i++ )
{
var wearableSprite = wearableSprites[i];