Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs
T
Joonas Rikkonen 23687fbf2f aeafa16...4d3cf73
2019-03-18 22:57:05 +02:00

440 lines
17 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.{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 (!outputContainer.Inventory.IsEmpty()) return;
#if SERVER
if (user != null)
{
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
#endif
#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 / item.MaxCondition);
}
private void CancelFabricating(Character user = null)
{
#if SERVER
if (fabricatedItem != null && user != null)
{
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
#endif
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);
}
bool isNotClient = true;
#if CLIENT
isNotClient = GameMain.Client == null;
#endif
if (isNotClient && 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(null);
}
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;
}
}
}