Unstable 1.1.14.0

This commit is contained in:
Markus Isberg
2023-10-02 16:43:54 +03:00
parent 94f5a93a0c
commit cf8f0de659
606 changed files with 21906 additions and 11456 deletions
@@ -62,18 +62,58 @@ namespace Barotrauma.Items.Components
public IEnumerable<LimbPos> LimbPositions { get { return limbPositions; } }
[Editable, Serialize(false, IsPropertySaveable.No, description: "When enabled, the item will continuously send out a 0/1 signal and interacting with it will flip the signal (making the item behave like a switch). When disabled, the item will simply send out 1 when interacted with.", alwaysUseInstanceValues: true)]
[Editable, Serialize(false, IsPropertySaveable.No, description: "When enabled, the item will continuously send out a signal and interacting with it will flip the signal (making the item behave like a switch). When disabled, the item will simply send out a signal when interacted with.", alwaysUseInstanceValues: true)]
public bool IsToggle
{
get;
set;
}
[Editable, Serialize(false, IsPropertySaveable.No, description: "Whether the item is toggled on/off. Only valid if IsToggle is set to true.", alwaysUseInstanceValues: true)]
private string output;
[ConditionallyEditable(ConditionallyEditable.ConditionType.HasConnectionPanel, onlyInEditors: false),
Serialize("1", IsPropertySaveable.Yes, description: "The signal sent when the controller is being activated or is toggled on. If empty, no signal is sent.", alwaysUseInstanceValues: true)]
public string Output
{
get { return output; }
set
{
if (value == null || value == output) { return; }
output = value;
//reactivate if signal isn't empty (we may not have been previously sending a signal, but might now)
if (!value.IsNullOrEmpty()) { IsActive = true; }
}
}
private string falseOutput;
[ConditionallyEditable(ConditionallyEditable.ConditionType.IsToggleableController, onlyInEditors: false),
Serialize("0", IsPropertySaveable.Yes, description: "The signal sent when the controller is toggled off. If empty, no signal is sent. Only valid if IsToggle is true.", alwaysUseInstanceValues: true)]
public string FalseOutput
{
get { return falseOutput; }
set
{
if (value == null || value == falseOutput) { return; }
falseOutput = value;
//reactivate if signal isn't empty (we may not have been previously sending a signal, but might now)
if (!value.IsNullOrEmpty()) { IsActive = true; }
}
}
private bool state;
[ConditionallyEditable(ConditionallyEditable.ConditionType.IsToggleableController, onlyInEditors: true),
Serialize(false, IsPropertySaveable.No, description: "Whether the item is toggled on/off. Only valid if IsToggle is set to true.", alwaysUseInstanceValues: true)]
public bool State
{
get;
set;
get { return state; }
set
{
if (state != value)
{
state = value;
string newOutput = state ? output : falseOutput;
IsActive = !string.IsNullOrEmpty(newOutput);
}
}
}
[Serialize(true, IsPropertySaveable.No, description: "Should the HUD (inventory, health bar, etc) be hidden when this item is selected.")]
@@ -164,10 +204,11 @@ namespace Barotrauma.Items.Components
this.cam = cam;
UserInCorrectPosition = false;
if (IsToggle)
string signal = IsToggle && State ? output : falseOutput;
if (item.Connections != null && IsToggle && !string.IsNullOrEmpty(signal))
{
item.SendSignal(State ? "1" : "0", "signal_out");
item.SendSignal(State ? "1" : "0", "trigger_out");
item.SendSignal(signal, "signal_out");
item.SendSignal(signal, "trigger_out");
}
if (user == null
@@ -183,7 +224,7 @@ namespace Barotrauma.Items.Components
CancelUsing(user);
user = null;
}
if (!IsToggle || item.Connections == null) { IsActive = false; }
if (item.Connections == null || !IsToggle || string.IsNullOrEmpty(signal)) { IsActive = false; }
return;
}
@@ -313,9 +354,9 @@ namespace Barotrauma.Items.Components
#endif
}
}
else
else if (!string.IsNullOrEmpty(output))
{
item.SendSignal(new Signal("1", sender: user), "trigger_out");
item.SendSignal(new Signal(output, sender: user), "trigger_out");
}
lastUsed = Timing.TotalTime;
@@ -419,9 +460,9 @@ namespace Barotrauma.Items.Components
#endif
}
}
else
else if (!string.IsNullOrEmpty(output))
{
item.SendSignal(new Signal("1", sender: picker), "signal_out");
item.SendSignal(new Signal(output, sender: picker), "signal_out");
}
#if CLIENT
PlaySound(ActionType.OnUse, picker);
@@ -480,6 +521,7 @@ namespace Barotrauma.Items.Components
IsActive = false;
CancelUsing(user);
user = null;
return false;
}
else if (user.IsBot && !activator.IsBot)
{
@@ -500,8 +542,11 @@ namespace Barotrauma.Items.Components
}
#if SERVER
item.CreateServerEvent(this);
#endif
item.SendSignal(new Signal("1", sender: user), "signal_out");
#endif
if (!string.IsNullOrEmpty(output))
{
item.SendSignal(new Signal(output, sender: user), "signal_out");
}
return true;
}
@@ -92,7 +92,7 @@ namespace Barotrauma.Items.Components
repairable.LastActiveTime = (float)Timing.TotalTime + 10.0f;
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
ApplyStatusEffects(ActionType.OnActive, deltaTime);
progressTimer += deltaTime * Math.Min(powerConsumption <= 0.0f ? 1 : Voltage, MaxOverVoltageFactor);
@@ -24,6 +24,14 @@ namespace Barotrauma.Items.Components
/// </summary>
private float targetForce;
/// <summary>
/// Power demand of a marine engine is proportional with the cube of the square root of the thrusting force.
/// In practice meaning lower thrust is more effective at conserving power than it would be if the relationship between thrust and power consumption was linear.
/// Reverse exponent defined for use with overvoltage calculation: Supplying 2x power will result in 59% more force, 26% more speed, therefore 2x power.
/// </summary>
private const float ForceToPowerExponent = 3f / 2f;
private const float PowerToForceExponent = 1.0f / ForceToPowerExponent;
private float maxForce;
private readonly Attack propellerDamage;
@@ -130,7 +138,7 @@ namespace Barotrauma.Items.Components
if (Math.Abs(Force) > 1.0f)
{
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, MaxOverVoltageFactor);
float currForce = force * voltageFactor;
float currForce = force * MathF.Pow(voltageFactor, PowerToForceExponent);
float condition = item.MaxCondition <= 0.0f ? 0.0f : item.Condition / item.MaxCondition;
// Broken engine makes more noise.
float noise = Math.Abs(currForce) * MathHelper.Lerp(1.5f, 1f, condition);
@@ -180,7 +188,7 @@ namespace Barotrauma.Items.Components
return 0;
}
currPowerConsumption = Math.Abs(targetForce) / 100.0f * powerConsumption;
currPowerConsumption = MathF.Pow(Math.Abs(targetForce) / 100.0f, ForceToPowerExponent) * powerConsumption;
//engines consume more power when in a bad condition
item.GetComponent<Repairable>()?.AdjustPowerConsumption(ref currPowerConsumption);
return currPowerConsumption;
@@ -92,6 +92,8 @@ namespace Barotrauma.Items.Components
private readonly Dictionary<uint, int> fabricationLimits = new Dictionary<uint, int>();
public Action<Item, Character> OnItemFabricated;
public Fabricator(Item item, ContentXElement element)
: base(item, element)
{
@@ -128,6 +130,11 @@ namespace Barotrauma.Items.Components
}
if (recipeInvalid) { continue; }
if (fabricationRecipes.TryGetValue(recipe.RecipeHash, out var duplicateRecipe))
{
DebugConsole.ThrowError($"Error in the fabrication recipe for \"{itemPrefab.Name}\". Duplicate recipe in \"{duplicateRecipe.TargetItem.Identifier}\".");
continue;
}
fabricationRecipes.Add(recipe.RecipeHash, recipe);
if (recipe.FabricationLimitMax >= 0)
{
@@ -327,7 +334,7 @@ namespace Barotrauma.Items.Components
}
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
ApplyStatusEffects(ActionType.OnActive, deltaTime);
float fabricationSpeedIncrease = 1f + tinkeringStrength * TinkeringSpeedIncrease;
@@ -387,37 +394,35 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember is null || GameMain.NetworkMember.IsServer)
{
List<Item> foundAvailableItems = new List<Item>();
foreach (FabricationRecipe.RequiredItem requiredItem in fabricatedItem.RequiredItems)
List<Item> chosenIngredients = new List<Item>();
var suitableIngredients = GetSortedSuitableIngredients();
foreach (var requiredItem in fabricatedItem.RequiredItems)
{
for (int usedPrefabsAmount = 0; usedPrefabsAmount < requiredItem.Amount; usedPrefabsAmount++)
for (int i = 0; i < requiredItem.Amount; i++)
{
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
foreach (var suitableIngredient in suitableIngredients)
{
if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; }
if (!requiredItem.MatchesItem(suitableIngredient)) { continue; }
if (chosenIngredients.Contains(suitableIngredient)) { continue; }
var availableItems = availableIngredients[requiredPrefab.Identifier];
var availableItem = availableItems.FirstOrDefault(potentialPrefab => requiredItem.IsConditionSuitable(potentialPrefab.ConditionPercentage));
if (availableItem == null) { continue; }
ingredientsStolen |= availableItem.StolenDuringRound;
if (!availableItem.AllowStealing)
ingredientsStolen |= suitableIngredient.StolenDuringRound;
if (!suitableIngredient.AllowStealing)
{
ingredientsAllowStealing = false;
}
//Leave it behind with reduced condition if it has enough to stay above 0
if (requiredItem.UseCondition && availableItem.ConditionPercentage - requiredItem.MinCondition * 100 > 0.0f)
if (requiredItem.UseCondition && suitableIngredient.ConditionPercentage - requiredItem.MinCondition * 100 > 0.0f)
{
availableItem.Condition -= availableItem.Prefab.Health * requiredItem.MinCondition;
suitableIngredient.Condition -= suitableIngredient.Prefab.Health * requiredItem.MinCondition;
continue;
}
if (availableItem.OwnInventory != null)
if (suitableIngredient.OwnInventory != null)
{
foreach (Item containedItem in availableItem.OwnInventory.AllItemsMod)
foreach (Item containedItem in suitableIngredient.OwnInventory.AllItemsMod)
{
if (availableItem.GetComponent<ItemContainer>()?.RemoveContainedItemsOnDeconstruct ?? false)
if (suitableIngredient.GetComponent<ItemContainer>()?.RemoveContainedItemsOnDeconstruct ?? false)
{
Entity.Spawner.AddItemToRemoveQueue(containedItem);
}
@@ -427,15 +432,13 @@ namespace Barotrauma.Items.Components
}
}
}
foundAvailableItems.Add(availableItem);
availableItems.Remove(availableItem);
chosenIngredients.Add(suitableIngredient);
break;
}
}
}
var fabricationIngredients = new AbilityFabricationItemIngredients(foundAvailableItems);
var fabricationIngredients = new AbilityFabricationItemIngredients(chosenIngredients);
user?.CheckTalents(AbilityEffectType.OnItemFabricatedIngredients, fabricationIngredients);
foreach (Item availableItem in fabricationIngredients.Items)
@@ -459,7 +462,7 @@ namespace Barotrauma.Items.Components
{
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationitemAmount);
}
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationitemAmount);
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationitemAmount);
quality = GetFabricatedItemQuality(fabricatedItem, user);
}
@@ -510,7 +513,7 @@ namespace Barotrauma.Items.Components
}
}
static void onItemSpawned(Item spawnedItem, Character user)
void onItemSpawned(Item spawnedItem, Character user)
{
if (user != null && user.TeamID != CharacterTeamType.None)
{
@@ -519,6 +522,7 @@ namespace Barotrauma.Items.Components
wifiComponent.TeamID = user.TeamID;
}
}
OnItemFabricated?.Invoke(spawnedItem, user);
}
if (user?.Info != null && !user.Removed)
{
@@ -623,6 +627,11 @@ namespace Barotrauma.Items.Components
}
}
if (fabricableItem.HideForNonTraitors)
{
if (character is not { IsTraitor: true }) { return false; }
}
if (fabricableItem.RequiredMoney > 0)
{
switch (GameMain.GameSession?.GameMode)
@@ -757,6 +766,19 @@ namespace Barotrauma.Items.Components
itemList.AddRange(user.Inventory.AllItems);
linkedInventories.Add(user.Inventory);
}
foreach (Character c in Character.CharacterList)
{
//take materials from characters who've selected a linked container too
//(e.g. cabinet that's set to display alongside the fabricator UI)
if (c.SelectedItem != null &&
c.Inventory != null &&
linkedInventories.Contains(c.SelectedItem.OwnInventory) &&
!linkedInventories.Contains(c.Inventory))
{
itemList.AddRange(c.Inventory.AllItems);
linkedInventories.Add(c.Inventory);
}
}
availableIngredients.Clear();
foreach (Item item in itemList)
{
@@ -765,34 +787,51 @@ namespace Barotrauma.Items.Components
{
availableIngredients[itemIdentifier] = new List<Item>(itemList.Count);
}
//order by condition (prefer using worst-condition items)
int index = 0;
while (index < availableIngredients[itemIdentifier].Count &&
compare(item, availableIngredients[itemIdentifier][index], inputContainer.Inventory) < 0)
{
index++;
}
static int compare(Item item1, Item item2, Inventory inputInventory)
{
bool item1InInputInventory = item1.ParentInventory == inputInventory;
bool item2InInputInventory = item2.ParentInventory == inputInventory;
//prefer items in the input inventory
if (item1InInputInventory != item2InInputInventory)
{
return item1InInputInventory ? 1 : -1;
}
else
{
float condition1 = MathUtils.IsValid(item1.Condition) ? item1.Condition : 0;
float condition2 = MathUtils.IsValid(item2.Condition) ? item2.Condition : 0;
//prefer items in worse condition
return Math.Sign(condition2 - condition1);
}
}
availableIngredients[itemIdentifier].Insert(index, item);
availableIngredients[itemIdentifier].Add(item);
}
foreach (var itemId in availableIngredients.Keys)
{
availableIngredients[itemId] = SortIngredients(availableIngredients[itemId]).ToList();
}
}
private IEnumerable<Item> SortIngredients(IEnumerable<Item> items)
{
return items
.OrderByDescending(getIngredientContainerPriority)
.ThenBy(it => it.Prefab.DefaultPrice?.Price ?? 0)
.ThenBy(it => MathUtils.IsValid(it.Condition) ? it.Condition : 0)
.ThenByDescending(it => it.ParentInventory?.FindIndex(it) ?? 0);
int getIngredientContainerPriority(Item item)
{
if (item.ParentInventory == InputContainer.Inventory)
{
return 3;
}
else if (item.ParentInventory is CharacterInventory)
{
return 2;
}
return 1;
}
}
private IEnumerable<Item> GetSortedSuitableIngredients()
{
List<Item> suitableIngredients = new List<Item>();
foreach (FabricationRecipe.RequiredItem requiredItem in fabricatedItem.RequiredItems)
{
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
{
if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; }
var availableItems = availableIngredients[requiredPrefab.Identifier];
suitableIngredients.AddRange(
availableItems.Where(potentialItem => requiredItem.IsConditionSuitable(potentialItem.ConditionPercentage)));
}
}
return SortIngredients(suitableIngredients);
}
/// <summary>
@@ -801,43 +840,34 @@ namespace Barotrauma.Items.Components
/// </summary>
private void MoveIngredientsToInputContainer(FabricationRecipe targetItem)
{
//required ingredients that are already present in the input container
List<Item> usedItems = new List<Item>();
List<Item> chosenIngredients = new List<Item>();
var suitableIngredients = GetSortedSuitableIngredients();
targetItem.RequiredItems.ForEach(requiredItem => {
foreach (var requiredItem in targetItem.RequiredItems)
{
for (int i = 0; i < requiredItem.Amount; i++)
{
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
foreach (var suitableIngredient in suitableIngredients)
{
if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; }
if (!requiredItem.MatchesItem(suitableIngredient)) { continue; }
if (chosenIngredients.Contains(suitableIngredient)) { continue; }
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
var availablePrefab = availablePrefabs.FirstOrDefault(potentialPrefab =>
//in another inventory, we need to move the item
if (suitableIngredient.ParentInventory != inputContainer.Inventory)
{
return !usedItems.Contains(potentialPrefab) && requiredItem.IsConditionSuitable(potentialPrefab.ConditionPercentage);
});
if (availablePrefab == null) { continue; }
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))
if (!inputContainer.Inventory.CanBePut(suitableIngredient))
{
var unneededItem = inputContainer.Inventory.AllItems.FirstOrDefault(it => !usedItems.Contains(it));
var unneededItem = inputContainer.Inventory.AllItems.FirstOrDefault(it => !chosenIngredients.Contains(it));
unneededItem?.Drop(null);
}
inputContainer.Inventory.TryPutItem(availablePrefab, user: null);
inputContainer.Inventory.TryPutItem(suitableIngredient, user: null);
}
chosenIngredients.Add(suitableIngredient);
break;
}
}
});
}
RefreshAvailableIngredients();
}
@@ -884,6 +914,13 @@ namespace Barotrauma.Items.Components
}
savedFabricatedItem = null;
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
OnItemFabricated = null;
}
class AbilityFabricatorSkillGain : AbilityObject, IAbilityValue, IAbilitySkillIdentifier
{
public AbilityFabricatorSkillGain(Identifier skillIdentifier, float skillAmount)
@@ -83,7 +83,7 @@ namespace Barotrauma.Items.Components
hasPower = Voltage > MinVoltage;
if (hasPower)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
ApplyStatusEffects(ActionType.OnActive, deltaTime);
}
}
@@ -132,7 +132,7 @@ namespace Barotrauma.Items.Components
UpdateProjSpecific(deltaTime);
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
ApplyStatusEffects(ActionType.OnActive, deltaTime);
if (item.CurrentHull == null) { return; }
@@ -203,6 +203,8 @@ namespace Barotrauma.Items.Components
set;
}
public bool MeltedDownThisRound { get; private set; }
public Reactor(Item item, ContentXElement element)
: base(item, element)
{
@@ -282,7 +284,7 @@ namespace Barotrauma.Items.Components
}
prevAvailableFuel = AvailableFuel;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
ApplyStatusEffects(ActionType.OnActive, deltaTime);
//use a smoothed "correct output" instead of the actual correct output based on the load
//so the player doesn't have to keep adjusting the rate impossibly fast when the load fluctuates heavily
@@ -338,7 +340,7 @@ namespace Barotrauma.Items.Components
{
foreach (Item item in containedItems)
{
if (!item.HasTag("reactorfuel")) { continue; }
if (!item.HasTag(Tags.Fuel)) { continue; }
if (fissionRate > 0.0f)
{
bool isConnectedToFriendlyOutpost = Level.IsLoadedOutpost &&
@@ -648,6 +650,7 @@ namespace Barotrauma.Items.Components
item.Condition = 0.0f;
fireTimer = 0.0f;
meltDownTimer = 0.0f;
MeltedDownThisRound = true;
var containedItems = item.OwnInventory?.AllItems;
if (containedItems != null)
@@ -704,13 +707,13 @@ namespace Barotrauma.Items.Components
var containObjective = AIContainItems<Reactor>(container, character, objective, itemCount: 1, equip: true, removeEmpty: true, spawnItemIfNotFound: !character.IsOnPlayerTeam, dropItemOnDeselected: true);
containObjective.Completed += ReportFuelRodCount;
containObjective.Abandoned += ReportFuelRodCount;
character.Speak(TextManager.Get("DialogReactorFuel").Value, null, 0.0f, "reactorfuel".ToIdentifier(), 30.0f);
character.Speak(TextManager.Get("DialogReactorFuel").Value, null, 0.0f, Tags.Fuel, 30.0f);
void ReportFuelRodCount()
{
if (!character.IsOnPlayerTeam) { return; }
if (character.Submarine != Submarine.MainSub) { return; }
int remainingFuelRods = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("reactorfuel") && i.Condition > 1);
int remainingFuelRods = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(Tags.Fuel) && i.Condition > 1);
if (remainingFuelRods == 0)
{
character.Speak(TextManager.Get("DialogOutOfFuelRods").Value, null, 0.0f, "outoffuelrods".ToIdentifier(), 30.0f);
@@ -300,7 +300,7 @@ namespace Barotrauma.Items.Components
user = null;
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
ApplyStatusEffects(ActionType.OnActive, deltaTime);
float userSkill = 0.0f;
if (user != null && controlledSub != null &&
@@ -508,7 +508,7 @@ namespace Barotrauma.Items.Components
var closeCells = Level.Loaded.GetCells(controlledSub.WorldPosition, 4);
foreach (VoronoiCell cell in closeCells)
{
if (cell.DoesDamage)
if (cell.DoesDamage || cell.Body is { BodyType: BodyType.Dynamic })
{
foreach (GraphEdge edge in cell.Edges)
{
@@ -525,7 +525,7 @@ namespace Barotrauma.Items.Components
newAvoidStrength += avoid;
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, edge.Center, 1.0f, avoid, cell.Translation));
if (dot > 0.0f)
if (dot > 0.0f && cell.DoesDamage)
{
showIceSpireWarning = true;
}