7dbfbfd4eb
commit cd504791ebda32f7e9d79ec2ac726058e83b5bf1 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sun Feb 3 20:03:46 2019 +0200 Additional server logging for steam auth & desync kicks commit 6efece5e42502c1cdba89d4f4cc91398402f2b25 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sun Feb 3 19:46:51 2019 +0200 Fixed server failing to sync clients who join the server after a character has been removed during the round (e.g. eaten, turned into a husk). commit 482c9f87ec715119ad9ad420f25003ac92e666b9 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sun Feb 3 19:37:00 2019 +0200 Fixed server-side error messages when clients attempt to use a fabricator. Happened because the server tried to set the required time -text on the fabricator interface based on the controlled character instead of the character using the fabricator. commit 0a21304ee43935364de131d8aba09da8f51e6a47 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sun Feb 3 17:30:39 2019 +0200 Fixed AI crew occasionally going outside to fix leaks commit 78fa9382490f3b8d63c2ced9b31551fddf937703 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sun Feb 3 16:47:09 2019 +0200 Changed coilgun ammo box category from Machine to Equipment (no machine tab in the store menu, caused errors when trying to find a tab button style) commit 73f4374938a69bbeb9e5c0177bcd2b632fc33c8f Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sun Feb 3 16:14:56 2019 +0200 Fixed humanhusk not spawning when a husk-infected human dies. commit 080b04d6d046a3c7659942a0a72a3f1a0aa33f4d Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sun Feb 3 15:57:18 2019 +0200 Made coilgun ammo boxes fabricable and purchaseable, coilgun bolts can't be crafted, alien flares can't be purchased. Closes #1027 commit edd46655a853880e03c2f9a32abd92b6429a6c8e Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sun Feb 3 15:48:39 2019 +0200 Removed auxiliorizine from the chemical shipment mission (auxiliorizine doesn't exist anymore). commit c19620e1f507b2bda398ab4b8ab8cd7d3ea5af23 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sun Feb 3 15:46:26 2019 +0200 Removed duplicate line from loading screen tips. Closes #1032 commit e7df25130bfc76c16a08e86d3d42b16eab7cb21d Author: ezjamsen <ezjames.fi@gmail.com> Date: Sun Feb 3 13:01:19 2019 +0200 Halved moloch speeds temporarily until a full balance of enemies is done.
463 lines
18 KiB
C#
463 lines
18 KiB
C#
using Barotrauma.Networking;
|
|
using Lidgren.Network;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma.Items.Components
|
|
{
|
|
class FabricableItem
|
|
{
|
|
public class RequiredItem
|
|
{
|
|
public readonly ItemPrefab ItemPrefab;
|
|
public int Amount;
|
|
public readonly float MinCondition;
|
|
public readonly bool UseCondition;
|
|
|
|
public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, bool useCondition)
|
|
{
|
|
ItemPrefab = itemPrefab;
|
|
Amount = amount;
|
|
MinCondition = minCondition;
|
|
UseCondition = useCondition;
|
|
}
|
|
}
|
|
|
|
public readonly ItemPrefab TargetItem;
|
|
|
|
public readonly string DisplayName;
|
|
|
|
public readonly List<RequiredItem> RequiredItems;
|
|
|
|
public readonly float RequiredTime;
|
|
|
|
public readonly float OutCondition; //Percentage-based from 0 to 1
|
|
|
|
public readonly List<Skill> RequiredSkills;
|
|
|
|
public FabricableItem(XElement element)
|
|
{
|
|
if (element.Attribute("name") != null)
|
|
{
|
|
string name = element.Attribute("name").Value;
|
|
DebugConsole.ThrowError("Error in fabricable item config (" + name + ") - use item identifiers instead of names");
|
|
TargetItem = MapEntityPrefab.Find(name) as ItemPrefab;
|
|
if (TargetItem == null)
|
|
{
|
|
DebugConsole.ThrowError("Error in fabricable item config - item prefab \"" + name + "\" not found.");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string identifier = element.GetAttributeString("identifier", "");
|
|
TargetItem = MapEntityPrefab.Find(null, identifier) as ItemPrefab;
|
|
if (TargetItem == null)
|
|
{
|
|
DebugConsole.ThrowError("Error in fabricable item config - item prefab \"" + identifier + "\" not found.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
string displayName = element.GetAttributeString("displayname", "");
|
|
DisplayName = string.IsNullOrEmpty(displayName) ? TargetItem.Name : TextManager.Get(displayName);
|
|
|
|
RequiredSkills = new List<Skill>();
|
|
RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f);
|
|
OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
|
|
RequiredItems = new List<RequiredItem>();
|
|
|
|
foreach (XElement subElement in element.Elements())
|
|
{
|
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "requiredskill":
|
|
if (subElement.Attribute("name") != null)
|
|
{
|
|
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Use skill identifiers instead of names.");
|
|
continue;
|
|
}
|
|
|
|
RequiredSkills.Add(new Skill(
|
|
subElement.GetAttributeString("identifier", ""),
|
|
subElement.GetAttributeInt("level", 0)));
|
|
break;
|
|
case "item":
|
|
case "requireditem":
|
|
string requiredItemIdentifier = subElement.GetAttributeString("identifier", "");
|
|
if (string.IsNullOrWhiteSpace(requiredItemIdentifier))
|
|
{
|
|
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! One of the required items has no identifier.");
|
|
continue;
|
|
}
|
|
|
|
float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f);
|
|
//Substract mincondition from required item's condition or delete it regardless?
|
|
bool useCondition = subElement.GetAttributeBool("usecondition", true);
|
|
int count = subElement.GetAttributeInt("count", 1);
|
|
|
|
|
|
ItemPrefab requiredItem = MapEntityPrefab.Find(null, requiredItemIdentifier.Trim()) as ItemPrefab;
|
|
if (requiredItem == null)
|
|
{
|
|
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Required item \"" + requiredItemIdentifier + "\" not found.");
|
|
continue;
|
|
}
|
|
|
|
var existing = RequiredItems.Find(r => r.ItemPrefab == requiredItem);
|
|
if (existing == null)
|
|
{
|
|
RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition));
|
|
}
|
|
else
|
|
{
|
|
|
|
RequiredItems.Remove(existing);
|
|
RequiredItems.Add(new RequiredItem(requiredItem, existing.Amount + count, minCondition, useCondition));
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
|
|
{
|
|
public const float SkillIncreaseMultiplier = 0.5f;
|
|
|
|
private List<FabricableItem> fabricableItems;
|
|
|
|
private FabricableItem fabricatedItem;
|
|
private float timeUntilReady;
|
|
private float requiredTime;
|
|
|
|
private Character user;
|
|
|
|
private ItemContainer inputContainer, outputContainer;
|
|
|
|
private float progressState;
|
|
|
|
public Fabricator(Item item, XElement element)
|
|
: base(item, element)
|
|
{
|
|
fabricableItems = new List<FabricableItem>();
|
|
|
|
foreach (XElement subElement in element.Elements())
|
|
{
|
|
if (subElement.Name.ToString().ToLowerInvariant() != "fabricableitem") continue;
|
|
|
|
FabricableItem fabricableItem = new FabricableItem(subElement);
|
|
if (fabricableItem.TargetItem == null)
|
|
{
|
|
DebugConsole.ThrowError("Error in item " + item.Name + "! Fabricable item \"" + subElement.GetAttributeString("name", "") + "\" not found.");
|
|
}
|
|
else
|
|
{
|
|
fabricableItems.Add(fabricableItem);
|
|
}
|
|
}
|
|
|
|
InitProjSpecific();
|
|
}
|
|
|
|
public override void OnItemLoaded()
|
|
{
|
|
var containers = item.GetComponents<ItemContainer>().ToList();
|
|
if (containers.Count < 2)
|
|
{
|
|
DebugConsole.ThrowError("Error in item \"" + item.Name + "\": Fabricators must have two ItemContainer components!");
|
|
return;
|
|
}
|
|
|
|
inputContainer = containers[0];
|
|
outputContainer = containers[1];
|
|
|
|
foreach (FabricableItem fabricableItem in fabricableItems)
|
|
{
|
|
int ingredientCount = fabricableItem.RequiredItems.Sum(it => it.Amount);
|
|
if (ingredientCount > inputContainer.Capacity)
|
|
{
|
|
DebugConsole.ThrowError("Error in item \"" + item.Name + "\": There's not enough room in the input inventory for the ingredients of \"" + fabricableItem.TargetItem.Name + "\"!");
|
|
}
|
|
}
|
|
|
|
OnItemLoadedProjSpecific();
|
|
}
|
|
|
|
partial void OnItemLoadedProjSpecific();
|
|
|
|
|
|
partial void InitProjSpecific();
|
|
|
|
public override bool Select(Character character)
|
|
{
|
|
SelectProjSpecific(character);
|
|
return base.Select(character);
|
|
}
|
|
|
|
partial void SelectProjSpecific(Character character);
|
|
|
|
public override bool Pick(Character picker)
|
|
{
|
|
return (picker != null);
|
|
}
|
|
|
|
private void StartFabricating(FabricableItem selectedItem, Character user)
|
|
{
|
|
if (selectedItem == null) return;
|
|
|
|
if (user != null)
|
|
{
|
|
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
|
}
|
|
|
|
#if CLIENT
|
|
itemList.Enabled = false;
|
|
activateButton.Text = TextManager.Get("FabricatorCancel");
|
|
#endif
|
|
|
|
MoveIngredientsToInputContainer(selectedItem);
|
|
|
|
fabricatedItem = selectedItem;
|
|
IsActive = true;
|
|
this.user = user;
|
|
|
|
requiredTime = GetRequiredTime(fabricatedItem, user);
|
|
timeUntilReady = requiredTime;
|
|
|
|
inputContainer.Inventory.Locked = true;
|
|
outputContainer.Inventory.Locked = true;
|
|
|
|
currPowerConsumption = powerConsumption;
|
|
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
|
}
|
|
|
|
private void CancelFabricating(Character user = null)
|
|
{
|
|
if (fabricatedItem != null && user != null)
|
|
{
|
|
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
|
}
|
|
|
|
IsActive = false;
|
|
fabricatedItem = null;
|
|
this.user = null;
|
|
|
|
currPowerConsumption = 0.0f;
|
|
|
|
#if CLIENT
|
|
itemList.Enabled = true;
|
|
if (activateButton != null)
|
|
{
|
|
activateButton.Text = TextManager.Get("FabricatorCreate");
|
|
}
|
|
#endif
|
|
progressState = 0.0f;
|
|
|
|
timeUntilReady = 0.0f;
|
|
|
|
inputContainer.Inventory.Locked = false;
|
|
outputContainer.Inventory.Locked = false;
|
|
}
|
|
|
|
public override void Update(float deltaTime, Camera cam)
|
|
{
|
|
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem))
|
|
{
|
|
CancelFabricating();
|
|
return;
|
|
}
|
|
|
|
progressState = fabricatedItem == null ? 0.0f : (requiredTime - timeUntilReady) / requiredTime;
|
|
|
|
if (voltage < minVoltage) { return; }
|
|
|
|
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
|
|
|
if (powerConsumption <= 0) { voltage = 1.0f; }
|
|
|
|
timeUntilReady -= deltaTime * voltage;
|
|
voltage -= deltaTime * 10.0f;
|
|
|
|
if (timeUntilReady > 0.0f) { return; }
|
|
|
|
var availableIngredients = GetAvailableIngredients();
|
|
foreach (FabricableItem.RequiredItem ingredient in fabricatedItem.RequiredItems)
|
|
{
|
|
for (int i = 0; i < ingredient.Amount; i++)
|
|
{
|
|
var requiredItem = inputContainer.Inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ingredient.ItemPrefab && it.Condition >= ingredient.ItemPrefab.Health * ingredient.MinCondition);
|
|
if (requiredItem == null) continue;
|
|
|
|
//Item4 = use condition bool
|
|
if (ingredient.UseCondition && requiredItem.Condition - ingredient.ItemPrefab.Health * ingredient.MinCondition > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
|
|
{
|
|
requiredItem.Condition -= ingredient.ItemPrefab.Health * ingredient.MinCondition;
|
|
continue;
|
|
}
|
|
Entity.Spawner.AddToRemoveQueue(requiredItem);
|
|
inputContainer.Inventory.RemoveItem(requiredItem);
|
|
}
|
|
}
|
|
|
|
if (outputContainer.Inventory.Items.All(i => i != null))
|
|
{
|
|
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
|
|
}
|
|
else
|
|
{
|
|
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
|
|
}
|
|
|
|
if (GameMain.Client == null && user != null)
|
|
{
|
|
foreach (Skill skill in fabricatedItem.RequiredSkills)
|
|
{
|
|
user.Info.IncreaseSkillLevel(skill.Identifier, skill.Level / 100.0f * SkillIncreaseMultiplier, user.WorldPosition + Vector2.UnitY * 150.0f);
|
|
}
|
|
}
|
|
|
|
CancelFabricating(null);
|
|
}
|
|
|
|
private bool CanBeFabricated(FabricableItem fabricableItem)
|
|
{
|
|
if (fabricableItem == null) { return false; }
|
|
List<Item> availableIngredients = GetAvailableIngredients();
|
|
return CanBeFabricated(fabricableItem, availableIngredients);
|
|
}
|
|
|
|
private bool CanBeFabricated(FabricableItem fabricableItem, IEnumerable<Item> availableIngredients)
|
|
{
|
|
if (fabricableItem == null) { return false; }
|
|
foreach (FabricableItem.RequiredItem requiredItem in fabricableItem.RequiredItems)
|
|
{
|
|
if (availableIngredients.Count(it => IsItemValidIngredient(it, requiredItem)) < requiredItem.Amount)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private float GetRequiredTime(FabricableItem fabricableItem, Character user)
|
|
{
|
|
float degreeOfSuccess = DegreeOfSuccess(user, fabricableItem.RequiredSkills);
|
|
|
|
float t = degreeOfSuccess < 0.5f ? degreeOfSuccess * degreeOfSuccess : degreeOfSuccess * 2;
|
|
|
|
//fabricating takes 100 times longer if degree of success is close to 0
|
|
//characters with a higher skill than required can fabricate up to 100% faster
|
|
return fabricableItem.RequiredTime / MathHelper.Clamp(t, 0.01f, 2.0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a list of all items available in the input container and linked containers
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private List<Item> GetAvailableIngredients()
|
|
{
|
|
List<Item> availableIngredients = new List<Item>();
|
|
availableIngredients.AddRange(inputContainer.Inventory.Items.Where(it => it != null));
|
|
foreach (MapEntity linkedTo in item.linkedTo)
|
|
{
|
|
if (linkedTo is Item linkedItem)
|
|
{
|
|
var itemContainer = linkedItem.GetComponent<ItemContainer>();
|
|
if (itemContainer == null) { continue; }
|
|
|
|
var deconstructor = linkedItem.GetComponent<Deconstructor>();
|
|
if (deconstructor != null)
|
|
{
|
|
itemContainer = deconstructor.OutputContainer;
|
|
}
|
|
|
|
availableIngredients.AddRange(itemContainer.Inventory.Items.Where(it => it != null));
|
|
}
|
|
}
|
|
return availableIngredients;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the items required for fabrication into the input container.
|
|
/// The method assumes that all the required ingredients are available either in the input container or linked containers.
|
|
/// </summary>
|
|
private void MoveIngredientsToInputContainer(FabricableItem targetItem)
|
|
{
|
|
//required ingredients that are already present in the input container
|
|
List<Item> usedItems = new List<Item>();
|
|
|
|
var availableIngredients = GetAvailableIngredients();
|
|
foreach (var requiredItem in targetItem.RequiredItems)
|
|
{
|
|
for (int i = 0; i < requiredItem.Amount; i++)
|
|
{
|
|
var matchingItem = availableIngredients.Find(it => !usedItems.Contains(it) && IsItemValidIngredient(it, requiredItem));
|
|
if (matchingItem == null) { continue; }
|
|
|
|
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.Items.All(it => it != null))
|
|
{
|
|
var unneededItem = inputContainer.Inventory.Items.FirstOrDefault(it => !usedItems.Contains(it));
|
|
unneededItem?.Drop();
|
|
}
|
|
inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsItemValidIngredient(Item item, FabricableItem.RequiredItem requiredItem)
|
|
{
|
|
return
|
|
item != null &&
|
|
item.prefab == requiredItem.ItemPrefab &&
|
|
item.Condition / item.Prefab.Health >= requiredItem.MinCondition;
|
|
}
|
|
|
|
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
|
{
|
|
int itemIndex = msg.ReadRangedInteger(-1, fabricableItems.Count - 1);
|
|
|
|
item.CreateServerEvent(this);
|
|
|
|
if (!item.CanClientAccess(c)) return;
|
|
|
|
if (itemIndex == -1)
|
|
{
|
|
CancelFabricating(c.Character);
|
|
}
|
|
else
|
|
{
|
|
//if already fabricating the selected item, return
|
|
if (fabricatedItem != null && fabricableItems.IndexOf(fabricatedItem) == itemIndex) return;
|
|
if (itemIndex < 0 || itemIndex >= fabricableItems.Count) return;
|
|
#if CLIENT
|
|
SelectItem(c.Character, fabricableItems[itemIndex]);
|
|
#endif
|
|
StartFabricating(fabricableItems[itemIndex], c.Character);
|
|
}
|
|
}
|
|
|
|
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
|
{
|
|
int itemIndex = fabricatedItem == null ? -1 : fabricableItems.IndexOf(fabricatedItem);
|
|
msg.WriteRangedInteger(-1, fabricableItems.Count - 1, itemIndex);
|
|
UInt16 userID = fabricatedItem == null || user == null ? (UInt16)0 : user.ID;
|
|
msg.Write(userID);
|
|
}
|
|
|
|
}
|
|
}
|