Release v0.15.12.0

This commit is contained in:
Joonas Rikkonen
2021-10-27 18:50:57 +03:00
parent bf95e82d80
commit 234fb6bc06
450 changed files with 26042 additions and 10457 deletions
@@ -1,4 +1,5 @@
using Barotrauma.Extensions;
using Barotrauma.Abilities;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
@@ -14,6 +15,12 @@ namespace Barotrauma.Items.Components
private bool hasPower;
private Character user;
private float userDeconstructorSpeedMultiplier = 1.0f;
private const float TinkeringSpeedIncrease = 1.5f;
private ItemContainer inputContainer, outputContainer;
public ItemContainer InputContainer
@@ -25,7 +32,10 @@ namespace Barotrauma.Items.Components
{
get { return outputContainer; }
}
[Serialize(false, true)]
public bool DeconstructItemsSimultaneously { get; set; }
[Editable, Serialize(1.0f, true)]
public float DeconstructionSpeed { get; set; }
@@ -81,65 +91,177 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0.0f) { Voltage = 1.0f; }
progressTimer += deltaTime * Math.Min(Voltage, 1.0f);
var targetItem = inputContainer.Inventory.LastOrDefault();
if (targetItem == null) { return; }
float deconstructTime = targetItem.Prefab.DeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / DeconstructionSpeed : 1.0f;
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime)
float tinkeringStrength = 0f;
if (repairable.IsTinkering)
{
// In multiplayer, the server handles the deconstruction into new items
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
tinkeringStrength = repairable.TinkeringStrength;
}
// doesn't quite work properly, remaining time changes if tinkering stops
float deconstructionSpeedModifier = userDeconstructorSpeedMultiplier * (1f + tinkeringStrength * TinkeringSpeedIncrease);
if (targetItem.Prefab.RandomDeconstructionOutput)
if (DeconstructItemsSimultaneously)
{
float deconstructTime = 0.0f;
foreach (Item targetItem in inputContainer.Inventory.AllItems)
{
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);
}
deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier);
}
else
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime)
{
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
List<Item> items = inputContainer.Inventory.AllItems.ToList();
foreach (Item targetItem in items)
{
CreateDeconstructProduct(deconstructProduct);
if ((Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false) || !inputContainer.Inventory.AllItems.Contains(targetItem)) { continue; }
var validDeconstructItems = targetItem.Prefab.DeconstructItems.FindAll(it =>
(it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))) &&
(it.RequiredOtherItem.Length == 0 || it.RequiredOtherItem.Any(r => items.Any(it => it != targetItem && (it.HasTag(r) || it.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))))));
ProcessItem(targetItem, items, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any());
}
#if SERVER
item.CreateServerEvent(this);
#endif
progressTimer = 0.0f;
progressState = 0.0f;
}
}
else
{
var targetItem = inputContainer.Inventory.LastOrDefault();
if (targetItem == null) { return; }
var validDeconstructItems = targetItem.Prefab.DeconstructItems.FindAll(it =>
it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase)));
float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier) : 1.0f;
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime)
{
ProcessItem(targetItem, inputContainer.Inventory.AllItemsMod, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any());
#if SERVER
item.CreateServerEvent(this);
#endif
progressTimer = 0.0f;
progressState = 0.0f;
}
}
}
private void ProcessItem(Item targetItem, IEnumerable<Item> inputItems, List<DeconstructItem> validDeconstructItems, bool allowRemove = true)
{
// In multiplayer, the server handles the deconstruction into new items
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (user != null && !user.Removed)
{
var abilityTargetItem = new AbilityItem(targetItem);
user.CheckTalents(AbilityEffectType.OnItemDeconstructed, abilityTargetItem);
}
if (targetItem.Prefab.RandomDeconstructionOutput)
{
int amount = targetItem.Prefab.RandomDeconstructionOutputAmount;
List<int> deconstructItemIndexes = new List<int>();
for (int i = 0; i < validDeconstructItems.Count; i++)
{
deconstructItemIndexes.Add(i);
}
List<float> commonness = validDeconstructItems.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(validDeconstructItems[itemIndex]);
var removeIndex = deconstructItemIndexes.IndexOf(itemIndex);
deconstructItemIndexes.RemoveAt(removeIndex);
commonness.RemoveAt(removeIndex);
}
foreach (DeconstructItem deconstructProduct in products)
{
CreateDeconstructProduct(deconstructProduct, inputItems);
}
}
else
{
foreach (DeconstructItem deconstructProduct in validDeconstructItems)
{
CreateDeconstructProduct(deconstructProduct, inputItems);
}
}
void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable<Item> inputItems)
{
float percentageHealth = targetItem.Condition / targetItem.MaxCondition;
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 + "\"!");
return;
}
float condition = deconstructProduct.CopyCondition ?
percentageHealth * itemPrefab.Health :
itemPrefab.Health * Rand.Range(deconstructProduct.OutConditionMin, deconstructProduct.OutConditionMax);
if (DeconstructItemsSimultaneously && deconstructProduct.RequiredOtherItem.Length > 0)
{
foreach (Item otherItem in inputItems)
{
if (targetItem == otherItem) { continue; }
if (deconstructProduct.RequiredOtherItem.Any(r => otherItem.HasTag(r) || r.Equals(otherItem.Prefab.Identifier, StringComparison.OrdinalIgnoreCase)))
{
user.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
foreach (Character character in Character.GetFriendlyCrew(user))
{
character.CheckTalents(AbilityEffectType.OnCrewGeneticMaterialCombinedOrRefined);
}
var geneticMaterial1 = targetItem.GetComponent<GeneticMaterial>();
var geneticMaterial2 = otherItem.GetComponent<GeneticMaterial>();
if (geneticMaterial1 != null && geneticMaterial2 != null)
{
if (geneticMaterial1.Combine(geneticMaterial2, user))
{
inputContainer.Inventory.RemoveItem(otherItem);
OutputContainer.Inventory.RemoveItem(otherItem);
Entity.Spawner.AddToRemoveQueue(otherItem);
}
allowRemove = false;
return;
}
inputContainer.Inventory.RemoveItem(otherItem);
OutputContainer.Inventory.RemoveItem(otherItem);
Entity.Spawner.AddToRemoveQueue(otherItem);
}
}
}
void CreateDeconstructProduct(DeconstructItem deconstructProduct)
int amount = 1;
if (user != null && !user.Removed)
{
float percentageHealth = targetItem.Condition / targetItem.Prefab.Health;
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; }
var itemsCreated = new AbilityValueItem(amount, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated);
amount = (int)itemsCreated.Value;
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 + "\"!");
return;
}
float condition = deconstructProduct.CopyCondition ?
percentageHealth * itemPrefab.Health :
itemPrefab.Health * deconstructProduct.OutCondition;
// used to spawn items directly into the deconstructor
var itemContainer = new AbilityItemPrefabItem(item, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedInventory, itemContainer);
}
for (int i = 0; i < amount; i++)
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) =>
{
for (int i = 0; i < outputContainer.Capacity; i++)
@@ -153,36 +275,31 @@ namespace Barotrauma.Items.Components
PutItemsToLinkedContainer();
});
}
}
if (targetItem.Prefab.AllowDeconstruct)
if (targetItem.AllowDeconstruct && allowRemove)
{
//drop all items that are inside the deconstructed item
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
{
//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();
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) || (Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false))
{
targetItem.Drop(dropper: null);
}
else
{
if (!outputContainer.Inventory.CanBePut(targetItem))
{
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;
}
}
@@ -190,7 +307,7 @@ namespace Barotrauma.Items.Components
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (outputContainer.Inventory.IsEmpty()) { return; }
foreach (MapEntity linkedTo in item.linkedTo)
{
if (linkedTo is Item linkedItem)
@@ -201,7 +318,7 @@ namespace Barotrauma.Items.Components
if (itemContainer == null) { continue; }
outputContainer.Inventory.AllItemsMod.ForEach(containedItem => itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true));
}
}
}
}
/// <summary>
@@ -221,14 +338,54 @@ namespace Barotrauma.Items.Components
}
}
private IEnumerable<(Item item, DeconstructItem output)> GetAvailableOutputs(bool checkRequiredOtherItems = true)
{
var items = inputContainer.Inventory.AllItems;
foreach (Item inputItem in items)
{
if (!inputItem.AllowDeconstruct) { continue; }
foreach (var deconstructItem in inputItem.Prefab.DeconstructItems)
{
if (deconstructItem.RequiredDeconstructor.Length > 0)
{
if (!deconstructItem.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))) { continue; }
}
if (deconstructItem.RequiredOtherItem.Length > 0 && checkRequiredOtherItems)
{
if (!deconstructItem.RequiredOtherItem.Any(r => items.Any(it => it.HasTag(r) || it.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase)))) { continue; }
bool validOtherItemFound = false;
foreach (Item otherInputItem in items)
{
if (otherInputItem == inputItem) { continue; }
if (!deconstructItem.RequiredOtherItem.Any(r => otherInputItem.HasTag(r) || otherInputItem.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))) { continue; }
var geneticMaterial1 = inputItem.GetComponent<GeneticMaterial>();
var geneticMaterial2 = otherInputItem.GetComponent<GeneticMaterial>();
if (geneticMaterial1 != null && geneticMaterial2 != null)
{
if (!geneticMaterial1.CanBeCombinedWith(geneticMaterial2)) { continue; }
}
validOtherItemFound = true;
}
if (!validOtherItemFound) { continue; }
}
yield return (inputItem, deconstructItem);
}
}
}
private void SetActive(bool active, Character user = null)
{
PutItemsToLinkedContainer();
this.user = user;
if (inputContainer.Inventory.IsEmpty()) { active = false; }
IsActive = active;
currPowerConsumption = IsActive ? powerConsumption : 0.0f;
userDeconstructorSpeedMultiplier = user != null ? 1f + user.GetStatValue(StatTypes.DeconstructorSpeedMultiplier) : 1f;
#if SERVER
if (user != null)
{
@@ -241,10 +398,6 @@ namespace Barotrauma.Items.Components
progressState = 0.0f;
}
#if CLIENT
activateButton.Text = TextManager.Get(IsActive ? "DeconstructorCancel" : "DeconstructorDeconstruct");
#endif
inputContainer.Inventory.Locked = IsActive;
}
}
@@ -73,6 +73,8 @@ namespace Barotrauma.Items.Components
}
}
private const float TinkeringForceIncrease = 1.5f;
public Engine(Item item, XElement element)
: base(item, element)
{
@@ -113,31 +115,35 @@ namespace Barotrauma.Items.Components
Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, 0.1f);
if (Math.Abs(Force) > 1.0f)
{
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f);
float currForce = force * voltageFactor;
float condition = item.Condition / item.MaxCondition;
// Broken engine makes more noise.
float noise = Math.Abs(currForce) * MathHelper.Lerp(1.5f, 1f, condition);
UpdateAITargets(noise);
//arbitrary multiplier that was added to changes in submarine mass without having to readjust all engines
float forceMultiplier = 0.1f;
if (User != null)
{
forceMultiplier *= MathHelper.Lerp(0.5f, 2.0f, (float)Math.Sqrt(User.GetSkillLevel("helm") / 100));
}
currForce *= maxForce * forceMultiplier;
if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering)
{
currForce *= 1f + repairable.TinkeringStrength * TinkeringForceIncrease;
}
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f);
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);
currForce *= MathHelper.Lerp(0.5f, 2.0f, condition);
if (item.Submarine.FlippedX) { currForce *= -1; }
item.Submarine.ApplyForce(currForce);
Vector2 forceVector = new Vector2(currForce, 0);
item.Submarine.ApplyForce(forceVector);
UpdatePropellerDamage(deltaTime);
float maxChangeSpeed = 0.5f;
float modifier = 2;
float noise = MathUtils.NearlyEqual(0.0f, maxForce) ? 0.0f : currForce.Length() * forceMultiplier * modifier / maxForce;
float min = Math.Max(1 - maxChangeSpeed, 0);
float max = 1 + maxChangeSpeed;
UpdateAITargets(Math.Clamp(noise, min, max), deltaTime);
#if CLIENT
particleTimer -= deltaTime;
if (particleTimer <= 0.0f)
{
Vector2 particleVel = -currForce.ClampLength(5000.0f) / 5.0f;
Vector2 particleVel = -forceVector.ClampLength(5000.0f) / 5.0f;
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos * item.Scale,
particleVel * Rand.Range(0.9f, 1.1f),
0.0f, item.CurrentHull);
@@ -147,14 +153,14 @@ namespace Barotrauma.Items.Components
}
}
private void UpdateAITargets(float increaseSpeed, float deltaTime)
private void UpdateAITargets(float noise)
{
if (item.AiTarget != null)
{
item.AiTarget.IncreaseSoundRange(deltaTime, increaseSpeed);
item.AiTarget.SoundRange = MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, noise / 100);
if (item.CurrentHull != null && item.CurrentHull.AiTarget != null)
{
// It's possible that some othe item increases the hull's soundrange more than the engine.
// It's possible that some other item increases the hull's soundrange more than the engine.
item.CurrentHull.AiTarget.SoundRange = Math.Max(item.CurrentHull.AiTarget.SoundRange, item.AiTarget.SoundRange);
}
}
@@ -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
{
@@ -32,6 +32,8 @@ namespace Barotrauma.Items.Components
[Serialize(1.0f, true)]
public float SkillRequirementMultiplier { get; set; }
private const float TinkeringSpeedIncrease = 1.5f;
private enum FabricatorState
{
Active = 1,
@@ -240,7 +242,8 @@ 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))
{
CancelFabricating();
return;
@@ -278,48 +281,91 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0) { Voltage = 1.0f; }
timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f);
float tinkeringStrength = 0f;
if (repairable.IsTinkering)
{
tinkeringStrength = repairable.TinkeringStrength;
}
float fabricationSpeedIncrease = 1f + tinkeringStrength * TinkeringSpeedIncrease;
timeUntilReady -= deltaTime * fabricationSpeedIncrease * Math.Min(Voltage, 1.0f);
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);
}
});
int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health);
var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem);
int quality = 0;
if (user?.Info != null)
{
foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID))
{
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem);
}
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem);
quality = GetFabricatedItemQuality(fabricatedItem, user);
}
Character tempUser = user;
int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health);
for (int i = 0; i < fabricatedItem.Amount; i++)
var tempUser = user;
for (int i = 0; i < (int)fabricationValueItem.Value; i++)
{
float outCondition = fabricatedItem.OutCondition;
if (i < amountFittingContainer)
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * outCondition,
onSpawned: (Item spawnedItem) =>
{
onItemSpawned(spawnedItem, tempUser);
spawnedItem.Quality = quality;
//reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers
spawnedItem.Condition = spawnedItem.MaxCondition * outCondition;
});
}
else
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * outCondition,
onSpawned: (Item spawnedItem) =>
{
onItemSpawned(spawnedItem, tempUser);
spawnedItem.Quality = quality;
//reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers
spawnedItem.Condition = spawnedItem.MaxCondition * outCondition;
});
}
}
@@ -333,16 +379,19 @@ namespace Barotrauma.Items.Components
}
}
}
if (user?.Info != null && !user.Removed)
{
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 AbilityValueString(0f, skill.Identifier);
user.CheckTalents(AbilityEffectType.OnItemFabricationSkillGain, addedSkillValue);
addedSkill += addedSkillValue.Value;
user.Info.IncreaseSkillLevel(
skill.Identifier,
skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f),
user.Position + Vector2.UnitY * 150.0f);
addedSkill);
}
}
@@ -363,26 +412,57 @@ namespace Barotrauma.Items.Components
}
}
partial void UpdateRequiredTimeProjSpecific();
private bool CanBeFabricated(FabricationRecipe fabricableItem)
private int GetFabricatedItemQuality(FabricationRecipe fabricatedItem, Character user)
{
if (fabricableItem == null) { return false; }
List<Item> availableIngredients = GetAvailableIngredients();
return CanBeFabricated(fabricableItem, availableIngredients);
if (user == null) { return 0; }
if (fabricatedItem.TargetItem.ConfigElement.GetChildElement("Quality") == null) { return 0; }
int quality = 0;
float floatQuality = 0.0f;
foreach (string tag in fabricatedItem.TargetItem.Tags)
{
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
}
quality = (int)floatQuality;
const int MaxCraftingSkill = 100;
quality += fabricatedItem.RequiredSkills.All(s => user.GetSkillLevel(s.Identifier) >= MaxCraftingSkill) ? 1 : 0;
quality += FabricationDegreeOfSuccess(user, fabricatedItem.RequiredSkills) >= 0.5f ? 1 : 0;
return quality;
}
private bool CanBeFabricated(FabricationRecipe fabricableItem, IEnumerable<Item> availableIngredients)
partial void UpdateRequiredTimeProjSpecific();
private bool CanBeFabricated(FabricationRecipe fabricableItem, Dictionary<string, List<Item>> availableIngredients, Character character)
{
if (fabricableItem == null) { return false; }
foreach (FabricationRecipe.RequiredItem requiredItem in fabricableItem.RequiredItems)
if (fabricableItem == null) { return false; }
if (fabricableItem.RequiresRecipe && (character == null || !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier))) { return false; }
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 +496,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 +528,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 +555,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,38 @@ 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 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 +91,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 +123,7 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
}
}
public override bool Pick(Character picker)
{
return picker != null;
@@ -99,30 +141,49 @@ namespace Barotrauma.Items.Components
hullDatas.Add(sourceHull, hullData);
}
if (hullData.Distort) return;
if (hullData.Distort) { return; }
switch (connection.Name)
{
case "water_data_in":
//cheating a bit because water detectors don't actually send the water level
float waterAmount;
if (source.GetComponent<WaterDetector>() == null)
{
hullData.Water = Rand.Range(0.0f, 1.0f);
waterAmount = Rand.Range(0.0f, 1.0f);
}
else
{
hullData.Water = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
waterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
}
hullData.ReceivedWaterAmount = waterAmount;
foreach (var linked in sourceHull.linkedTo)
{
if (!(linked is Hull linkedHull)) { continue; }
if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData))
{
linkedHullData = new HullData();
hullDatas.Add(linkedHull, linkedHullData);
}
linkedHullData.ReceivedWaterAmount = waterAmount;
}
break;
case "oxygen_data_in":
float oxy;
if (!float.TryParse(signal.value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out oxy))
if (!float.TryParse(signal.value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float oxy))
{
oxy = Rand.Range(0.0f, 100.0f);
}
hullData.Oxygen = oxy;
hullData.ReceivedOxygenAmount = oxy;
foreach (var linked in sourceHull.linkedTo)
{
if (!(linked is Hull linkedHull)) { continue; }
if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData))
{
linkedHullData = new HullData();
hullDatas.Add(linkedHull, linkedHullData);
}
linkedHullData.ReceivedOxygenAmount = oxy;
}
break;
}
}
@@ -11,9 +11,12 @@ namespace Barotrauma.Items.Components
private float generatedAmount;
//key = vent, float = total volume of the hull the vent is in and the hulls connected to it
private Dictionary<Vent, float> ventList;
private List<(Vent vent, float hullVolume)> ventList;
private float totalHullVolume;
private float ventUpdateTimer;
const float VentUpdateInterval = 5.0f;
public float CurrFlow
{
@@ -31,6 +34,8 @@ namespace Barotrauma.Items.Components
public OxygenGenerator(Item item, XElement element)
: base(item, element)
{
//randomize update timer so all oxygen generators don't update at the same time
ventUpdateTimer = Rand.Range(0.0f, VentUpdateInterval);
IsActive = true;
}
@@ -64,7 +69,7 @@ namespace Barotrauma.Items.Components
//20% condition = 4%
CurrFlow *= conditionMult * conditionMult;
UpdateVents(CurrFlow);
UpdateVents(CurrFlow, deltaTime);
}
public override void UpdateBroken(float deltaTime, Camera cam)
@@ -75,7 +80,9 @@ namespace Barotrauma.Items.Components
private void GetVents()
{
ventList = new Dictionary<Vent, float>();
totalHullVolume = 0.0f;
ventList ??= new List<(Vent vent, float hullVolume)>();
ventList.Clear();
foreach (MapEntity entity in item.linkedTo)
{
if (!(entity is Item linkedItem)) { continue; }
@@ -83,30 +90,40 @@ namespace Barotrauma.Items.Components
Vent vent = linkedItem.GetComponent<Vent>();
if (vent?.Item.CurrentHull == null) { continue; }
ventList.Add(vent, 0.0f);
foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: true, searchDepth: 10, ignoreClosedGaps: true))
{
totalHullVolume += vent.Item.CurrentHull.Volume;
ventList.Add((vent, vent.Item.CurrentHull.Volume));
}
for (int i = 0; i < ventList.Count; i++)
{
Vent vent = ventList[i].vent;
foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: false, searchDepth: 3, ignoreClosedGaps: true))
{
//another vent in the connected hull -> don't add it to this vent's total hull volume
if (ventList.Any(v => v.vent != vent && v.vent.Item.CurrentHull == connectedHull)) { continue; }
totalHullVolume += connectedHull.Volume;
ventList[vent] += connectedHull.Volume;
ventList[i] = (ventList[i].vent, ventList[i].hullVolume + connectedHull.Volume);
}
}
}
private void UpdateVents(float deltaOxygen)
private void UpdateVents(float deltaOxygen, float deltaTime)
{
if (ventList == null)
if (ventList == null || ventUpdateTimer < 0.0f)
{
GetVents();
ventUpdateTimer = VentUpdateInterval;
}
ventUpdateTimer -= deltaTime;
if (!ventList.Any() || totalHullVolume <= 0.0f) { return; }
foreach (KeyValuePair<Vent, float> v in ventList)
foreach ((Vent vent, float hullVolume) in ventList)
{
if (v.Key?.Item.CurrentHull == null) { continue; }
if (vent.Item.CurrentHull == null) { continue; }
v.Key.OxygenFlow = deltaOxygen * (v.Value / totalHullVolume);
v.Key.IsActive = true;
vent.OxygenFlow = deltaOxygen * (hullVolume / totalHullVolume);
vent.IsActive = true;
}
}
}
@@ -70,6 +70,8 @@ namespace Barotrauma.Items.Components
public bool HasPower => IsActive && Voltage >= MinVoltage;
public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
private const float TinkeringSpeedIncrease = 1.5f;
public Pump(Item item, XElement element)
: base(item, element)
{
@@ -105,11 +107,19 @@ 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>() is Repairable repairable && repairable.IsTinkering)
{
currFlow *= 1f + repairable.TinkeringStrength * TinkeringSpeedIncrease;
}
//less effective when in a bad condition
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition);
item.CurrentHull.WaterVolume += currFlow;
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
Voltage -= deltaTime;
}
public void InfectBallast(string identifier, bool allowMultiplePerShip = false)
@@ -367,23 +367,19 @@ namespace Barotrauma.Items.Components
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
}
}
if (item.CurrentHull != null)
{
var aiTarget = item.CurrentHull.AiTarget;
if (aiTarget != null && MaxPowerOutput > 0)
{
float range = Math.Abs(currPowerConsumption) / MaxPowerOutput;
float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range);
aiTarget.SoundRange = Math.Max(aiTarget.SoundRange, noise);
}
}
if (item.AiTarget != null && MaxPowerOutput > 0)
{
var aiTarget = item.AiTarget;
float range = Math.Abs(currPowerConsumption) / MaxPowerOutput;
aiTarget.SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range);
if (item.CurrentHull != null)
{
var hullAITarget = item.CurrentHull.AiTarget;
if (hullAITarget != null)
{
hullAITarget.SoundRange = Math.Max(hullAITarget.SoundRange, aiTarget.SoundRange);
}
}
}
}
@@ -498,15 +494,12 @@ namespace Barotrauma.Items.Components
{
float prevFireTimer = fireTimer;
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition);
#if SERVER
if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedConstruction == item)
{
GameMain.Server.KarmaManager.OnReactorOverHeating(blameOnBroken.Character, deltaTime);
GameMain.Server.KarmaManager.OnReactorOverHeating(item, blameOnBroken.Character, deltaTime);
}
#endif
if (fireTimer >= FireDelay && prevFireTimer < fireDelay)
{
new FireSource(item.WorldPosition);
@@ -595,7 +588,7 @@ namespace Barotrauma.Items.Components
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
if (GameMain.Server != null)
{
GameMain.Server.KarmaManager.OnReactorMeltdown(blameOnBroken?.Character);
GameMain.Server.KarmaManager.OnReactorMeltdown(item, blameOnBroken?.Character);
}
#endif
}
@@ -1,4 +1,4 @@
using Barotrauma.Networking;
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
@@ -364,6 +364,16 @@ 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.
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 * Physics.DisplayToRealWorldRatio).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x");
item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y");
}
// if our tactical AI pilot has left, revert back to maintaining position
if (navigateTactically && (user == null || user.SelectedConstruction != item))
{
@@ -382,8 +392,7 @@ namespace Barotrauma.Items.Components
float userSkill = Math.Max(user.GetSkillLevel("helm"), 1.0f) / 100.0f;
user.Info.IncreaseSkillLevel(
"helm",
SkillSettings.Current.SkillIncreasePerSecondWhenSteering / userSkill * deltaTime,
user.Position + Vector2.UnitY * 150.0f);
SkillSettings.Current.SkillIncreasePerSecondWhenSteering / userSkill * deltaTime);
}
private void UpdateAutoPilot(float deltaTime)