38f1ddb...178a853: v0.8.9.1, removed content folder

This commit is contained in:
Joonas Rikkonen
2019-03-18 19:46:58 +02:00
parent 38f1ddb6fe
commit 6c0679c297
1054 changed files with 151673 additions and 144931 deletions
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -146,7 +147,7 @@ namespace Barotrauma.Items.Components
limb.Disabled = true;
Vector2 worldPosition = lb.position + new Vector2(item.WorldRect.X, item.WorldRect.Y);
Vector2 worldPosition = new Vector2(item.WorldRect.X, item.WorldRect.Y) + lb.position * item.Scale;
Vector2 diff = worldPosition - limb.WorldPosition;
limb.PullJointEnabled = true;
@@ -225,7 +226,7 @@ namespace Barotrauma.Items.Components
Turret turret = targetItem.GetComponent<Turret>();
if (turret != null)
{
centerPos = new Vector2(targetItem.WorldRect.X + turret.BarrelPos.X, targetItem.WorldRect.Y - turret.BarrelPos.Y);
centerPos = new Vector2(targetItem.WorldRect.X + turret.TransformedBarrelPos.X, targetItem.WorldRect.Y - turret.TransformedBarrelPos.Y);
}
}
@@ -240,7 +241,7 @@ namespace Barotrauma.Items.Components
private Item GetFocusTarget()
{
item.SendSignal(0, targetRotation.ToString(), "position_out", character);
item.SendSignal(0, MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), "position_out", character);
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
{
@@ -259,7 +260,7 @@ namespace Barotrauma.Items.Components
item.SendSignal(0, "1", "signal_out", picker);
#if CLIENT
PlaySound(ActionType.OnUse, item.WorldPosition);
PlaySound(ActionType.OnUse, item.WorldPosition, picker);
#endif
return true;
@@ -308,7 +309,7 @@ namespace Barotrauma.Items.Components
return true;
}
public override void FlipX()
public override void FlipX(bool relativeToSub)
{
if (dir != Direction.None)
{
@@ -319,16 +320,32 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < limbPositions.Count; i++)
{
float diff = (item.Rect.X + limbPositions[i].position.X) - item.Rect.Center.X;
float diff = (item.Rect.X + limbPositions[i].position.X * item.Scale) - item.Rect.Center.X;
Vector2 flippedPos =
new Vector2(
item.Rect.Center.X - diff - item.Rect.X,
(item.Rect.Center.X - diff - item.Rect.X) / item.Scale,
limbPositions[i].position.Y);
limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
}
}
public override void FlipY(bool relativeToSub)
{
userPos.Y = -UserPos.Y;
for (int i = 0; i < limbPositions.Count; i++)
{
float diff = (item.Rect.Y + limbPositions[i].position.Y) - item.Rect.Center.Y;
Vector2 flippedPos =
new Vector2(
limbPositions[i].position.X,
item.Rect.Center.Y - diff - item.Rect.Y);
limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
}
}
}
}
@@ -1,6 +1,5 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
@@ -9,98 +8,148 @@ namespace Barotrauma.Items.Components
{
partial class Deconstructor : Powered, IServerSerializable, IClientSerializable
{
float progressTimer;
private float progressTimer;
private float progressState;
ItemContainer container;
private ItemContainer inputContainer, outputContainer;
public ItemContainer OutputContainer
{
get { return outputContainer; }
}
public Deconstructor(Item item, XElement element)
: base(item, element)
{
#if CLIENT
progressBar = new GUIProgressBar(new Rectangle(0,0,200,20), Color.Green, "", 0.0f, Alignment.BottomCenter, GuiFrame);
activateButton = new GUIButton(new Rectangle(0, 0, 200, 20), "Deconstruct", Alignment.TopCenter, "", GuiFrame);
activateButton.OnClicked = ToggleActive;
#endif
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void OnItemLoaded()
{
var containers = item.GetComponents<ItemContainer>().ToList();
if (containers.Count < 2)
{
DebugConsole.ThrowError("Error in item \"" + item.Name + "\": Deconstructors must have two ItemContainer components!");
return;
}
inputContainer = containers[0];
outputContainer = containers[1];
OnItemLoadedProjSpecific();
}
partial void OnItemLoadedProjSpecific();
public override void Update(float deltaTime, Camera cam)
{
if (container == null || container.Inventory.Items.All(i => i == null))
MoveInputQueue();
if (inputContainer == null || inputContainer.Inventory.Items.All(i => i == null))
{
SetActive(false);
return;
}
if (voltage < minVoltage) return;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (powerConsumption == 0.0f) voltage = 1.0f;
progressTimer += deltaTime*voltage;
progressTimer += deltaTime * voltage;
Voltage -= deltaTime * 10.0f;
var targetItem = container.Inventory.Items.FirstOrDefault(i => i != null);
#if CLIENT
progressBar.BarSize = Math.Min(progressTimer / targetItem.Prefab.DeconstructTime, 1.0f);
#endif
if (progressTimer>targetItem.Prefab.DeconstructTime)
var targetItem = inputContainer.Inventory.Items.LastOrDefault(i => i != null);
if (targetItem == null) { return; }
progressState = Math.Min(progressTimer / targetItem.Prefab.DeconstructTime, 1.0f);
if (progressTimer > targetItem.Prefab.DeconstructTime)
{
var containers = item.GetComponents<ItemContainer>();
if (containers.Count < 2)
{
DebugConsole.ThrowError("Error in Deconstructor.Update: Deconstructors must have two ItemContainer components!");
return;
}
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
{
float percentageHealth = targetItem.Condition / targetItem.Prefab.Health;
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) continue;
var itemPrefab = MapEntityPrefab.Find(deconstructProduct.ItemPrefabName) as ItemPrefab;
var itemPrefab = MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct + "\"!");
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!");
continue;
}
float condition = deconstructProduct.CopyCondition ?
percentageHealth * itemPrefab.Health :
itemPrefab.Health * deconstructProduct.OutCondition;
//container full, drop the items outside the deconstructor
if (containers[1].Inventory.Items.All(i => i != null))
if (outputContainer.Inventory.Items.All(i => i != null))
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine, itemPrefab.Health * deconstructProduct.OutCondition);
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine, condition);
}
else
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, containers[1].Inventory, itemPrefab.Health * deconstructProduct.OutCondition);
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition);
}
}
container.Inventory.RemoveItem(targetItem);
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
if (container.Inventory.Items.Any(i => i != null))
if (inputContainer.Inventory.Items.Any(i => i != null))
{
progressTimer = 0.0f;
#if CLIENT
progressBar.BarSize = 0.0f;
#endif
}
}
}
private void PutItemsToLinkedContainer()
{
if (GameMain.Client != null) { return; }
if (outputContainer.Inventory.Items.All(it => it == null)) return;
foreach (MapEntity linkedTo in item.linkedTo)
{
if (linkedTo is Item linkedItem)
{
var fabricator = linkedItem.GetComponent<Fabricator>();
if (fabricator != null) { continue; }
var itemContainer = linkedItem.GetComponent<ItemContainer>();
if (itemContainer == null) { continue; }
foreach (Item containedItem in outputContainer.Inventory.Items)
{
if (containedItem == null) { continue; }
if (itemContainer.Inventory.Items.All(it => it != null)) { break; }
itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true);
}
}
}
}
/// <summary>
/// Move items towards the last slot in the inventory if there's free slots
/// </summary>
private void MoveInputQueue()
{
for (int i = inputContainer.Inventory.Capacity - 2; i >= 0; i--)
{
if (inputContainer.Inventory.Items[i] != null && inputContainer.Inventory.Items[i + 1] == null)
{
inputContainer.Inventory.TryPutItem(inputContainer.Inventory.Items[i], i + 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: true);
}
}
}
private void SetActive(bool active, Character user = null)
{
container = item.GetComponent<ItemContainer>();
if (container == null)
{
DebugConsole.ThrowError("Error in Deconstructor.Activate: Deconstructors must have two ItemContainer components");
return;
}
PutItemsToLinkedContainer();
if (container.Inventory.Items.All(i => i == null)) active = false;
if (inputContainer.Inventory.Items.All(i => i == null)) { active = false; }
IsActive = active;
@@ -109,22 +158,21 @@ namespace Barotrauma.Items.Components
GameServer.Log(user.LogName + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
}
if (!IsActive) { progressState = 0.0f; }
#if CLIENT
if (!IsActive)
{
progressBar.BarSize = 0.0f;
progressTimer = 0.0f;
activateButton.Text = "Deconstruct";
activateButton.Text = TextManager.Get("DeconstructorDeconstruct");
}
else
{
activateButton.Text = "Cancel";
activateButton.Text = TextManager.Get("DeconstructorCancel");
}
#endif
container.Inventory.Locked = IsActive;
inputContainer.Inventory.Locked = IsActive;
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
@@ -2,10 +2,12 @@
using System;
using System.Globalization;
using System.Xml.Linq;
using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma.Items.Components
{
partial class Engine : Powered
partial class Engine : Powered, IServerSerializable, IClientSerializable
{
private float force;
@@ -13,6 +15,14 @@ namespace Barotrauma.Items.Components
private float maxForce;
private Attack propellerDamage;
private float damageTimer;
private bool hasPower;
private float prevVoltage;
[Editable(0.0f, 10000000.0f, ToolTip = "The amount of force exerted on the submarine when the engine is operating at full power."),
Serialize(2000.0f, true)]
public float MaxForce
@@ -24,56 +34,70 @@ namespace Barotrauma.Items.Components
}
}
[Editable, Serialize("0.0,0.0", true)]
public Vector2 PropellerPos
{
get;
set;
}
public float Force
{
get { return force;}
set { force = MathHelper.Clamp(value, -100.0f, 100.0f); }
}
public float CurrentVolume
{
get { return Math.Abs((force / 100.0f) * (minVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage / minVoltage, 1.0f))); }
}
public Engine(Item item, XElement element)
: base(item, element)
{
IsActive = true;
#if CLIENT
var button = new GUIButton(new Rectangle(160, 50, 30, 30), "-", "", GuiFrame);
button.OnClicked = (GUIButton btn, object obj) =>
foreach (XElement subElement in element.Elements())
{
targetForce -= 1.0f;
return true;
};
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "propellerdamage":
propellerDamage = new Attack(subElement, item.Name + ", Engine");
break;
}
}
button = new GUIButton(new Rectangle(200, 50, 30, 30), "+", "", GuiFrame);
button.OnClicked = (GUIButton btn, object obj) =>
{
targetForce += 1.0f;
return true;
};
#endif
}
public float CurrentVolume
{
get { return Math.Abs((force / 100.0f) * Math.Min(voltage / minVoltage, 1.0f)); }
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void Update(float deltaTime, Camera cam)
{
UpdateOnActiveEffects(deltaTime);
UpdateAnimation(deltaTime);
currPowerConsumption = Math.Abs(targetForce) / 100.0f * powerConsumption;
//pumps consume more power when in a bad condition
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
if (powerConsumption == 0.0f) voltage = 1.0f;
prevVoltage = voltage;
hasPower = voltage > minVoltage;
Force = MathHelper.Lerp(force, (voltage < minVoltage) ? 0.0f : targetForce, 0.1f);
if (Math.Abs(Force) > 1.0f)
{
Vector2 currForce = new Vector2((force / 100.0f) * maxForce * Math.Min(voltage / minVoltage, 1.0f), 0.0f);
//less effective when in a bad condition
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / 100.0f);
item.Submarine.ApplyForce(currForce);
UpdatePropellerDamage(deltaTime);
if (item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SoundRange = Math.Max(currForce.Length(), item.CurrentHull.AiTarget.SoundRange);
@@ -82,7 +106,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
for (int i = 0; i < 5; i++)
{
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition - (Vector2.UnitX * item.Rect.Width/2),
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos,
-currForce / 5.0f + new Vector2(Rand.Range(-100.0f, 100.0f), Rand.Range(-50f, 50f)),
0.0f, item.CurrentHull);
}
@@ -91,24 +115,69 @@ namespace Barotrauma.Items.Components
voltage = 0.0f;
}
private void UpdatePropellerDamage(float deltaTime)
{
damageTimer += deltaTime;
if (damageTimer < 0.5f) return;
damageTimer = 0.1f;
if (propellerDamage == null) return;
Vector2 propellerWorldPos = item.WorldPosition + PropellerPos;
foreach (Character character in Character.CharacterList)
{
if (character.Submarine != null || !character.Enabled || character.Removed) continue;
float dist = Vector2.DistanceSquared(character.WorldPosition, propellerWorldPos);
if (dist > propellerDamage.DamageRange * propellerDamage.DamageRange) continue;
character.LastDamageSource = item;
propellerDamage.DoDamage(null, character, propellerWorldPos, 1.0f, true);
}
}
partial void UpdateAnimation(float deltaTime);
public override void UpdateBroken(float deltaTime, Camera cam)
{
force = MathHelper.Lerp(force, 0.0f, 0.1f);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
if (connection.Name == "set_force")
{
float tempForce;
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out tempForce))
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float tempForce))
{
targetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
}
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
//force can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger(-10, 10, (int)(targetForce / 10.0f));
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
float newTargetForce = msg.ReadRangedInteger(-10, 10) * 10.0f;
if (item.CanClientAccess(c))
{
if (Math.Abs(newTargetForce - targetForce) > 0.01f)
{
GameServer.Log(c.Character.LogName + " set the force of " + item.Name + " to " + (int)(newTargetForce) + " %", ServerLog.MessageType.ItemInteraction);
}
targetForce = newTargetForce;
}
//notify all clients of the changed state
item.CreateServerEvent(this);
}
}
}
@@ -10,91 +10,113 @@ namespace Barotrauma.Items.Components
{
class FabricableItem
{
public readonly ItemPrefab TargetItem;
public class RequiredItem
{
public readonly ItemPrefab ItemPrefab;
public int Amount;
public readonly float MinCondition;
public readonly bool UseCondition;
//TODO: refactor this (maybe make it a struct)
public readonly List<Tuple<ItemPrefab, int, float, bool>> RequiredItems;
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)
{
string name = element.GetAttributeString("name", "");
TargetItem = MapEntityPrefab.Find(name) as ItemPrefab;
if (TargetItem == null)
if (element.Attribute("name") != null)
{
return;
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<Tuple<ItemPrefab, int, float, bool>>();
//Backwards compatibility for string lists
string[] requiredItemNames = element.GetAttributeString("requireditems", "").Split(',');
foreach (string requiredItemName in requiredItemNames)
{
if (string.IsNullOrWhiteSpace(requiredItemName)) continue;
ItemPrefab requiredItem = MapEntityPrefab.Find(requiredItemName.Trim()) as ItemPrefab;
if (requiredItem == null)
{
DebugConsole.ThrowError("Error in fabricable item " + name + "! Required item \"" + requiredItemName + "\" not found.");
continue;
}
var existing = RequiredItems.Find(r => r.Item1 == requiredItem);
if (existing == null)
{
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, 1, 1.0f, false));
}
else
{
RequiredItems.Remove(existing);
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, existing.Item2 + 1, 1.0f, false));
}
}
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("name", ""),
subElement.GetAttributeString("identifier", ""),
subElement.GetAttributeInt("level", 0)));
break;
case "item": //New system allowing for setting minimal item condition
string requiredItemName = subElement.GetAttributeString("name", "");
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);
if (string.IsNullOrWhiteSpace(requiredItemName)) continue;
ItemPrefab requiredItem = MapEntityPrefab.Find(requiredItemName.Trim()) as ItemPrefab;
ItemPrefab requiredItem = MapEntityPrefab.Find(null, requiredItemIdentifier.Trim()) as ItemPrefab;
if (requiredItem == null)
{
DebugConsole.ThrowError("Error in fabricable item " + name + "! Required item \"" + requiredItemName + "\" not found.");
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Required item \"" + requiredItemIdentifier + "\" not found.");
continue;
}
var existing = RequiredItems.Find(r => r.Item1 == requiredItem);
var existing = RequiredItems.Find(r => r.ItemPrefab == requiredItem);
if (existing == null)
{
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, count, minCondition, useCondition));
RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition));
}
else
{
RequiredItems.Remove(existing);
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, existing.Item2 + count, minCondition, useCondition));
RequiredItems.Add(new RequiredItem(requiredItem, existing.Amount + count, minCondition, useCondition));
}
break;
@@ -106,15 +128,20 @@ namespace Barotrauma.Items.Components
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
{
public const float SkillIncreaseMultiplier = 0.5f;
private List<FabricableItem> fabricableItems;
private FabricableItem fabricatedItem;
private float timeUntilReady;
//used for checking if contained items have changed
//(in which case we need to recheck which items can be fabricated)
private Item[] prevContainedItems;
private float requiredTime;
private Character user;
private ItemContainer inputContainer, outputContainer;
private float progressState;
public Fabricator(Item item, XElement element)
: base(item, element)
{
@@ -138,89 +165,88 @@ namespace Barotrauma.Items.Components
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)
{
CheckFabricableItems(character);
#if CLIENT
if (itemList.Selected != null)
{
SelectItem(itemList.Selected, itemList.Selected.UserData);
}
#endif
SelectProjSpecific(character);
return base.Select(character);
}
partial void SelectProjSpecific(Character character);
public override bool Pick(Character picker)
{
return (picker != null);
}
/// <summary>
/// check which of the items can be fabricated by the character
/// and update the text colors of the item list accordingly
/// </summary>
private void CheckFabricableItems(Character character)
{
#if CLIENT
foreach (GUIComponent child in itemList.children)
{
var itemPrefab = child.UserData as FabricableItem;
if (itemPrefab == null) continue;
bool canBeFabricated = CanBeFabricated(itemPrefab, character);
child.GetChild<GUITextBlock>().TextColor = Color.White * (canBeFabricated ? 1.0f : 0.5f);
child.GetChild<GUIImage>().Color = itemPrefab.TargetItem.SpriteColor * (canBeFabricated ? 1.0f : 0.5f);
}
#endif
var itemContainer = item.GetComponent<ItemContainer>();
prevContainedItems = new Item[itemContainer.Inventory.Items.Length];
itemContainer.Inventory.Items.CopyTo(prevContainedItems, 0);
}
private void StartFabricating(FabricableItem selectedItem, Character user = null)
private void StartFabricating(FabricableItem selectedItem, Character user)
{
if (selectedItem == null) return;
if (user != null)
{
GameServer.Log(user.LogName + " started fabricating " + selectedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
#if CLIENT
itemList.Enabled = false;
activateButton.Text = "Cancel";
activateButton.Text = TextManager.Get("FabricatorCancel");
#endif
MoveIngredientsToInputContainer(selectedItem);
fabricatedItem = selectedItem;
IsActive = true;
timeUntilReady = fabricatedItem.RequiredTime;
var containers = item.GetComponents<ItemContainer>();
containers[0].Inventory.Locked = true;
containers[1].Inventory.Locked = 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.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
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;
@@ -228,100 +254,178 @@ namespace Barotrauma.Items.Components
itemList.Enabled = true;
if (activateButton != null)
{
activateButton.Text = "Create";
activateButton.Text = TextManager.Get("FabricatorCreate");
}
if (progressBar != null) progressBar.BarSize = 0.0f;
#endif
progressState = 0.0f;
timeUntilReady = 0.0f;
var containers = item.GetComponents<ItemContainer>();
containers[0].Inventory.Locked = false;
containers[1].Inventory.Locked = false;
inputContainer.Inventory.Locked = false;
outputContainer.Inventory.Locked = false;
}
public override void Update(float deltaTime, Camera cam)
{
if (fabricatedItem == null)
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem))
{
CancelFabricating();
return;
}
#if CLIENT
if (progressBar != null)
{
progressBar.BarSize = fabricatedItem == null ? 0.0f : (fabricatedItem.RequiredTime - timeUntilReady) / fabricatedItem.RequiredTime;
}
#endif
progressState = fabricatedItem == null ? 0.0f : (requiredTime - timeUntilReady) / requiredTime;
if (voltage < minVoltage) return;
if (voltage < minVoltage) { return; }
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (powerConsumption == 0) voltage = 1.0f;
timeUntilReady -= deltaTime*voltage;
if (powerConsumption <= 0) { voltage = 1.0f; }
timeUntilReady -= deltaTime * voltage;
voltage -= deltaTime * 10.0f;
if (timeUntilReady > 0.0f) return;
if (timeUntilReady > 0.0f) { return; }
var containers = item.GetComponents<ItemContainer>();
if (containers.Count < 2)
var availableIngredients = GetAvailableIngredients();
foreach (FabricableItem.RequiredItem ingredient in fabricatedItem.RequiredItems)
{
DebugConsole.ThrowError("Error while fabricating a new item: fabricators must have two ItemContainer components");
return;
}
foreach (Tuple<ItemPrefab, int, float, bool> ip in fabricatedItem.RequiredItems)
{
for (int i = 0; i < ip.Item2; i++)
for (int i = 0; i < ingredient.Amount; i++)
{
var requiredItem = containers[0].Inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ip.Item1 && it.Condition >= ip.Item1.Health * ip.Item3);
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 (ip.Item4 && requiredItem.Condition - ip.Item1.Health * ip.Item3 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
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 -= ip.Item1.Health * ip.Item3;
requiredItem.Condition -= ingredient.ItemPrefab.Health * ingredient.MinCondition;
continue;
}
Entity.Spawner.AddToRemoveQueue(requiredItem);
containers[0].Inventory.RemoveItem(requiredItem);
inputContainer.Inventory.RemoveItem(requiredItem);
}
}
if (containers[1].Inventory.Items.All(i => i != null))
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, containers[1].Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
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, Character user)
private bool CanBeFabricated(FabricableItem fabricableItem)
{
if (fabricableItem == null) return false;
if (fabricableItem == null) { return false; }
List<Item> availableIngredients = GetAvailableIngredients();
return CanBeFabricated(fabricableItem, availableIngredients);
}
if (user != null &&
fabricableItem.RequiredSkills.Any(skill => user.GetSkillLevel(skill.Name) < skill.Level))
private bool CanBeFabricated(FabricableItem fabricableItem, IEnumerable<Item> availableIngredients)
{
if (fabricableItem == null) { return false; }
foreach (FabricableItem.RequiredItem requiredItem in fabricableItem.RequiredItems)
{
return false;
if (availableIngredients.Count(it => IsItemValidIngredient(it, requiredItem)) < requiredItem.Amount)
{
return false;
}
}
ItemContainer container = item.GetComponent<ItemContainer>();
foreach (Tuple<ItemPrefab, int, float, bool> ip in fabricableItem.RequiredItems)
{
if (Array.FindAll(container.Inventory.Items, it => it != null && it.Prefab == ip.Item1 && it.Condition >= ip.Item1.Health * ip.Item3).Length < ip.Item2) 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);
@@ -351,6 +455,8 @@ namespace Barotrauma.Items.Components
{
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);
}
}
@@ -1,4 +1,5 @@
using System;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
@@ -10,11 +11,16 @@ namespace Barotrauma.Items.Components
{
public float? Oxygen;
public float? Water;
public bool Distort;
public float DistortionTimer;
}
private DateTime resetDataTime;
bool hasPower;
private bool hasPower;
private Dictionary<Hull, HullData> hullDatas;
[Editable(ToolTip = "Does the machine require inputs from water detectors in order to show the water levels inside rooms."), Serialize(false, true)]
public bool RequireWaterDetectors
@@ -30,35 +36,42 @@ namespace Barotrauma.Items.Components
set;
}
[Editable(ToolTip = "Should damaged walls be displayed by the machine."), Serialize(false, true)]
[Editable(ToolTip = "Should damaged walls be displayed by the machine."), Serialize(true, true)]
public bool ShowHullIntegrity
{
get;
set;
}
private Dictionary<Hull, HullData> hullDatas;
public MiniMap(Item item, XElement element)
: base(item, element)
{
IsActive = true;
hullDatas = new Dictionary<Hull, HullData>();
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
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)
if (DateTime.Now > resetDataTime)
{
hullDatas.Clear();
foreach (HullData hullData in hullDatas.Values)
{
if (!hullData.Distort)
{
hullData.Oxygen = null;
hullData.Water = null;
}
}
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
}
currPowerConsumption = powerConsumption;
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
hasPower = voltage > minVoltage;
if (hasPower)
@@ -71,28 +84,24 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
if (picker == null) return false;
//picker.SelectedConstruction = item;
return true;
return picker != null;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
if (sender == null || sender.CurrentHull == null) return;
if (source == null || source.CurrentHull == null) return;
Hull senderHull = sender.CurrentHull;
HullData hullData;
if (!hullDatas.TryGetValue(senderHull, out hullData))
Hull sourceHull = source.CurrentHull;
if (!hullDatas.TryGetValue(sourceHull, out HullData hullData))
{
hullData = new HullData();
hullDatas.Add(senderHull, hullData);
hullDatas.Add(sourceHull, hullData);
}
if (hullData.Distort) return;
switch (connection.Name)
{
case "water_data_in":
@@ -103,7 +112,7 @@ namespace Barotrauma.Items.Components
}
else
{
hullData.Water = Math.Min(senderHull.WaterVolume / senderHull.Volume, 1.0f);
hullData.Water = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
}
break;
case "oxygen_data_in":
@@ -9,9 +9,7 @@ namespace Barotrauma.Items.Components
class OxygenGenerator : Powered
{
private float powerDownTimer;
private bool running;
private float generatedAmount;
private List<Vent> ventList;
@@ -43,24 +41,29 @@ namespace Barotrauma.Items.Components
CurrFlow = 0.0f;
currPowerConsumption = powerConsumption;
//consume more power when in a bad condition
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
if (powerConsumption <= 0.0f)
{
voltage = 1.0f;
}
if (item.CurrentHull == null) return;
if (voltage < minVoltage)
{
powerDownTimer += deltaTime;
running = false;
return;
}
else
{
powerDownTimer = 0.0f;
}
running = true;
CurrFlow = Math.Min(voltage, 1.0f) * generatedAmount * 100.0f;
//item.CurrentHull.Oxygen += CurrFlow * deltaTime;
//less effective when in bad condition
CurrFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / 100.0f);
UpdateVents(CurrFlow);
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
private float? targetLevel;
public Hull hull1;
private bool hasPower;
[Serialize(0.0f, true)]
public float FlowPercentage
@@ -34,7 +34,7 @@ namespace Barotrauma.Items.Components
set { maxFlow = value; }
}
float currFlow;
private float currFlow;
public float CurrFlow
{
get
@@ -43,85 +43,59 @@ namespace Barotrauma.Items.Components
return Math.Abs(currFlow);
}
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
base.IsActive = value;
#if CLIENT
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
#endif
}
}
public Pump(Item item, XElement element)
: base(item, element)
{
GetHull();
InitProjSpecific();
}
partial void InitProjSpecific();
public override void Move(Vector2 amount)
{
base.Move(amount);
GetHull();
}
public override void OnMapLoaded()
{
GetHull();
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void Update(float deltaTime, Camera cam)
{
currFlow = 0.0f;
hasPower = false;
if (targetLevel != null)
{
float hullPercentage = 0.0f;
if (hull1 != null) hullPercentage = (hull1.WaterVolume / hull1.Volume) * 100.0f;
if (item.CurrentHull != null) hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f;
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
}
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
//pumps consume more power when in a bad condition
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
if (voltage < minVoltage) return;
UpdateProjSpecific(deltaTime);
hasPower = true;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
//check the hull if the item is movable
if (item.body != null) GetHull();
if (hull1 == null) return;
if (item.CurrentHull == null) { return; }
float powerFactor = currPowerConsumption <= 0.0f ? 1.0f : voltage;
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
//less effective when in a bad condition
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / 100.0f);
hull1.WaterVolume += currFlow;
if (hull1.WaterVolume > hull1.Volume) hull1.Pressure += 0.5f;
item.CurrentHull.WaterVolume += currFlow;
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
voltage = 0.0f;
}
private void GetHull()
{
hull1 = Hull.FindHull(item.WorldPosition, item.CurrentHull);
}
partial void UpdateProjSpecific(float deltaTime);
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
if (connection.Name == "toggle")
{
IsActive = !IsActive;
@@ -132,24 +106,43 @@ namespace Barotrauma.Items.Components
}
else if (connection.Name == "set_speed")
{
float tempSpeed;
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out tempSpeed))
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
{
flowPercentage = MathHelper.Clamp(tempSpeed, -100.0f, 100.0f);
}
}
else if (connection.Name == "set_targetlevel")
{
float tempTarget;
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out tempTarget))
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempTarget))
{
targetLevel = MathHelper.Clamp((tempTarget+100.0f)/2.0f, 0.0f, 100.0f);
targetLevel = MathHelper.Clamp((tempTarget + 100.0f) / 2.0f, 0.0f, 100.0f);
}
}
if (!IsActive) currPowerConsumption = 0.0f;
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (GameMain.Client != null) return false;
if (objective.Option.ToLowerInvariant() == "stoppumping")
{
if (FlowPercentage > 0.0f) item.CreateServerEvent(this);
FlowPercentage = 0.0f;
}
else
{
if (!IsActive || FlowPercentage > -100.0f)
{
item.CreateServerEvent(this);
}
IsActive = true;
FlowPercentage = -100.0f;
}
return true;
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
{
float newFlowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
@@ -1,162 +0,0 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Radar : Powered, IServerSerializable, IClientSerializable
{
private float range;
private float pingState;
private readonly Sprite pingCircle, screenOverlay;
private readonly Sprite radarBlip;
private float prevPingRadius;
float prevPassivePingRadius;
private Vector2 center;
private float displayRadius;
private float displayScale;
private float displayBorderSize;
[Serialize(10000.0f, false)]
public float Range
{
get { return range; }
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
}
[Serialize(false, false)]
public bool DetectSubmarineWalls
{
get;
set;
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
base.IsActive = value;
#if CLIENT
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
#endif
}
}
public Radar(Item item, XElement element)
: base(item, element)
{
#if CLIENT
radarBlips = new List<RadarBlip>();
#endif
displayBorderSize = element.GetAttributeFloat("displaybordersize", 0.0f);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "pingcircle":
pingCircle = new Sprite(subElement);
break;
case "screenoverlay":
screenOverlay = new Sprite(subElement);
break;
case "blip":
radarBlip = new Sprite(subElement);
break;
}
}
#if CLIENT
isActiveTickBox = new GUITickBox(new Rectangle(0, 0, 20, 20), "Active Sonar", Alignment.TopLeft, GuiFrame);
isActiveTickBox.OnSelected = (GUITickBox box) =>
{
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
else if (GameMain.Client != null)
{
item.CreateClientEvent(this);
correctionTimer = CorrectionDelay;
}
IsActive = box.Selected;
return true;
};
GuiFrame.CanBeFocused = false;
#endif
IsActive = false;
}
public override void Update(float deltaTime, Camera cam)
{
currPowerConsumption = powerConsumption;
UpdateOnActiveEffects(deltaTime);
if (voltage >= minVoltage || powerConsumption <= 0.0f)
{
pingState = pingState + deltaTime * 0.5f;
if (pingState > 1.0f)
{
if (item.CurrentHull != null) item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState, item.CurrentHull.AiTarget.SoundRange);
item.Use(deltaTime);
pingState = 0.0f;
}
}
else
{
pingState = 0.0f;
}
Voltage -= deltaTime;
}
public override bool Use(float deltaTime, Character character = null)
{
return pingState > 1.0f;
}
protected override void RemoveComponentSpecific()
{
if (pingCircle!=null) pingCircle.Remove();
if (screenOverlay != null) screenOverlay.Remove();
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
{
bool isActive = msg.ReadBoolean();
if (!item.CanClientAccess(c)) return;
IsActive = isActive;
#if CLIENT
isActiveTickBox.Selected = IsActive;
#endif
item.CreateServerEvent(this);
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
{
msg.Write(IsActive);
}
}
}
@@ -8,80 +8,99 @@ using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Reactor : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
partial class Reactor : Powered, IServerSerializable, IClientSerializable
{
const float NetworkUpdateInterval = 0.5f;
//the rate at which the reactor is being run un
//higher rates generate more power (and heat)
//the rate at which the reactor is being run on (higher rate -> higher temperature)
private float fissionRate;
//the rate at which the heat is being dissipated
private float coolingRate;
//how much of the generated steam is used to spin the turbines and generate power
private float turbineOutput;
private float temperature;
private Client BlameOnBroken;
//is automatic temperature control on
//(adjusts the cooling rate automatically to keep the
//(adjusts the fission rate and turbine output automatically to keep the
//amount of power generated balanced with the load)
private bool autoTemp;
//the temperature after which fissionrate is automatically
//turned down and cooling increased
private float shutDownTemp;
private Client BlameOnBroken;
private float fireTemp, meltDownTemp, meltDownDelay;
//automatical adjustment to the power output when
//turbine output and temperature are in the optimal range
private float autoAdjustAmount;
private float fuelConsumptionRate;
private float meltDownTimer;
private float meltDownTimer, meltDownDelay;
private float fireTimer, fireDelay;
//how much power is provided to the grid per 1 temperature unit
private float powerPerTemp;
private float maxPowerOutput;
private float load;
private bool unsentChanges;
private float sendUpdateTimer;
private Character lastUser;
private float degreeOfSuccess;
private float? nextServerLogWriteTime;
private float lastServerLogWriteTime;
[Editable(ToolTip = "The temperature at which the reactor melts down."), Serialize(9500.0f, true)]
public float MeltDownTemp
private Vector2 optimalTemperature, allowedTemperature;
private Vector2 optimalFissionRate, allowedFissionRate;
private Vector2 optimalTurbineOutput, allowedTurbineOutput;
private bool shutDown;
const float AIUpdateInterval = 1.0f;
private float aiUpdateTimer;
private Character lastUser;
private Character LastUser
{
get { return meltDownTemp; }
set
get { return lastUser; }
set
{
meltDownTemp = Math.Max(0.0f, value);
if (lastUser == value) return;
lastUser = value;
degreeOfSuccess = lastUser == null ? 0.0f : DegreeOfSuccess(lastUser);
}
}
[Serialize(30.0f, true)]
[Editable(0.0f, float.MaxValue, ToolTip = "How much power (kW) the reactor generates when operating at full capacity."), Serialize(10000.0f, true)]
public float MaxPowerOutput
{
get { return maxPowerOutput; }
set
{
maxPowerOutput = Math.Max(0.0f, value);
}
}
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until a meltdown occurs."), Serialize(30.0f, true)]
public float MeltdownDelay
{
get { return meltDownDelay; }
set { meltDownDelay = Math.Max(value, 0.0f); }
}
[Editable(ToolTip = "The temperature at which the reactor catches fire."), Serialize(9000.0f, true)]
public float FireTemp
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until the reactor catches fire."), Serialize(10.0f, true)]
public float FireDelay
{
get { return fireTemp; }
set
{
fireTemp = Math.Max(0.0f, value);
}
get { return fireDelay; }
set { fireDelay = Math.Max(value, 0.0f); }
}
[Editable(0.0f, float.MaxValue, ToolTip = "How much power (kW) the reactor generates relative to it's operating temperature (kW per one degree Celsius)."), Serialize(1.0f, true)]
public float PowerPerTemp
[Serialize(0.0f, true)]
public float Temperature
{
get { return powerPerTemp; }
get { return temperature; }
set
{
powerPerTemp = Math.Max(0.0f, value);
if (!MathUtils.IsValid(value)) return;
temperature = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
@@ -97,32 +116,32 @@ namespace Barotrauma.Items.Components
}
[Serialize(0.0f, true)]
public float CoolingRate
public float TurbineOutput
{
get { return coolingRate; }
get { return turbineOutput; }
set
{
if (!MathUtils.IsValid(value)) return;
coolingRate = MathHelper.Clamp(value, 0.0f, 100.0f);
turbineOutput = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
[Serialize(0.0f, true)]
public float Temperature
[Serialize(0.2f, true), Editable(0.0f, 1000.0f, ToolTip = "How fast the condition of the contained fuel rods deteriorates.")]
public float FuelConsumptionRate
{
get { return temperature; }
set
get { return fuelConsumptionRate; }
set
{
if (!MathUtils.IsValid(value)) return;
temperature = MathHelper.Clamp(value, 0.0f, 10000.0f);
fuelConsumptionRate = Math.Max(value, 0.0f);
}
}
public bool IsRunning()
{
return (temperature > 0.0f);
}
private float correctTurbineOutput;
private float targetFissionRate;
private float targetTurbineOutput;
[Serialize(false, true)]
public bool AutoTemp
{
@@ -131,109 +150,108 @@ namespace Barotrauma.Items.Components
{
autoTemp = value;
#if CLIENT
if (autoTempTickBox!=null) autoTempTickBox.Selected = value;
if (autoTempSlider != null)
{
autoTempSlider.BarScroll = value ?
Math.Min(0.45f, autoTempSlider.BarScroll) :
Math.Max(0.55f, autoTempSlider.BarScroll);
}
#endif
}
}
public float ExtraCooling { get; set; }
private float prevAvailableFuel;
public float AvailableFuel { get; set; }
private float availableHeat, availableCooling;
private float prevTemperature, temperatureChange;
[Serialize(500.0f, true)]
public float ShutDownTemp
{
get { return shutDownTemp; }
set { shutDownTemp = MathHelper.Clamp(value, 0.0f, 10000.0f); }
}
public Reactor(Item item, XElement element)
: base(item, element)
{
shutDownTemp = 500.0f;
powerPerTemp = 1.0f;
{
IsActive = true;
InitProjSpecific();
InitProjSpecific(element);
}
partial void InitProjSpecific();
public override void Update(float deltaTime, Camera cam)
partial void InitProjSpecific(XElement element);
public override void Update(float deltaTime, Camera cam)
{
if (GameMain.Server != null && nextServerLogWriteTime != null)
{
if (Timing.TotalTime >= (float)nextServerLogWriteTime)
{
GameServer.Log(lastUser.LogName + " adjusted reactor settings: " +
"Temperature: " + (int)temperature +
", Fission rate: " + (int)fissionRate +
", Cooling rate: " + (int)coolingRate +
", Cooling rate: " + coolingRate +
", Shutdown temp: " + shutDownTemp +
"Temperature: " + (int)(temperature * 100.0f) +
", Fission rate: " + (int)targetFissionRate +
", Turbine output: " + (int)targetTurbineOutput +
(autoTemp ? ", Autotemp ON" : ", Autotemp OFF"),
ServerLog.MessageType.ItemInteraction);
nextServerLogWriteTime = null;
lastServerLogWriteTime = (float)Timing.TotalTime;
}
}
prevAvailableFuel = AvailableFuel;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
fissionRate = Math.Min(fissionRate, AvailableFuel);
//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
correctTurbineOutput += MathHelper.Clamp((load / MaxPowerOutput * 100.0f) - correctTurbineOutput, -10.0f, 10.0f) * deltaTime;
//the amount of cooling is always non-zero, so that the reactor always needs
//to generate some amount of heat to prevent the temperature from dropping
availableCooling = Math.Max(ExtraCooling, 5.0f);
availableHeat = 80 * (AvailableFuel / 2000.0f);
//calculate tolerances of the meters based on the skills of the user
//more skilled characters have larger "sweet spots", making it easier to keep the power output at a suitable level
float tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
float temperatureTolerance = MathHelper.Lerp(10.0f, 20.0f, degreeOfSuccess);
optimalTemperature = Vector2.Lerp(new Vector2(40.0f, 60.0f), new Vector2(30.0f, 70.0f), degreeOfSuccess);
allowedTemperature = Vector2.Lerp(new Vector2(30.0f, 70.0f), new Vector2(10.0f, 90.0f), degreeOfSuccess);
float heat = availableHeat * fissionRate;
float heatDissipation = 50 * coolingRate + availableCooling;
float fissionRateTolerance = MathHelper.Lerp(10.0f, 20.0f, degreeOfSuccess);
optimalFissionRate = Vector2.Lerp(new Vector2(40.0f, 70.0f), new Vector2(30.0f, 85.0f), degreeOfSuccess);
allowedFissionRate = Vector2.Lerp(new Vector2(30.0f, 85.0f), new Vector2(20.0f, 98.0f), degreeOfSuccess);
float deltaTemp = (((heat - heatDissipation) * 5) - temperature) / 10000.0f;
Temperature = temperature + deltaTemp;
float heatAmount = fissionRate * (AvailableFuel / 100.0f) * 2.0f;
float temperatureDiff = (heatAmount - turbineOutput) - Temperature;
Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff));
if (item.InWater && AvailableFuel < 100.0f) Temperature -= 12.0f * deltaTime;
temperatureChange = Temperature - prevTemperature;
prevTemperature = temperature;
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(targetFissionRate, AvailableFuel), deltaTime);
TurbineOutput = MathHelper.Lerp(turbineOutput, targetTurbineOutput, deltaTime);
if (temperature > fireTemp && temperature - deltaTemp < fireTemp)
float temperatureFactor = Math.Min(temperature / 50.0f, 1.0f);
currPowerConsumption = -MaxPowerOutput * Math.Min(turbineOutput / 100.0f, temperatureFactor);
//if the turbine output and coolant flow are the optimal range,
//make the generated power slightly adjust according to the load
// (-> the reactor can automatically handle small changes in load as long as the values are roughly correct)
if (turbineOutput > optimalTurbineOutput.X && turbineOutput < optimalTurbineOutput.Y &&
temperature > optimalTemperature.X && temperature < optimalTemperature.Y)
{
#if CLIENT
Vector2 baseVel = Rand.Vector(300.0f);
for (int i = 0; i < 10; i++)
{
var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
}
#endif
new FireSource(item.WorldPosition);
}
if (temperature > meltDownTemp)
{
item.SendSignal(0, "1", "meltdown_warning", null);
meltDownTimer += deltaTime;
if (meltDownTimer > MeltdownDelay)
{
MeltDown();
return;
}
float maxAutoAdjust = maxPowerOutput * 0.1f;
autoAdjustAmount = MathHelper.Lerp(
autoAdjustAmount,
MathHelper.Clamp(-load - currPowerConsumption, -maxAutoAdjust, maxAutoAdjust),
deltaTime * 10.0f);
}
else
{
item.SendSignal(0, "0", "meltdown_warning", null);
meltDownTimer = Math.Max(0.0f, meltDownTimer - deltaTime);
autoAdjustAmount = MathHelper.Lerp(autoAdjustAmount, 0.0f, deltaTime * 10.0f);
}
currPowerConsumption += autoAdjustAmount;
if (shutDown)
{
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
}
else if (autoTemp)
{
UpdateAutoTemp(2.0f, deltaTime);
}
load = 0.0f;
List<Connection> connections = item.Connections;
if (connections != null && connections.Count > 0)
{
@@ -247,50 +265,40 @@ namespace Barotrauma.Items.Components
PowerTransfer pt = it.GetComponent<PowerTransfer>();
if (pt == null) continue;
load = Math.Max(load,pt.PowerLoad);
load = Math.Max(load, pt.PowerLoad);
}
}
}
//item.Condition -= temperature * deltaTime * 0.00005f;
if (temperature > shutDownTemp)
if (fissionRate > 0.0f)
{
CoolingRate += 0.5f;
FissionRate -= 0.5f;
}
else if (autoTemp)
{
//take deltaTemp into account to slow down the change in temperature when getting closer to the desired value
float target = temperature + deltaTemp * 100.0f;
foreach (Item item in item.ContainedItems)
{
if (!item.HasTag("reactorfuel")) continue;
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
}
//-1.0f in order to gradually turn down both rates when the target temperature is reached
FissionRate += (MathHelper.Clamp(load - target, -10.0f, 10.0f) - 1.0f) * deltaTime;
CoolingRate += (MathHelper.Clamp(target - load, -5.0f, 5.0f) - 1.0f) * deltaTime;
}
//the power generated by the reactor is equal to the temperature
currPowerConsumption = -temperature*powerPerTemp;
if (item.CurrentHull != null)
{
//the sound can be heard from 20 000 display units away when running at full power
item.CurrentHull.SoundRange = Math.Max(temperature * 2, item.CurrentHull.AiTarget.SoundRange);
if (item.CurrentHull != null)
{
//the sound can be heard from 20 000 display units away when running at full power
item.CurrentHull.SoundRange = Math.Max(
(-currPowerConsumption / MaxPowerOutput) * 20000.0f,
item.CurrentHull.AiTarget.SoundRange);
}
}
item.SendSignal(0, ((int)(temperature * 100.0f)).ToString(), "temperature_out", null);
UpdateFailures(deltaTime);
#if CLIENT
UpdateGraph(deltaTime);
#endif
ExtraCooling = 0.0f;
AvailableFuel = 0.0f;
item.SendSignal(0, ((int)temperature).ToString(), "temperature_out", null);
sendUpdateTimer = Math.Max(sendUpdateTimer - deltaTime, 0.0f);
if (unsentChanges && sendUpdateTimer<= 0.0f)
if (unsentChanges && sendUpdateTimer <= 0.0f)
{
if (GameMain.Server != null)
{
@@ -302,27 +310,78 @@ namespace Barotrauma.Items.Components
item.CreateClientEvent(this);
}
#endif
sendUpdateTimer = NetworkUpdateInterval;
unsentChanges = false;
}
}
}
private void UpdateFailures(float deltaTime)
{
if (temperature > allowedTemperature.Y)
{
item.SendSignal(0, "1", "meltdown_warning", null);
//faster meltdown if the item is in a bad condition
meltDownTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / 100.0f);
if (meltDownTimer > MeltdownDelay)
{
MeltDown();
return;
}
}
else
{
item.SendSignal(0, "0", "meltdown_warning", null);
meltDownTimer = Math.Max(0.0f, meltDownTimer - deltaTime);
}
if (temperature > optimalTemperature.Y)
{
float prevFireTimer = fireTimer;
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / 100.0f);
if (fireTimer >= FireDelay && prevFireTimer < fireDelay)
{
new FireSource(item.WorldPosition);
}
}
else
{
fireTimer = Math.Max(0.0f, fireTimer - deltaTime);
}
}
private void UpdateAutoTemp(float speed, float deltaTime)
{
float desiredTurbineOutput = (optimalTurbineOutput.X + optimalTurbineOutput.Y) / 2.0f;
targetTurbineOutput += MathHelper.Clamp(desiredTurbineOutput - targetTurbineOutput, -speed, speed) * deltaTime;
float desiredFissionRate = (optimalFissionRate.X + optimalFissionRate.Y) / 2.0f;
targetFissionRate += MathHelper.Clamp(desiredFissionRate - targetFissionRate, -speed, speed) * deltaTime;
if (temperature > (optimalTemperature.X + optimalTemperature.Y) / 2.0f)
{
targetFissionRate = Math.Min(targetFissionRate - speed * 2 * deltaTime, allowedFissionRate.Y);
}
else if (-currPowerConsumption < load)
{
targetFissionRate = Math.Min(targetFissionRate + speed * 2 * deltaTime, allowedFissionRate.Y);
}
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.UpdateBroken(deltaTime, cam);
currPowerConsumption = 0.0f;
Temperature -= deltaTime * 1000.0f;
FissionRate -= deltaTime * 10.0f;
CoolingRate -= deltaTime * 10.0f;
currPowerConsumption = -temperature;
targetFissionRate = Math.Max(targetFissionRate - deltaTime * 10.0f, 0.0f);
targetTurbineOutput = Math.Max(targetTurbineOutput - deltaTime * 10.0f, 0.0f);
#if CLIENT
fissionRateScrollBar.BarScroll = 1.0f - FissionRate / 100.0f;
turbineOutputScrollBar.BarScroll = 1.0f - TurbineOutput / 100.0f;
UpdateGraph(deltaTime);
#endif
ExtraCooling = 0.0f;
}
private void MeltDown()
@@ -332,6 +391,8 @@ namespace Barotrauma.Items.Components
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
item.Condition = 0.0f;
fireTimer = 0.0f;
meltDownTimer = 0.0f;
var containedItems = item.ContainedItems;
if (containedItems != null)
@@ -356,6 +417,8 @@ namespace Barotrauma.Items.Components
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (GameMain.Client != null) return false;
float degreeOfSuccess = DegreeOfSuccess(character);
//characters with insufficient skill levels don't refuel the reactor
@@ -371,65 +434,97 @@ namespace Barotrauma.Items.Components
}
}
//the temperature is too low and not increasing even though the fission rate is high and cooling low
// -> we need more fuel
if (temperature < load * 0.5f && temperatureChange <= 0.0f && fissionRate > 0.9f && coolingRate < 0.1f)
//we need more fuel
if (-currPowerConsumption < load * 0.5f && prevAvailableFuel <= 0.0f)
{
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "Fuel Rod", "reactorfuel" }, item.GetComponent<ItemContainer>());
containFuelObjective.MinContainedAmount = containedItems.Count(i => i != null && i.Prefab.NameMatches("Fuel Rod") || i.HasTag("reactorfuel")) + 1;
containFuelObjective.GetItemPriority = (Item fuelItem) =>
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent<ItemContainer>())
{
if (fuelItem.ParentInventory?.Owner is Item)
MinContainedAmount = containedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1,
GetItemPriority = (Item fuelItem) =>
{
//don't take fuel from other reactors
if (((Item)fuelItem.ParentInventory.Owner).GetComponent<Reactor>() != null) return 0.0f;
if (fuelItem.ParentInventory?.Owner is Item)
{
//don't take fuel from other reactors
if (((Item)fuelItem.ParentInventory.Owner).GetComponent<Reactor>() != null) return 0.0f;
}
return 1.0f;
}
return 1.0f;
};
objective.AddSubObjective(containFuelObjective);
character?.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
return false;
}
}
if (aiUpdateTimer > 0.0f)
{
aiUpdateTimer -= deltaTime;
return false;
}
if (lastUser != character && lastUser != null && lastUser.SelectedConstruction == item)
{
character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f);
}
LastUser = character;
switch (objective.Option.ToLowerInvariant())
{
case "power up":
float tempDiff = load - temperature;
shutDownTemp = Math.Min(load + 1000.0f, 7500.0f);
case "powerup":
shutDown = false;
//characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
if (Math.Abs(tempDiff) < 500.0f || degreeOfSuccess < 0.5f)
if (degreeOfSuccess < 0.5f)
{
if (!autoTemp) unsentChanges = true;
AutoTemp = true;
}
else
{
AutoTemp = false;
//higher skill levels make the character adjust the temperature faster
FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess;
CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess;
}
unsentChanges = true;
UpdateAutoTemp(2.0f + degreeOfSuccess * 5.0f, 1.0f);
}
#if CLIENT
onOffSwitch.BarScroll = 0.0f;
fissionRateScrollBar.BarScroll = FissionRate / 100.0f;
turbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f;
#endif
break;
case "shutdown":
shutDownTemp = 0.0f;
#if CLIENT
onOffSwitch.BarScroll = 1.0f;
#endif
AutoTemp = false;
shutDown = true;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
break;
}
aiUpdateTimer = AIUpdateInterval;
return false;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "shutdown":
if (shutDownTemp > 0.0f)
if (targetFissionRate > 0.0f || targetTurbineOutput > 0.0f)
{
shutDown = true;
AutoTemp = false;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
unsentChanges = true;
shutDownTemp = 0.0f;
#if CLIENT
onOffSwitch.BarScroll = 1.0f;
#endif
}
break;
}
@@ -438,41 +533,46 @@ namespace Barotrauma.Items.Components
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
bool autoTemp = msg.ReadBoolean();
float shutDownTemp = msg.ReadRangedSingle(0.0f, 10000.0f, 15);
float coolingRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
bool shutDown = msg.ReadBoolean();
float fissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
float turbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
if (!item.CanClientAccess(c)) return;
if (!autoTemp && AutoTemp) BlameOnBroken = c;
if (shutDownTemp > ShutDownTemp) BlameOnBroken = c;
if (fissionRate > FissionRate) BlameOnBroken = c;
if (turbineOutput < targetTurbineOutput) BlameOnBroken = c;
if (fissionRate > targetFissionRate) BlameOnBroken = c;
if (!this.shutDown && shutDown) BlameOnBroken = c;
AutoTemp = autoTemp;
ShutDownTemp = shutDownTemp;
this.shutDown = shutDown;
targetFissionRate = fissionRate;
targetTurbineOutput = turbineOutput;
CoolingRate = coolingRate;
FissionRate = fissionRate;
lastUser = c.Character;
LastUser = c.Character;
if (nextServerLogWriteTime == null)
{
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
}
#if CLIENT
fissionRateScrollBar.BarScroll = 1.0f - targetFissionRate / 100.0f;
turbineOutputScrollBar.BarScroll = 1.0f - targetTurbineOutput / 100.0f;
onOffSwitch.BarScroll = shutDown ? Math.Max(onOffSwitch.BarScroll, 0.55f) : Math.Min(onOffSwitch.BarScroll, 0.45f);
#endif
//need to create a server event to notify all clients of the changed state
unsentChanges = true;
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.WriteRangedSingle(temperature, 0.0f, 10000.0f, 16);
msg.Write(autoTemp);
msg.WriteRangedSingle(shutDownTemp, 0.0f, 10000.0f, 15);
msg.WriteRangedSingle(coolingRate, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(fissionRate, 0.0f, 100.0f, 8);
msg.Write(shutDown);
msg.WriteRangedSingle(temperature, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(targetFissionRate, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(targetTurbineOutput, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(degreeOfSuccess, 0.0f, 1.0f, 8);
}
}
}
@@ -0,0 +1,353 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Sonar : Powered, IServerSerializable, IClientSerializable
{
public const float DefaultSonarRange = 10000.0f;
class ConnectedTransducer
{
public readonly SonarTransducer Transducer;
public float SignalStrength;
public float DisconnectTimer;
public ConnectedTransducer(SonarTransducer transducer, float signalStrength, float disconnectTimer)
{
Transducer = transducer;
SignalStrength = signalStrength;
DisconnectTimer = disconnectTimer;
}
}
private const float DirectionalPingSector = 30.0f;
private static readonly float DirectionalPingDotProduct;
static Sonar()
{
DirectionalPingDotProduct = (float)Math.Cos(MathHelper.ToRadians(DirectionalPingSector) * 0.5f);
}
private float range;
private float pingState;
private const float MinZoom = 1.0f, MaxZoom = 4.0f;
private float zoom = 1.0f;
private bool useDirectionalPing = false;
private Vector2 lastPingDirection = new Vector2(1.0f, 0.0f);
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
//was the last ping sent with directional pinging
private bool isLastPingDirectional;
private readonly Sprite pingCircle, directionalPingCircle, screenOverlay, screenBackground;
private readonly Sprite sonarBlip;
private bool aiPingCheckPending;
//the float value is a timer used for disconnecting the transducer if no signal is received from it for 1 second
private List<ConnectedTransducer> connectedTransducers;
public IEnumerable<SonarTransducer> ConnectedTransducers
{
get { return connectedTransducers.Select(t => t.Transducer); }
}
[Serialize(DefaultSonarRange, false)]
public float Range
{
get { return range; }
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
}
[Serialize(false, false)]
public bool DetectSubmarineWalls
{
get;
set;
}
[Serialize(false, false), Editable(ToolTip = "Does the sonar have to be connected to external transducers to work.")]
public bool UseTransducers
{
get;
set;
}
public float Zoom
{
get { return zoom; }
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
base.IsActive = value;
if (!value && item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
}
#if CLIENT
if (activeTickBox != null) activeTickBox.Selected = value;
if (passiveTickBox != null) passiveTickBox.Selected = !value;
#endif
}
}
public Sonar(Item item, XElement element)
: base(item, element)
{
connectedTransducers = new List<ConnectedTransducer>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "pingcircle":
pingCircle = new Sprite(subElement);
break;
case "directionalpingcircle":
directionalPingCircle = new Sprite(subElement);
break;
case "screenoverlay":
screenOverlay = new Sprite(subElement);
break;
case "screenbackground":
screenBackground = new Sprite(subElement);
break;
case "blip":
sonarBlip = new Sprite(subElement);
break;
}
}
IsActive = false;
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void Update(float deltaTime, Camera cam)
{
currPowerConsumption = powerConsumption;
UpdateOnActiveEffects(deltaTime);
if (UseTransducers)
{
foreach (ConnectedTransducer transducer in connectedTransducers)
{
transducer.DisconnectTimer -= deltaTime;
}
connectedTransducers.RemoveAll(t => t.DisconnectTimer <= 0.0f);
}
if ((voltage >= minVoltage || powerConsumption <= 0.0f) &&
(!UseTransducers || connectedTransducers.Count > 0))
{
pingState = pingState + deltaTime * 0.5f;
if (pingState > 1.0f)
{
if (item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.CurrentHull.AiTarget.SoundRange);
item.CurrentHull.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f;
item.CurrentHull.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
}
if (item.AiTarget != null)
{
item.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.AiTarget.SoundRange);
item.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f;
item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
}
aiPingCheckPending = true;
isLastPingDirectional = useDirectionalPing;
lastPingDirection = pingDirection;
item.Use(deltaTime);
pingState = 0.0f;
}
}
else
{
if (item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
}
aiPingCheckPending = false;
pingState = 0.0f;
}
Voltage -= deltaTime;
}
public override bool Use(float deltaTime, Character character = null)
{
return pingState > 1.0f;
}
protected override void RemoveComponentSpecific()
{
sonarBlip?.Remove();
pingCircle?.Remove();
directionalPingCircle?.Remove();
screenOverlay?.Remove();
screenBackground?.Remove();
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (!IsActive || !aiPingCheckPending) return false;
Dictionary<string, List<Character>> targetGroups = new Dictionary<string, List<Character>>();
foreach (Character c in Character.CharacterList)
{
if (c.AnimController.CurrentHull != null || !c.Enabled) continue;
if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) continue;
if (Vector2.DistanceSquared(c.WorldPosition, item.WorldPosition) > range * range) continue;
string directionName = GetDirectionName(c.WorldPosition - item.WorldPosition);
if (!targetGroups.ContainsKey(directionName))
{
targetGroups.Add(directionName, new List<Character>());
}
targetGroups[directionName].Add(c);
}
foreach (KeyValuePair<string, List<Character>> targetGroup in targetGroups)
{
string dialogTag = "DialogSonarTarget";
if (targetGroup.Value.Count > 1)
{
dialogTag = "DialogSonarTargetMultiple";
}
else if (targetGroup.Value[0].Mass > 100.0f)
{
dialogTag = "DialogSonarTargetLarge";
}
character.Speak(TextManager.Get(dialogTag).Replace("[direction]", targetGroup.Key).Replace("[count]", targetGroup.Value.Count.ToString()),
null, 0, "sonartarget" + targetGroup.Value[0].ID, 30);
//prevent the character from reporting other targets in the group
for (int i = 1; i < targetGroup.Value.Count; i++)
{
character.DisableLine("sonartarget" + targetGroup.Value[i].ID);
}
}
return true;
}
private string GetDirectionName(Vector2 dir)
{
float angle = MathUtils.WrapAngleTwoPi((float)-Math.Atan2(dir.Y, dir.X) + MathHelper.PiOver2);
int clockDir = (int)Math.Round((angle / MathHelper.TwoPi) * 12);
if (clockDir == 0) clockDir = 12;
return TextManager.Get("SubDirOClock").Replace("[dir]", clockDir.ToString());
}
private Vector2 GetTransducerCenter()
{
if (!UseTransducers || connectedTransducers.Count == 0) return Vector2.Zero;
Vector2 transducerPosSum = Vector2.Zero;
foreach (ConnectedTransducer transducer in connectedTransducers)
{
transducerPosSum += transducer.Transducer.Item.WorldPosition;
}
return transducerPosSum / connectedTransducers.Count;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
if (connection.Name == "transducer_in")
{
var transducer = source.GetComponent<SonarTransducer>();
if (transducer == null) return;
var connectedTransducer = connectedTransducers.Find(t => t.Transducer == transducer);
if (connectedTransducer == null)
{
connectedTransducers.Add(new ConnectedTransducer(transducer, signalStrength, 1.0f));
}
else
{
connectedTransducer.SignalStrength = signalStrength;
connectedTransducer.DisconnectTimer = 1.0f;
}
}
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
{
bool isActive = msg.ReadBoolean();
bool directionalPing = useDirectionalPing;
float zoomT = zoom, pingDirectionT = 0.0f;
if (isActive)
{
zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
directionalPing = msg.ReadBoolean();
if (directionalPing)
{
pingDirectionT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
}
}
if (!item.CanClientAccess(c)) return;
IsActive = isActive;
#if CLIENT
activeTickBox.Selected = IsActive;
#endif
if (isActive)
{
zoom = MathHelper.Lerp(MinZoom, MaxZoom, zoomT);
useDirectionalPing = directionalPing;
if (useDirectionalPing)
{
float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, pingDirectionT);
pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
}
#if CLIENT
zoomSlider.BarScroll = zoomT;
directionalTickBox.Selected = useDirectionalPing;
directionalSlider.BarScroll = pingDirectionT;
#endif
}
item.CreateServerEvent(this);
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(IsActive);
if (IsActive)
{
msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8);
msg.Write(useDirectionalPing);
if (useDirectionalPing)
{
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
}
}
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class SonarTransducer : Powered
{
const float SendSignalInterval = 0.5f;
private float sendSignalTimer;
public SonarTransducer(Item item, XElement element) : base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
UpdateOnActiveEffects(deltaTime);
if (voltage >= minVoltage || PowerConsumption <= 0.0f)
{
sendSignalTimer += deltaTime;
if (sendSignalTimer > SendSignalInterval)
{
item.SendSignal(0, "0101101101101011010", "data_out", sender: null);
sendSignalTimer = SendSignalInterval;
}
}
voltage = 0.0f;
}
}
}
@@ -2,7 +2,9 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using Voronoi2;
@@ -11,10 +13,13 @@ namespace Barotrauma.Items.Components
partial class Steering : Powered, IServerSerializable, IClientSerializable
{
private const float AutopilotRayCastInterval = 0.5f;
private const float RecalculatePathInterval = 10.0f;
private Vector2 currVelocity;
private Vector2 targetVelocity;
private Vector2 steeringInput;
private bool autoPilot;
private Vector2? posToMaintain;
@@ -27,10 +32,19 @@ namespace Barotrauma.Items.Components
private bool unsentChanges;
private float autopilotRayCastTimer;
private float autopilotRecalculatePathTimer;
private Vector2 avoidStrength;
private float neutralBallastLevel;
private float steeringAdjustSpeed = 1.0f;
private Character user;
private Sonar sonar;
private Submarine controlledSub;
public bool AutoPilot
{
@@ -38,11 +52,10 @@ namespace Barotrauma.Items.Components
set
{
if (value == autoPilot) return;
autoPilot = value;
#if CLIENT
autopilotTickBox.Selected = value;
autopilotTickBox.Selected = autoPilot;
manualTickBox.Selected = !autoPilot;
maintainPosTickBox.Enabled = autoPilot;
levelEndTickBox.Enabled = autoPilot;
levelStartTickBox.Enabled = autoPilot;
@@ -50,24 +63,19 @@ namespace Barotrauma.Items.Components
if (autoPilot)
{
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
#if CLIENT
ToggleMaintainPosition(maintainPosTickBox);
#endif
MaintainPos = true;
}
#if CLIENT
else
{
maintainPosTickBox.Selected = false;
levelEndTickBox.Selected = false;
levelStartTickBox.Selected = false;
posToMaintain = null;
PosToMaintain = null;
MaintainPos = false;
LevelEndSelected = false;
LevelStartSelected = false;
}
#endif
}
}
[Editable(0.0f, 1.0f, ToolTip = "How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
[Editable(0.0f, 1.0f, decimals: 3, ToolTip = "How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
+" Can be used to compensate if the ballast tanks are too large/small relative to the size of the submarine."), Serialize(0.5f, true)]
public float NeutralBallastLevel
{
@@ -78,6 +86,13 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(1000.0f, true)]
public float DockingAssistThreshold
{
get;
set;
}
public Vector2 TargetVelocity
{
get { return targetVelocity;}
@@ -88,12 +103,53 @@ namespace Barotrauma.Items.Components
targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
}
}
public Vector2 SteeringInput
{
get { return steeringInput; }
set
{
if (!MathUtils.IsValid(value)) return;
steeringInput.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
steeringInput.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
}
}
public SteeringPath SteeringPath
{
get { return steeringPath; }
}
public Vector2? PosToMaintain
{
get { return posToMaintain; }
set { posToMaintain = value; }
}
struct ObstacleDebugInfo
{
public Vector2 Point1;
public Vector2 Point2;
public Vector2? Intersection;
public float Dot;
public Vector2 AvoidStrength;
public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength)
{
Point1 = edge.Point1;
Point2 = edge.Point2;
Intersection = intersection;
Dot = dot;
AvoidStrength = avoidStrength;
}
}
//edge point 1, edge point 2, avoid strength
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();
public Steering(Item item, XElement element)
: base(item, element)
{
@@ -103,11 +159,24 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific();
public override void OnItemLoaded()
{
sonar = item.GetComponent<Sonar>();
}
public override bool Select(Character character)
{
if (!CanBeSelected) return false;
user = character;
return true;
}
public override void Update(float deltaTime, Camera cam)
{
networkUpdateTimer -= deltaTime;
if (unsentChanges)
{
networkUpdateTimer -= deltaTime;
if (networkUpdateTimer <= 0.0f)
{
#if CLIENT
@@ -123,14 +192,21 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
networkUpdateTimer = 0.5f;
networkUpdateTimer = 0.1f;
unsentChanges = false;
}
}
controlledSub = item.Submarine;
var sonar = item.GetComponent<Sonar>();
if (sonar != null && sonar.UseTransducers)
{
controlledSub = sonar.ConnectedTransducers.Any() ? sonar.ConnectedTransducers.First().Item.Submarine : null;
}
currPowerConsumption = powerConsumption;
if (voltage < minVoltage && currPowerConsumption > 0.0f) return;
if (voltage < minVoltage && currPowerConsumption > 0.0f) { return; }
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
@@ -138,6 +214,30 @@ namespace Barotrauma.Items.Components
{
UpdateAutoPilot(deltaTime);
}
else
{
if (user != null && user.Info != null && user.SelectedConstruction == item)
{
user.Info.IncreaseSkillLevel("helm", 0.005f * deltaTime, user.WorldPosition + Vector2.UnitY * 150.0f);
}
Vector2 velocityDiff = steeringInput - targetVelocity;
if (velocityDiff != Vector2.Zero)
{
if (steeringAdjustSpeed >= 0.99f)
{
TargetVelocity = steeringInput;
}
else
{
float steeringChange = 1.0f / (1.0f - steeringAdjustSpeed);
steeringChange *= steeringChange * 10.0f;
TargetVelocity += Vector2.Normalize(velocityDiff) *
Math.Min(steeringChange * deltaTime, velocityDiff.Length());
}
}
}
item.SendSignal(0, targetVelocity.X.ToString(CultureInfo.InvariantCulture), "velocity_x_out", null);
@@ -151,6 +251,7 @@ namespace Barotrauma.Items.Components
private void UpdateAutoPilot(float deltaTime)
{
if (controlledSub == null) return;
if (posToMaintain != null)
{
SteerTowardsPosition((Vector2)posToMaintain);
@@ -158,34 +259,53 @@ namespace Barotrauma.Items.Components
}
autopilotRayCastTimer -= deltaTime;
autopilotRecalculatePathTimer -= deltaTime;
if (autopilotRecalculatePathTimer <= 0.0f)
{
//periodically recalculate the path in case the sub ends up to a position
//where it can't keep traversing the initially calculated path
UpdatePath();
autopilotRecalculatePathTimer = RecalculatePathInterval;
}
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(item.Submarine.WorldPosition), 10.0f);
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(controlledSub.WorldPosition), 10.0f);
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
{
Vector2 diff = Vector2.Normalize(ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - item.Submarine.WorldPosition));
Vector2 diff = ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - controlledSub.WorldPosition);
bool nextVisible = true;
for (int x = -1; x < 2; x += 2)
//if the node is close enough, check if it's visible
float lengthSqr = diff.LengthSquared();
if (lengthSqr > 0.001f && lengthSqr < 500.0f)
{
for (int y = -1; y < 2; y += 2)
diff = Vector2.Normalize(diff);
//check if the next waypoint is visible from all corners of the sub
//(i.e. if we can navigate directly towards it or if there's obstacles in the way)
bool nextVisible = true;
for (int x = -1; x < 2; x += 2)
{
Vector2 cornerPos =
new Vector2(item.Submarine.Borders.Width * x, item.Submarine.Borders.Height * y) / 2.0f;
for (int y = -1; y < 2; y += 2)
{
Vector2 cornerPos =
new Vector2(controlledSub.Borders.Width * x, controlledSub.Borders.Height * y) / 2.0f;
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.2f + item.Submarine.WorldPosition);
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.2f + controlledSub.WorldPosition);
float dist = Vector2.Distance(cornerPos, steeringPath.NextNode.SimPosition);
float dist = Vector2.Distance(cornerPos, steeringPath.NextNode.SimPosition);
if (Submarine.PickBody(cornerPos, cornerPos + diff * dist, null, Physics.CollisionLevel) == null) continue;
if (Submarine.PickBody(cornerPos, cornerPos + diff * dist, null, Physics.CollisionLevel) == null) continue;
nextVisible = false;
x = 2;
y = 2;
nextVisible = false;
x = 2;
y = 2;
}
}
if (nextVisible) steeringPath.SkipToNextNode();
}
if (nextVisible) steeringPath.SkipToNextNode();
autopilotRayCastTimer = AutopilotRayCastInterval;
}
@@ -195,35 +315,48 @@ namespace Barotrauma.Items.Components
SteerTowardsPosition(steeringPath.CurrentNode.WorldPosition);
}
float avoidRadius = Math.Max(item.Submarine.Borders.Width, item.Submarine.Borders.Height) * 2.0f;
avoidRadius = Math.Max(avoidRadius, 2000.0f);
Vector2 avoidDist = new Vector2(
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.X), controlledSub.Borders.Width * 1.5f),
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.Y), controlledSub.Borders.Height * 1.5f));
float avoidRadius = avoidDist.Length();
Vector2 newAvoidStrength = Vector2.Zero;
debugDrawObstacles.Clear();
//steer away from nearby walls
var closeCells = Level.Loaded.GetCells(item.Submarine.WorldPosition, 4);
var closeCells = Level.Loaded.GetCells(controlledSub.WorldPosition, 4);
foreach (VoronoiCell cell in closeCells)
{
foreach (GraphEdge edge in cell.edges)
foreach (GraphEdge edge in cell.Edges)
{
var intersection = MathUtils.GetLineIntersection(edge.point1, edge.point2, item.Submarine.WorldPosition, cell.Center);
if (intersection != null)
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, controlledSub.WorldPosition, cell.Center, out Vector2 intersection))
{
Vector2 diff = item.Submarine.WorldPosition - (Vector2)intersection;
Vector2 diff = controlledSub.WorldPosition - intersection;
float dist = diff.Length();
//far enough or too close to normalize the diff -> ignore
if (dist > avoidRadius || dist < 0.00001f) continue;
//far enough -> ignore
if (Math.Abs(diff.X) > avoidDist.X && Math.Abs(diff.Y) > avoidDist.Y)
{
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, 0.0f, Vector2.Zero));
continue;
}
if (diff.LengthSquared() < 1.0f) diff = Vector2.UnitY;
float dot = item.Submarine.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(item.Submarine.Velocity, -Vector2.Normalize(diff));
Vector2 normalizedDiff = Vector2.Normalize(diff);
float dot = controlledSub.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(controlledSub.Velocity, -normalizedDiff);
//not heading towards the wall -> ignore
if (dot < 0.5) continue;
Vector2 change = (Vector2.Normalize(diff) * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
if (dot < 0.5)
{
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, Vector2.Zero));
continue;
}
Vector2 change = (normalizedDiff * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
newAvoidStrength += change * dot;
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, change * dot));
}
}
}
@@ -235,22 +368,21 @@ namespace Barotrauma.Items.Components
//steer away from other subs
foreach (Submarine sub in Submarine.Loaded)
{
if (sub == item.Submarine) continue;
if (item.Submarine.DockedTo.Contains(sub)) continue;
if (sub == controlledSub) continue;
if (controlledSub.DockedTo.Contains(sub)) continue;
float thisSize = Math.Max(item.Submarine.Borders.Width, item.Submarine.Borders.Height);
float thisSize = Math.Max(controlledSub.Borders.Width, controlledSub.Borders.Height);
float otherSize = Math.Max(sub.Borders.Width, sub.Borders.Height);
Vector2 diff = item.Submarine.WorldPosition - sub.WorldPosition;
Vector2 diff = controlledSub.WorldPosition - sub.WorldPosition;
float dist = diff == Vector2.Zero ? 0.0f : diff.Length();
//far enough -> ignore
if (dist > thisSize + otherSize) continue;
Vector2 dir = dist <= 0.0001f ? Vector2.UnitY : diff / dist;
float dot = item.Submarine.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), -dir);
float dot = controlledSub.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(Vector2.Normalize(controlledSub.Velocity), -dir);
//heading away -> ignore
if (dot < 0.0f) continue;
@@ -264,7 +396,6 @@ namespace Barotrauma.Items.Components
{
targetVelocity *= 100.0f / velMagnitude;
}
}
private void UpdatePath()
@@ -280,20 +411,15 @@ namespace Barotrauma.Items.Components
{
target = ConvertUnits.ToSimUnits(Level.Loaded.StartPosition);
}
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(item.WorldPosition), target);
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition), target, "(Autopilot, target: " + target + ")");
}
public void SetDestinationLevelStart()
{
AutoPilot = true;
MaintainPos = false;
posToMaintain = null;
LevelEndSelected = false;
if (!LevelStartSelected)
{
LevelStartSelected = true;
@@ -303,13 +429,10 @@ namespace Barotrauma.Items.Components
public void SetDestinationLevelEnd()
{
AutoPilot = false;
AutoPilot = true;
MaintainPos = false;
posToMaintain = null;
LevelStartSelected = false;
if (!LevelEndSelected)
{
LevelEndSelected = true;
@@ -320,10 +443,10 @@ namespace Barotrauma.Items.Components
{
float prediction = 10.0f;
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(item.Submarine.Velocity) * prediction;
Vector2 targetSpeed = ((worldPosition - item.Submarine.WorldPosition) - futurePosition);
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(controlledSub.Velocity) * prediction;
Vector2 targetSpeed = ((worldPosition - controlledSub.WorldPosition) - futurePosition);
if (targetSpeed.Length()>500.0f)
if (targetSpeed.Length() > 500.0f)
{
targetSpeed = Vector2.Normalize(targetSpeed);
TargetVelocity = targetSpeed * 100.0f;
@@ -333,8 +456,52 @@ namespace Barotrauma.Items.Components
TargetVelocity = targetSpeed / 5.0f;
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (user != character && user != null && user.SelectedConstruction == item)
{
character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f);
}
user = character;
switch (objective.Option.ToLowerInvariant())
{
case "maintainposition":
if (!posToMaintain.HasValue)
{
unsentChanges = true;
posToMaintain = controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition;
}
if (!AutoPilot || !MaintainPos) unsentChanges = true;
AutoPilot = true;
MaintainPos = true;
break;
case "navigateback":
if (!AutoPilot || MaintainPos || LevelEndSelected || !LevelStartSelected)
{
unsentChanges = true;
}
SetDestinationLevelStart();
break;
case "navigatetodestination":
if (!AutoPilot || MaintainPos || !LevelEndSelected || LevelStartSelected)
{
unsentChanges = true;
}
SetDestinationLevelEnd();
break;
}
sonar?.AIOperate(deltaTime, character, objective);
return false;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection.Name == "velocity_in")
{
@@ -342,14 +509,14 @@ namespace Barotrauma.Items.Components
}
else
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
}
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
{
bool autoPilot = msg.ReadBoolean();
Vector2 newTargetVelocity = targetVelocity;
Vector2 newSteeringInput = targetVelocity;
bool maintainPos = false;
Vector2? newPosToMaintain = null;
bool headingToStart = false;
@@ -370,20 +537,22 @@ namespace Barotrauma.Items.Components
}
else
{
newTargetVelocity = new Vector2(msg.ReadFloat(), msg.ReadFloat());
newSteeringInput = new Vector2(msg.ReadFloat(), msg.ReadFloat());
}
if (!item.CanClientAccess(c)) return;
if (!item.CanClientAccess(c)) return;
user = c.Character;
AutoPilot = autoPilot;
if (!AutoPilot)
{
targetVelocity = newTargetVelocity;
steeringInput = newSteeringInput;
steeringAdjustSpeed = MathHelper.Lerp(0.2f, 1.0f, c.Character.GetSkillLevel("helm") / 100.0f);
}
else
{
MaintainPos = newPosToMaintain != null;
posToMaintain = newPosToMaintain;
@@ -411,8 +580,11 @@ namespace Barotrauma.Items.Components
if (!autoPilot)
{
//no need to write steering info if autopilot is controlling
msg.Write(steeringInput.X);
msg.Write(steeringInput.Y);
msg.Write(targetVelocity.X);
msg.Write(targetVelocity.Y);
msg.Write(steeringAdjustSpeed);
}
else
{