327 lines
12 KiB
C#
327 lines
12 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
|
|
{
|
|
|
|
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
|
|
{
|
|
public const float SkillIncreaseMultiplier = 0.5f;
|
|
|
|
private List<FabricationRecipe> fabricationRecipes = new List<FabricationRecipe>();
|
|
|
|
private FabricationRecipe 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)
|
|
{
|
|
foreach (XElement subElement in element.Elements())
|
|
{
|
|
if (subElement.Name.ToString().ToLowerInvariant() == "fabricableitem")
|
|
{
|
|
DebugConsole.ThrowError("Error in item " + item.Name + "! Fabrication recipes should be defined in the craftable item's xml, not in the fabricator.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (MapEntityPrefab me in MapEntityPrefab.List)
|
|
{
|
|
if (!(me is ItemPrefab itemPrefab)) { continue; }
|
|
|
|
foreach (FabricationRecipe recipe in itemPrefab.FabricationRecipes)
|
|
{
|
|
if (recipe.SuitableFabricatorIdentifiers.Length > 0)
|
|
{
|
|
if (!recipe.SuitableFabricatorIdentifiers.Any(i => item.prefab.Identifier == i || item.HasTag(i)))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
fabricationRecipes.Add(recipe);
|
|
}
|
|
}
|
|
|
|
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 (var recipe in fabricationRecipes)
|
|
{
|
|
int ingredientCount = recipe.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 \"" + recipe.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(FabricationRecipe 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 (FabricationRecipe.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.NetworkMember == null || GameMain.NetworkMember.IsServer) && user != null && !user.Removed)
|
|
{
|
|
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(FabricationRecipe fabricableItem)
|
|
{
|
|
if (fabricableItem == null) { return false; }
|
|
List<Item> availableIngredients = GetAvailableIngredients();
|
|
return CanBeFabricated(fabricableItem, availableIngredients);
|
|
}
|
|
|
|
private bool CanBeFabricated(FabricationRecipe fabricableItem, IEnumerable<Item> availableIngredients)
|
|
{
|
|
if (fabricableItem == null) { return false; }
|
|
foreach (FabricationRecipe.RequiredItem requiredItem in fabricableItem.RequiredItems)
|
|
{
|
|
if (availableIngredients.Count(it => IsItemValidIngredient(it, requiredItem)) < requiredItem.Amount)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private float GetRequiredTime(FabricationRecipe 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(FabricationRecipe 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, FabricationRecipe.RequiredItem requiredItem)
|
|
{
|
|
return
|
|
item != null &&
|
|
item.prefab == requiredItem.ItemPrefab &&
|
|
item.Condition / item.Prefab.Health >= requiredItem.MinCondition;
|
|
}
|
|
}
|
|
}
|