Unstable v0.1300.0.0 (February 19th 2021)

This commit is contained in:
Joonas Rikkonen
2021-02-25 13:44:23 +02:00
parent b772654326
commit 24cbef485a
441 changed files with 21343 additions and 8562 deletions
@@ -90,6 +90,13 @@ namespace Barotrauma.Items.Components
[Serialize(UseEnvironment.Both, false, description: "Can the item be selected in air, underwater or both.")]
public UseEnvironment UsableIn { get; set; }
[Serialize(false, false, description: "Should the character using the item be drawn behind the item.")]
public bool DrawUserBehind
{
get;
set;
}
public bool ControlCharacterPose
{
get { return limbPositions.Count > 0; }
@@ -236,12 +243,12 @@ namespace Barotrauma.Items.Components
case LimbType.RightHand:
case LimbType.RightForearm:
case LimbType.RightArm:
if (user.SelectedItems[0] != null) { continue; }
if (user.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) != null) { continue; }
break;
case LimbType.LeftHand:
case LimbType.LeftForearm:
case LimbType.LeftArm:
if ( user.SelectedItems[1] != null) { continue; }
if (user.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand) != null) { continue; }
break;
}
}
@@ -388,6 +395,12 @@ namespace Barotrauma.Items.Components
limb.PullJointEnabled = false;
}
//disable flipping for 0.5 seconds, because flipping the character when it's in a weird pose (e.g. lying in bed) can mess up the ragdoll
if (character.AnimController is HumanoidAnimController humanoidAnim)
{
humanoidAnim.LockFlippingUntil = (float)Timing.TotalTime + 0.5f;
}
if (character.SelectedConstruction == this.item) { character.SelectedConstruction = null; }
character.AnimController.Anim = AnimController.Animation.None;
@@ -470,6 +483,12 @@ namespace Barotrauma.Items.Components
}
}
public override bool HasAccess(Character character)
{
if (!item.IsInteractable(character)) { return false; }
return base.HasAccess(character);
}
partial void HideHUDs(bool value);
}
}
@@ -1,5 +1,7 @@
using Barotrauma.Networking;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -59,7 +61,7 @@ namespace Barotrauma.Items.Components
{
MoveInputQueue();
if (inputContainer == null || inputContainer.Inventory.Items.All(i => i == null))
if (inputContainer == null || inputContainer.Inventory.IsEmpty())
{
SetActive(false);
return;
@@ -79,7 +81,7 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0.0f) { Voltage = 1.0f; }
progressTimer += deltaTime * Math.Min(Voltage, 1.0f);
var targetItem = inputContainer.Inventory.Items.LastOrDefault(i => i != null);
var targetItem = inputContainer.Inventory.LastOrDefault();
if (targetItem == null) { return; }
float deconstructTime = targetItem.Prefab.DeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / DeconstructionSpeed : 1.0f;
@@ -87,78 +89,107 @@ namespace Barotrauma.Items.Components
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime)
{
int emptySlots = outputContainer.Inventory.Items.Where(i => i == null).Count();
// In multiplayer, the server handles the deconstruction into new items
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
if (targetItem.Prefab.RandomDeconstructionOutput)
{
int amount = targetItem.Prefab.RandomDeconstructionOutputAmount;
List<int> deconstructItemIndexes = new List<int>();
for (int i = 0; i < targetItem.Prefab.DeconstructItems.Count; i++)
{
deconstructItemIndexes.Add(i);
}
List<float> commonness = targetItem.Prefab.DeconstructItems.Select(i => i.Commonness).ToList();
List<DeconstructItem> products = new List<DeconstructItem>();
for (int i = 0; i < amount; i++)
{
if (deconstructItemIndexes.Count < 1) { break; }
var itemIndex = ToolBox.SelectWeightedRandom(deconstructItemIndexes, commonness, Rand.RandSync.Unsynced);
products.Add(targetItem.Prefab.DeconstructItems[itemIndex]);
var removeIndex = deconstructItemIndexes.IndexOf(itemIndex);
deconstructItemIndexes.RemoveAt(removeIndex);
commonness.RemoveAt(removeIndex);
}
foreach (DeconstructItem deconstructProduct in products)
{
CreateDeconstructProduct(deconstructProduct);
}
}
else
{
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
{
CreateDeconstructProduct(deconstructProduct);
}
}
void CreateDeconstructProduct(DeconstructItem deconstructProduct)
{
float percentageHealth = targetItem.Condition / targetItem.Prefab.Health;
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) continue;
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; }
if (!(MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) is ItemPrefab itemPrefab))
{
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!");
continue;
return;
}
float condition = deconstructProduct.CopyCondition ?
percentageHealth * itemPrefab.Health :
itemPrefab.Health * deconstructProduct.OutCondition;
//container full, drop the items outside the deconstructor
if (emptySlots <= 0)
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) =>
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine, condition);
}
else
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition);
emptySlots--;
}
}
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (targetItem.Prefab.AllowDeconstruct)
{
//drop all items that are inside the deconstructed item
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
for (int i = 0; i < outputContainer.Capacity; i++)
{
if (ic?.Inventory?.Items == null) { continue; }
foreach (Item containedItem in ic.Inventory.Items)
var containedItem = outputContainer.Inventory.GetItemAt(i);
if (containedItem?.Combine(spawnedItem, null) ?? false)
{
containedItem?.Drop(dropper: null, createNetworkEvent: true);
break;
}
}
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
});
}
if (targetItem.Prefab.AllowDeconstruct)
{
//drop all items that are inside the deconstructed item
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
{
if (ic?.Inventory == null || ic.RemoveContainedItemsOnDeconstruct) { continue; }
ic.Inventory.AllItemsMod.ForEach(containedItem => outputContainer.Inventory.TryPutItem(containedItem, user: null));
}
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
}
else
{
if (!outputContainer.Inventory.CanBePut(targetItem))
{
targetItem.Drop(dropper: null);
}
else
{
if (outputContainer.Inventory.Items.All(i => i != null))
{
targetItem.Drop(dropper: null);
}
else
{
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
}
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
}
#if SERVER
item.CreateServerEvent(this);
#endif
progressTimer = 0.0f;
progressState = 0.0f;
}
#if SERVER
item.CreateServerEvent(this);
#endif
progressTimer = 0.0f;
progressState = 0.0f;
}
}
private void PutItemsToLinkedContainer()
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (outputContainer.Inventory.Items.All(it => it == null)) return;
if (outputContainer.Inventory.IsEmpty()) { return; }
foreach (MapEntity linkedTo in item.linkedTo)
{
@@ -168,13 +199,7 @@ namespace Barotrauma.Items.Components
if (fabricator != null) { continue; }
var itemContainer = linkedItem.GetComponent<ItemContainer>();
if (itemContainer == null) { continue; }
foreach (Item containedItem in outputContainer.Inventory.Items)
{
if (containedItem == null) { continue; }
if (itemContainer.Inventory.Items.All(it => it != null)) { break; }
itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true);
}
outputContainer.Inventory.AllItemsMod.ForEach(containedItem => itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true));
}
}
}
@@ -186,9 +211,12 @@ namespace Barotrauma.Items.Components
{
for (int i = inputContainer.Inventory.Capacity - 2; i >= 0; i--)
{
if (inputContainer.Inventory.Items[i] != null && inputContainer.Inventory.Items[i + 1] == null)
while (inputContainer.Inventory.GetItemAt(i) is Item item1 && inputContainer.Inventory.CanBePut(item1, i + 1))
{
inputContainer.Inventory.TryPutItem(inputContainer.Inventory.Items[i], i + 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: true);
if (!inputContainer.Inventory.TryPutItem(item1, i + 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: true))
{
break;
}
}
}
}
@@ -197,18 +225,16 @@ namespace Barotrauma.Items.Components
{
PutItemsToLinkedContainer();
if (inputContainer.Inventory.Items.All(i => i == null)) { active = false; }
if (inputContainer.Inventory.IsEmpty()) { active = false; }
IsActive = active;
currPowerConsumption = IsActive ? powerConsumption : 0.0f;
#if SERVER
if (user != null)
{
GameServer.Log(GameServer.CharacterLogName(user) + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
}
#endif
if (!IsActive)
{
progressTimer = 0.0f;
@@ -27,7 +27,7 @@ namespace Barotrauma.Items.Components
public Character User;
[Editable(0.0f, 10000000.0f),
Serialize(2000.0f, true, description: "The amount of force exerted on the submarine when the engine is operating at full power.")]
Serialize(500.0f, true, description: "The amount of force exerted on the submarine when the engine is operating at full power.")]
public float MaxForce
{
get { return maxForce; }
@@ -46,6 +46,13 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize(false, true)]
public bool DisablePropellerDamage
{
get;
set;
}
public float Force
{
get { return force;}
@@ -117,6 +124,7 @@ namespace Barotrauma.Items.Components
Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f);
//less effective when in a bad condition
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition);
if (item.Submarine.FlippedX) { currForce *= -1; }
item.Submarine.ApplyForce(currForce);
UpdatePropellerDamage(deltaTime);
float maxChangeSpeed = 0.5f;
@@ -130,7 +138,7 @@ namespace Barotrauma.Items.Components
if (particleTimer <= 0.0f)
{
Vector2 particleVel = -currForce.ClampLength(5000.0f) / 5.0f;
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos,
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos * item.Scale,
particleVel * Rand.Range(0.9f, 1.1f),
0.0f, item.CurrentHull);
particleTimer = 1.0f / particlesPerSec;
@@ -154,19 +162,22 @@ namespace Barotrauma.Items.Components
private void UpdatePropellerDamage(float deltaTime)
{
if (DisablePropellerDamage) { return; }
damageTimer += deltaTime;
if (damageTimer < 0.5f) return;
if (damageTimer < 0.5f) { return; }
damageTimer = 0.1f;
if (propellerDamage == null) return;
Vector2 propellerWorldPos = item.WorldPosition + PropellerPos;
if (propellerDamage == null) { return; }
float scaledDamageRange = propellerDamage.DamageRange * item.Scale;
Vector2 propellerWorldPos = item.WorldPosition + PropellerPos * item.Scale;
foreach (Character character in Character.CharacterList)
{
if (character.Submarine != null || !character.Enabled || character.Removed) continue;
float dist = Vector2.DistanceSquared(character.WorldPosition, propellerWorldPos);
if (dist > propellerDamage.DamageRange * propellerDamage.DamageRange) continue;
if (character.Submarine != null || !character.Enabled || character.Removed) { continue; }
float distSqr = Vector2.DistanceSquared(character.WorldPosition, propellerWorldPos);
if (distSqr > scaledDamageRange * scaledDamageRange) { continue; }
character.LastDamageSource = item;
propellerDamage.DoDamage(null, character, propellerWorldPos, 1.0f, true);
}
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -96,6 +97,12 @@ namespace Barotrauma.Items.Components
fabricationRecipes.Add(recipe);
}
}
fabricationRecipes.Sort((r1, r2) =>
{
int hash1 = (int)r1.TargetItem.UIntIdentifier;
int hash2 = (int)r2.TargetItem.UIntIdentifier;
return hash1 - hash2;
});
state = FabricatorState.Stopped;
@@ -142,7 +149,7 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
return (picker != null);
return picker != null;
}
public void RemoveFabricationRecipes(List<string> allowedIdentifiers)
@@ -161,10 +168,10 @@ namespace Barotrauma.Items.Components
partial void CreateRecipes();
private void StartFabricating(FabricationRecipe selectedItem, Character user)
private void StartFabricating(FabricationRecipe selectedItem, Character user, bool addToServerLog = true)
{
if (selectedItem == null) { return; }
if (!outputContainer.Inventory.IsEmpty()) { return; }
if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem)) { return; }
#if CLIENT
itemList.Enabled = false;
@@ -190,7 +197,7 @@ namespace Barotrauma.Items.Components
State = FabricatorState.Active;
}
#if SERVER
if (user != null)
if (user != null && addToServerLog)
{
GameServer.Log(GameServer.CharacterLogName(user) + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
@@ -298,20 +305,24 @@ namespace Barotrauma.Items.Components
}
Character tempUser = user;
if (outputContainer.Inventory.Items.All(i => i != null))
int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem);
for (int i = 0; i < fabricatedItem.Amount; i++)
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
}
else
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
if (i < amountFittingContainer)
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
}
else
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
}
}
static void onItemSpawned(Item spawnedItem, Character user)
{
if (user != null && user.TeamID != Character.TeamType.None)
if (user != null && user.TeamID != CharacterTeamType.None)
{
foreach (WifiComponent wifiComponent in spawnedItem.GetComponents<WifiComponent>())
{
@@ -328,10 +339,23 @@ namespace Barotrauma.Items.Components
user.Info.IncreaseSkillLevel(
skill.Identifier,
skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f),
user.WorldPosition + Vector2.UnitY * 150.0f);
user.Position + Vector2.UnitY * 150.0f);
}
}
//disabled "continuous fabrication" for now
//before we enable it, there should be some UI controls for fabricating a specific number of items
/*var prevFabricatedItem = fabricatedItem;
var prevUser = user;
CancelFabricating();
if (CanBeFabricated(prevFabricatedItem))
{
//keep fabricating if we can fabricate more
StartFabricating(prevFabricatedItem, prevUser, addToServerLog: false);
}*/
CancelFabricating();
}
}
@@ -375,7 +399,7 @@ namespace Barotrauma.Items.Components
float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
float average = skillSum / skills.Count;
return ((average + 100.0f) / 2.0f) / 100.0f;
return (average + 100.0f) / 2.0f / 100.0f;
}
public override float GetSkillMultiplier()
@@ -390,7 +414,7 @@ namespace Barotrauma.Items.Components
private List<Item> GetAvailableIngredients()
{
List<Item> availableIngredients = new List<Item>();
availableIngredients.AddRange(inputContainer.Inventory.Items.Where(it => it != null));
availableIngredients.AddRange(inputContainer.Inventory.AllItems);
foreach (MapEntity linkedTo in item.linkedTo)
{
if (linkedTo is Item linkedItem)
@@ -404,18 +428,18 @@ namespace Barotrauma.Items.Components
itemContainer = deconstructor.OutputContainer;
}
availableIngredients.AddRange(itemContainer.Inventory.Items.Where(it => it != null));
availableIngredients.AddRange(itemContainer.Inventory.AllItems);
}
}
#if CLIENT
if (Character.Controlled?.Inventory != null)
{
availableIngredients.AddRange(Character.Controlled.Inventory.Items.Distinct().Where(it => it != null));
availableIngredients.AddRange(Character.Controlled.Inventory.AllItems);
}
#else
if (user?.Inventory != null)
{
availableIngredients.AddRange(user.Inventory.Items.Distinct().Where(it => it != null));
availableIngredients.AddRange(user.Inventory.AllItems);
}
#endif
@@ -450,9 +474,9 @@ namespace Barotrauma.Items.Components
}
else //in another inventory, we need to move the item
{
if (inputContainer.Inventory.Items.All(it => it != null))
if (!inputContainer.Inventory.CanBePut(matchingItem))
{
var unneededItem = inputContainer.Inventory.Items.FirstOrDefault(it => !usedItems.Contains(it));
var unneededItem = inputContainer.Inventory.AllItems.FirstOrDefault(it => !usedItems.Contains(it));
unneededItem?.Drop(null, createNetworkEvent: !isClient);
}
inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: !isClient);
@@ -31,20 +31,6 @@ namespace Barotrauma.Items.Components
private float pumpSpeedLockTimer, isActiveLockTimer;
private bool infected;
[Serialize(false, true, description: "Whether or not the pump is infected with ballast flora spores.")]
public bool Infected
{
get => infected;
set
{
infected = value;
}
}
public string InfectIdentifier;
[Serialize(0.0f, true, description: "How fast the item is currently pumping water (-100 = full speed out, 100 = full speed in). Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
public float FlowPercentage
{
@@ -116,35 +102,38 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull == null) { return; }
float powerFactor = Math.Min(currPowerConsumption <= 0.0f ? 1.0f : Voltage, 1.0f);
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, 1.0f);
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
//less effective when in a bad condition
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition);
if (currFlow < 0 && Infected)
{
InfectBallast(InfectIdentifier);
}
Infected = false;
item.CurrentHull.WaterVolume += currFlow;
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
}
public void InfectBallast(string identifier)
public void InfectBallast(string identifier, bool allowMultiplePerShip = false)
{
Hull hull = item.CurrentHull;
if (hull == null) { return; }
// if the ship is already infected then do nothing
if (Hull.hullList.Where(h => h.Submarine == hull.Submarine).Any(h => h.BallastFlora != null)) { return; }
if (!allowMultiplePerShip)
{
// if the ship is already infected then do nothing
if (Hull.hullList.Where(h => h.Submarine == hull.Submarine).Any(h => h.BallastFlora != null)) { return; }
}
if (hull.BallastFlora != null) { return; }
var ballastFloraPrefab = BallastFloraPrefab.Find(identifier);
if (ballastFloraPrefab == null)
{
DebugConsole.ThrowError($"Failed to infect a ballast pump (could not find a ballast flora prefab with the identifier \"{identifier}\").\n" + Environment.StackTrace);
return;
}
Vector2 offset = item.WorldPosition - hull.WorldPosition;
hull.BallastFlora = new BallastFloraBehavior(hull, BallastFloraPrefab.Find(identifier), offset, firstGrowth: true);
hull.BallastFlora = new BallastFloraBehavior(hull, ballastFloraPrefab, offset, firstGrowth: true);
#if SERVER
hull.BallastFlora.SendNetworkMessage(hull.BallastFlora, BallastFloraBehavior.NetworkHeader.Spawn);
@@ -180,7 +169,7 @@ namespace Barotrauma.Items.Components
{
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempTarget))
{
TargetLevel = MathHelper.Clamp(tempTarget + 50.0f, 0.0f, 100.0f);
TargetLevel = MathUtils.InverseLerp(-100.0f, 100.0f, tempTarget) * 100.0f;
pumpSpeedLockTimer = 0.1f;
}
}
@@ -137,7 +137,7 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(0.2f, true, description: "How fast the condition of the contained fuel rods deteriorates per second."), Editable(0.0f, 1000.0f)]
[Serialize(0.2f, true, description: "How fast the condition of the contained fuel rods deteriorates per second."), Editable(0.0f, 1000.0f, decimals: 3)]
public float FuelConsumptionRate
{
get { return fuelConsumptionRate; }
@@ -216,7 +216,7 @@ namespace Barotrauma.Items.Components
if (LastAIUser.SelectedConstruction != item && LastAIUser.CanInteractWith(item))
{
AutoTemp = true;
unsentChanges = true;
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
LastAIUser = null;
}
}
@@ -313,12 +313,11 @@ namespace Barotrauma.Items.Components
if (fissionRate > 0.0f)
{
var containedItems = item.OwnInventory?.Items;
var containedItems = item.OwnInventory?.AllItems;
if (containedItems != null)
{
foreach (Item item in containedItems)
{
if (item == null) { continue; }
if (!item.HasTag("reactorfuel")) { continue; }
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
}
@@ -399,6 +398,8 @@ namespace Barotrauma.Items.Components
//fission rate is clamped to the amount of available fuel
float maxFissionRate = Math.Min(prevAvailableFuel, 100.0f);
if (maxFissionRate >= 100.0f) { return false; }
float maxTurbineOutput = 100.0f;
//calculate the maximum output if the fission rate is cranked as high as it goes and turbine output is at max
@@ -412,8 +413,8 @@ namespace Barotrauma.Items.Components
private bool TooMuchFuel()
{
var containedItems = item.OwnInventory?.Items;
if (containedItems != null && containedItems.Count(i => i != null) <= 1) { return false; }
var containedItems = item.OwnInventory?.AllItems;
if (containedItems != null && containedItems.Count() <= 1) { return false; }
//get the amount of heat we'd generate if the fission rate was at the low end of the optimal range
float minimumHeat = GetGeneratedHeat(optimalFissionRate.X);
@@ -530,12 +531,11 @@ namespace Barotrauma.Items.Components
fireTimer = 0.0f;
meltDownTimer = 0.0f;
var containedItems = item.OwnInventory?.Items;
var containedItems = item.OwnInventory?.AllItems;
if (containedItems != null)
{
foreach (Item containedItem in containedItems)
{
if (containedItem == null) { continue; }
containedItem.Condition = 0.0f;
}
}
@@ -557,58 +557,68 @@ namespace Barotrauma.Items.Components
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
bool shutDown = objective.Option.Equals("shutdown", StringComparison.OrdinalIgnoreCase);
IsActive = true;
float degreeOfSuccess = DegreeOfSuccess(character);
float refuelLimit = 0.3f;
//characters with insufficient skill levels don't refuel the reactor
if (degreeOfSuccess > refuelLimit)
if (!shutDown)
{
if (objective.SubObjectives.None())
float degreeOfSuccess = DegreeOfSuccess(character);
float refuelLimit = 0.3f;
//characters with insufficient skill levels don't refuel the reactor
if (degreeOfSuccess > refuelLimit)
{
if (!AIDecontainEmptyItems(character, objective, equip: false))
if (aiUpdateTimer > 0.0f)
{
aiUpdateTimer -= deltaTime;
return false;
}
}
aiUpdateTimer = AIUpdateInterval;
if (aiUpdateTimer > 0.0f)
{
aiUpdateTimer -= deltaTime;
return false;
}
aiUpdateTimer = AIUpdateInterval;
// load more fuel if the current maximum output is only 50% of the current load
// or if the fuel rod is (almost) deplenished
float minCondition = fuelConsumptionRate * MathUtils.Pow((degreeOfSuccess - refuelLimit) * 2, 2);
if (NeedMoreFuel(minimumOutputRatio: 0.5f, minCondition: minCondition))
{
var container = item.GetComponent<ItemContainer>();
if (objective.SubObjectives.None())
// load more fuel if the current maximum output is only 50% of the current load
// or if the fuel rod is (almost) deplenished
float minCondition = fuelConsumptionRate * MathUtils.Pow((degreeOfSuccess - refuelLimit) * 2, 2);
if (NeedMoreFuel(minimumOutputRatio: 0.5f, minCondition: minCondition))
{
int itemCount = item.ContainedItems.Count(i => i != null && container.ContainableItems.Any(ri => ri.MatchesItem(i))) + 1;
AIContainItems<Reactor>(container, character, objective, itemCount, equip: false, removeEmpty: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC);
character.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
}
return false;
}
else if (TooMuchFuel())
{
var container = item.GetComponent<ItemContainer>();
var containedItems = item.OwnInventory?.Items;
if (containedItems != null)
{
foreach (Item item in containedItems)
bool outOfFuel = false;
var container = item.GetComponent<ItemContainer>();
if (objective.SubObjectives.None())
{
if (item != null && container.ContainableItems.Any(ri => ri.MatchesItem(item)))
int itemCount = item.ContainedItems.Count(i => i != null && container.ContainableItems.Any(ri => ri.MatchesItem(i))) + 1;
var containObjective = AIContainItems<Reactor>(container, character, objective, itemCount, equip: false, removeEmpty: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC, dropItemOnDeselected: true);
containObjective.Completed += ReportFuelRodCount;
containObjective.Abandoned += ReportFuelRodCount;
character.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
void ReportFuelRodCount()
{
if (!character.Inventory.TryPutItem(item, character, allowedSlots: item.AllowedSlots))
if (!character.IsOnPlayerTeam) { return; }
int remainingFuelRods = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("reactorfuel") && i.Condition > 1);
if (remainingFuelRods == 0)
{
character.Speak(TextManager.Get("DialogOutOfFuelRods"), null, 0.0f, "outoffuelrods", 30.0f);
outOfFuel = true;
}
else if (remainingFuelRods < 3)
{
character.Speak(TextManager.Get("DialogLowOnFuelRods"), null, 0.0f, "lowonfuelrods", 30.0f);
}
}
}
return outOfFuel;
}
else if (TooMuchFuel())
{
if (item.OwnInventory?.AllItems != null)
{
var container = item.GetComponent<ItemContainer>();
foreach (Item item in item.OwnInventory.AllItemsMod)
{
if (container.ContainableItems.Any(ri => ri.MatchesItem(item)))
{
item.Drop(character);
break;
}
break;
}
}
}
@@ -619,13 +629,13 @@ namespace Barotrauma.Items.Components
{
if (lastUser != null && lastUser != character && lastUser != LastAIUser)
{
if (lastUser.SelectedConstruction == item)
if (lastUser.SelectedConstruction == item && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f);
}
}
}
else if (LastUserWasPlayer)
else if (LastUserWasPlayer && lastUser != null && lastUser.TeamID == character.TeamID)
{
return true;
}
@@ -637,48 +647,45 @@ namespace Barotrauma.Items.Components
float prevFissionRate = targetFissionRate;
float prevTurbineOutput = targetTurbineOutput;
switch (objective.Option.ToLowerInvariant())
{
case "powerup":
PowerOn = true;
if (objective.Override || !autoTemp)
{
//characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
if (degreeOfSuccess < 0.5f)
{
AutoTemp = true;
}
else
{
AutoTemp = false;
UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f);
}
}
#if CLIENT
FissionRateScrollBar.BarScroll = FissionRate / 100.0f;
TurbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f;
#endif
break;
case "shutdown":
PowerOn = false;
AutoTemp = false;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
unsentChanges = true;
return true;
}
if (autoTemp != prevAutoTemp ||
prevPowerOn != _powerOn ||
Math.Abs(prevFissionRate - targetFissionRate) > 1.0f ||
Math.Abs(prevTurbineOutput - targetTurbineOutput) > 1.0f)
if (shutDown)
{
PowerOn = false;
AutoTemp = false;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
unsentChanges = true;
return true;
}
else
{
PowerOn = true;
if (objective.Override || !autoTemp)
{
//characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
if (degreeOfSuccess < 0.5f)
{
AutoTemp = true;
}
else
{
AutoTemp = false;
UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f);
}
}
#if CLIENT
FissionRateScrollBar.BarScroll = FissionRate / 100.0f;
TurbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f;
#endif
if (autoTemp != prevAutoTemp ||
prevPowerOn != _powerOn ||
Math.Abs(prevFissionRate - targetFissionRate) > 1.0f ||
Math.Abs(prevTurbineOutput - targetTurbineOutput) > 1.0f)
{
unsentChanges = true;
}
aiUpdateTimer = AIUpdateInterval;
return false;
}
aiUpdateTimer = AIUpdateInterval;
return false;
}
public override void OnMapLoaded()
@@ -697,14 +704,14 @@ namespace Barotrauma.Items.Components
AutoTemp = false;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
unsentChanges = true;
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
}
break;
case "set_fissionrate":
if (PowerOn && float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
{
targetFissionRate = newFissionRate;
unsentChanges = true;
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
#if CLIENT
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f;
#endif
@@ -714,7 +721,7 @@ namespace Barotrauma.Items.Components
if (PowerOn && float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
{
targetTurbineOutput = newTurbineOutput;
unsentChanges = true;
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
#if CLIENT
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f;
#endif
@@ -104,7 +104,8 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize(false, false, description: "Does the sonar have mineral scanning mode?")]
[Editable, Serialize(false, false, description: "Does the sonar have mineral scanning mode. " +
"Only available in-game when the Item has no Steering component.")]
public bool HasMineralScanner { get; set; }
public float Zoom
@@ -251,9 +252,10 @@ namespace Barotrauma.Items.Components
}
foreach (Character c in Character.CharacterList)
{
if (c.AnimController.CurrentHull != null || !c.Enabled) continue;
if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) continue;
if (Vector2.DistanceSquared(c.WorldPosition, item.WorldPosition) > range * range) continue;
if (c.IsDead || c.Removed || !c.Enabled) { continue; }
if (c.AnimController.CurrentHull != null || c.Params.HideInSonar) { continue; }
if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) { continue; }
if (Vector2.DistanceSquared(c.WorldPosition, item.WorldPosition) > range * range) { continue; }
string directionName = GetDirectionName(c.WorldPosition - item.WorldPosition);
if (!targetGroups.ContainsKey(directionName))
@@ -276,9 +278,12 @@ namespace Barotrauma.Items.Components
dialogTag = "DialogSonarTargetLarge";
}
character.Speak(TextManager.GetWithVariables(dialogTag, new string[2] { "[direction]", "[count]" },
new string[2] { targetGroup.Key.ToString(), targetGroup.Value.Count.ToString() },
new bool[2] { true, false }), null, 0, "sonartarget" + targetGroup.Value[0].ID, 60);
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariables(dialogTag, new string[2] { "[direction]", "[count]" },
new string[2] { targetGroup.Key.ToString(), targetGroup.Value.Count.ToString() },
new bool[2] { true, false }), null, 0, "sonartarget" + targetGroup.Value[0].ID, 60);
}
//prevent the character from reporting other targets in the group
for (int i = 1; i < targetGroup.Value.Count; i++)
@@ -22,7 +22,6 @@ namespace Barotrauma.Items.Components
private const float AutoPilotMaxSpeed = 0.5f;
private const float AIPilotMaxSpeed = 1.0f;
private Vector2 currVelocity;
private Vector2 targetVelocity;
private Vector2 steeringInput;
@@ -52,7 +51,13 @@ namespace Barotrauma.Items.Components
private Sonar sonar;
private Submarine controlledSub;
private bool showIceSpireWarning;
private List<Submarine> connectedSubs = new List<Submarine>();
private const float ConnectedSubUpdateInterval = 1.0f;
float connectedSubUpdateTimer;
public bool AutoPilot
{
get { return autoPilot; }
@@ -67,7 +72,10 @@ namespace Barotrauma.Items.Components
{
if (pathFinder == null)
{
pathFinder = new PathFinder(WayPoint.WayPointList, false);
pathFinder = new PathFinder(WayPoint.WayPointList, false)
{
GetNodePenalty = GetNodePenalty
};
}
MaintainPos = true;
if (posToMaintain == null)
@@ -87,7 +95,7 @@ namespace Barotrauma.Items.Components
}
}
[Editable(0.0f, 1.0f, decimals: 3),
[Editable(0.0f, 1.0f, decimals: 4),
Serialize(0.5f, true, description: "How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
+ " Can be used to compensate if the ballast tanks are too large/small relative to the size of the submarine.")]
public float NeutralBallastLevel
@@ -299,6 +307,7 @@ namespace Barotrauma.Items.Components
}
else
{
showIceSpireWarning = false;
if (user != null && user.Info != null &&
user.SelectedConstruction == item &&
controlledSub != null && controlledSub.Velocity.LengthSquared() > 0.01f)
@@ -323,12 +332,13 @@ namespace Barotrauma.Items.Components
}
}
}
item.SendSignal(0, targetVelocity.X.ToString(CultureInfo.InvariantCulture), "velocity_x_out", user);
float targetLevel = -targetVelocity.Y;
float targetLevel = targetVelocity.X;
if (controlledSub != null && controlledSub.FlippedX) { targetLevel *= -1; }
item.SendSignal(0, targetLevel.ToString(CultureInfo.InvariantCulture), "velocity_x_out", user);
targetLevel = -targetVelocity.Y;
targetLevel += (neutralBallastLevel - 0.5f) * 100.0f;
item.SendSignal(0, targetLevel.ToString(CultureInfo.InvariantCulture), "velocity_y_out", user);
}
@@ -342,7 +352,7 @@ namespace Barotrauma.Items.Components
user.Info.IncreaseSkillLevel(
"helm",
SkillSettings.Current.SkillIncreasePerSecondWhenSteering / userSkill * deltaTime,
user.WorldPosition + Vector2.UnitY * 150.0f);
user.Position + Vector2.UnitY * 150.0f);
}
private void UpdateAutoPilot(float deltaTime)
@@ -351,7 +361,8 @@ namespace Barotrauma.Items.Components
if (posToMaintain != null)
{
Vector2 steeringVel = GetSteeringVelocity((Vector2)posToMaintain, 10.0f);
TargetVelocity = Vector2.Lerp(TargetVelocity, steeringVel, AutoPilotSteeringLerp);
TargetVelocity = Vector2.Lerp(TargetVelocity, steeringVel, AutoPilotSteeringLerp);
showIceSpireWarning = false;
return;
}
@@ -365,9 +376,21 @@ namespace Barotrauma.Items.Components
autopilotRecalculatePathTimer = RecalculatePathInterval;
}
if (steeringPath == null) { return; }
if (steeringPath == null)
{
showIceSpireWarning = false;
return;
}
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(controlledSub.WorldPosition), 10.0f);
connectedSubUpdateTimer -= deltaTime;
if (connectedSubUpdateTimer <= 0.0f)
{
connectedSubs.Clear();
connectedSubs = controlledSub?.GetConnectedSubs();
connectedSubUpdateTimer = ConnectedSubUpdateInterval;
}
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
{
Vector2 diff = ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - controlledSub.WorldPosition);
@@ -417,27 +440,38 @@ namespace Barotrauma.Items.Components
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.Y), controlledSub.Borders.Height * 0.75f));
float avoidRadius = avoidDist.Length();
float damagingWallAvoidRadius = avoidRadius * 1.5f;
float damagingWallAvoidRadius = MathHelper.Clamp(avoidRadius * 1.5f, 5000.0f, 10000.0f);
Vector2 newAvoidStrength = Vector2.Zero;
debugDrawObstacles.Clear();
//steer away from nearby walls
showIceSpireWarning = false;
var closeCells = Level.Loaded.GetCells(controlledSub.WorldPosition, 4);
foreach (VoronoiCell cell in closeCells)
{
if (Level.Loaded?.ExtraWalls.Any(w => w.WallDamageOnTouch > 0.0f && w.Cells.Contains(cell)) ?? false)
if (cell.DoesDamage)
{
foreach (GraphEdge edge in cell.Edges)
{
Vector2 closestPoint = MathUtils.GetClosestPointOnLineSegment(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition);
float dist = Vector2.Distance(closestPoint, controlledSub.WorldPosition);
Vector2 diff = closestPoint - controlledSub.WorldPosition;
float dist = diff.Length() - Math.Max(controlledSub.Borders.Width, controlledSub.Borders.Height) / 2;
if (dist > damagingWallAvoidRadius) { continue; }
Vector2 diff = controlledSub.WorldPosition - cell.Center;
Vector2 avoid = Vector2.Normalize(diff) * (damagingWallAvoidRadius - dist) / damagingWallAvoidRadius;
Vector2 normalizedDiff = Vector2.Normalize(diff);
float dot = Vector2.Dot(normalizedDiff, controlledSub.Velocity);
float avoidStrength = MathHelper.Clamp(MathHelper.Lerp(1.0f, 0.0f, dist / damagingWallAvoidRadius - dot), 0.0f, 1.0f);
Vector2 avoid = -normalizedDiff * avoidStrength;
newAvoidStrength += avoid;
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, edge.Center, 1.0f, avoid, cell.Translation));
if (dot > 0.0f)
{
showIceSpireWarning = true;
}
}
continue;
}
@@ -453,7 +487,7 @@ namespace Barotrauma.Items.Components
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, 0.0f, Vector2.Zero, Vector2.Zero));
continue;
}
if (diff.LengthSquared() < 1.0f) diff = Vector2.UnitY;
if (diff.LengthSquared() < 1.0f) { diff = Vector2.UnitY; }
Vector2 normalizedDiff = Vector2.Normalize(diff);
float dot = controlledSub.Velocity == Vector2.Zero ?
@@ -480,8 +514,7 @@ namespace Barotrauma.Items.Components
//steer away from other subs
foreach (Submarine sub in Submarine.Loaded)
{
if (sub == controlledSub) { continue; }
if (controlledSub.DockedTo.Contains(sub)) { continue; }
if (sub == controlledSub || connectedSubs.Contains(sub)) { continue; }
Point sizeSum = controlledSub.Borders.Size + sub.Borders.Size;
Vector2 minDist = sizeSum.ToVector2() / 2;
Vector2 diff = controlledSub.WorldPosition - sub.WorldPosition;
@@ -512,6 +545,15 @@ namespace Barotrauma.Items.Components
}
}
private float? GetNodePenalty(PathNode node, PathNode nextNode)
{
if (node.Waypoint?.Tunnel == null || controlledSub == null || node.Waypoint.Tunnel.Type == Level.TunnelType.MainPath) { return 0.0f; }
//never navigate from the main path to another type of path
if (node.Waypoint.Tunnel.Type == Level.TunnelType.MainPath && nextNode.Waypoint?.Tunnel?.Type != Level.TunnelType.MainPath) { return null; }
//higher cost for side paths (= autopilot prefers the main path, but can still navigate side paths if it ends up on one)
return 1000.0f;
}
private void UpdatePath()
{
if (Level.Loaded == null) { return; }
@@ -584,7 +626,7 @@ namespace Barotrauma.Items.Components
{
if (objective.Override)
{
if (user != character && user != null && user.SelectedConstruction == item)
if (user != character && user != null && user.SelectedConstruction == item && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f);
}
@@ -647,6 +689,10 @@ namespace Barotrauma.Items.Components
break;
}
sonar?.AIOperate(deltaTime, character, objective);
if (!MaintainPos && showIceSpireWarning && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("dialogicespirespottedsonar"), null, 0.0f, "icespirespottedsonar", 60.0f);
}
return false;
}
@@ -654,7 +700,7 @@ namespace Barotrauma.Items.Components
{
if (connection.Name == "velocity_in")
{
currVelocity = XMLExtensions.ParseVector2(signal, false);
TargetVelocity = XMLExtensions.ParseVector2(signal, errorMessages: false);
}
else
{