(45f083a5a) Merge branch 'dev' of https://github.com/Regalis11/Barotrauma-development into dev

This commit is contained in:
Joonas Rikkonen
2019-04-03 16:22:12 +03:00
parent 0dd1823eb1
commit 0ecfc426ff
44 changed files with 660 additions and 549 deletions
@@ -631,8 +631,7 @@ namespace Barotrauma
else
{
// If the secondary cooldown is defined and expired, check if we can switch the attack
var previousLimb = AttackingLimb;
var newLimb = GetAttackLimb(attackWorldPos, previousLimb);
var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb);
if (newLimb != null)
{
// Attack with the new limb
@@ -680,8 +679,7 @@ namespace Barotrauma
else
{
// If the secondary cooldown is defined and expired, check if we can switch the attack
var previousLimb = AttackingLimb;
var newLimb = GetAttackLimb(attackWorldPos, previousLimb);
var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb);
if (newLimb != null)
{
// Attack with the new limb
@@ -707,16 +705,15 @@ namespace Barotrauma
default:
UpdateFallBack(attackWorldPos, deltaTime);
return;
}
}
if (AttackingLimb == null || _previousAiTarget != SelectedAiTarget)
{
AttackingLimb = GetAttackLimb(attackWorldPos);
}
if (canAttack)
{
if (AttackingLimb == null || _previousAiTarget != SelectedAiTarget)
{
AttackingLimb = GetAttackLimb(attackWorldPos);
}
canAttack = AttackingLimb != null && AttackingLimb.attack.CoolDownTimer <= 0;
}
float distance = 0;
@@ -725,6 +722,12 @@ namespace Barotrauma
// Check that we can reach the target
distance = Vector2.Distance(AttackingLimb.WorldPosition, attackWorldPos);
canAttack = distance < AttackingLimb.attack.Range;
if (!canAttack && !IsCoolDownRunning)
{
// If not, reset the attacking limb, if the cooldown is not running
// Don't use the property, because we don't want cancel reversing, if we are reversing.
_attackingLimb = null;
}
}
// If the attacking limb is a hand or claw, for example, using it as the steering limb can end in the result where the character circles around the target. For example the Hammerhead steering with the claws when it should use the torso.
@@ -794,7 +797,6 @@ namespace Barotrauma
{
UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance);
}
return false;
}
private bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime)
@@ -1041,7 +1043,7 @@ namespace Barotrauma
}
else
{
steeringManager.SteeringSeek(attackSimPosition - (mouthPos - SimPosition));
steeringManager.SteeringSeek(attackSimPosition - (mouthPos - SimPosition), 2);
}
}
@@ -1165,15 +1167,15 @@ namespace Barotrauma
else if (target.Entity is Structure s)
{
targetingTag = "wall";
if (!s.HasBody)
{
// Ignore structures that doesn't have a body (not walls)
continue;
}
// Ignore walls when inside.
valueModifier = character.CurrentHull == null ? 1 : 0;
if (aggressiveBoarding)
{
// Ignore walls when inside.
valueModifier = character.CurrentHull == null ? 2 : 0;
if (valueModifier > 0)
{
// Ignore structures that doesn't have a body (not walls)
valueModifier *= s.HasBody ? 1 : 0;
}
for (int i = 0; i < s.Sections.Length; i++)
{
var section = s.Sections[i];
@@ -1190,6 +1192,23 @@ namespace Barotrauma
}
}
}
else
{
// Ignore disabled walls
bool isDisabled = true;
for (int i = 0; i < s.Sections.Length; i++)
{
if (!s.SectionBodyDisabled(i))
{
isDisabled = false;
break;
}
}
if (isDisabled)
{
valueModifier = 0;
}
}
}
else
{
@@ -84,12 +84,9 @@ namespace Barotrauma
useExtinquisherTimer += deltaTime;
if (useExtinquisherTimer > 2.0f) useExtinquisherTimer = 0.0f;
character.AIController.SteeringManager.Reset();
character.CursorPosition = fs.Position;
character.SetInput(InputType.Aim, false, true);
if (!character.IsClimbing)
{
character.AIController.SteeringManager.Reset();
}
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
@@ -168,8 +168,11 @@ namespace Barotrauma
// Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - Item.SimPosition) / 2);
}
if (character.IsClimbing ||
VectorExtensions.Angle(VectorExtensions.Forward(repairTool.Item.body.TransformedRotation), fromToolToTarget) < MathHelper.PiOver4)
else
{
character.AIController.SteeringManager.Reset();
}
if (VectorExtensions.Angle(VectorExtensions.Forward(repairTool.Item.body.TransformedRotation), fromToolToTarget) < MathHelper.PiOver4)
{
repairTool.Use(deltaTime, character);
}
@@ -696,7 +696,7 @@ namespace Barotrauma
return;
}
limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength);
limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength, maxVelocity: 10.0f);
}
}
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma
{
@@ -1122,9 +1123,6 @@ namespace Barotrauma
//prevent the hands from going above the top of the ladders
handPos.Y = Math.Min(-0.5f, handPos.Y);
//prevent the hands from going above the top of the ladders
handPos.Y = Math.Min(-0.5f, handPos.Y);
// TODO: lock only one hand when aiming?
if (!PlayerInput.KeyDown(InputType.Aim) || Math.Abs(movement.Y) > 0.01f)
{
@@ -1660,7 +1658,7 @@ namespace Barotrauma
Holdable holdable = item.GetComponent<Holdable>();
if (!character.IsClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero)
if (!isClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero)
{
Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
@@ -1690,7 +1688,7 @@ namespace Barotrauma
}
Vector2 transformedHoldPos = shoulder.WorldAnchorA;
if (itemPos == Vector2.Zero || character.IsClimbing || usingController)
if (itemPos == Vector2.Zero || isClimbing || usingController)
{
if (character.SelectedItems[0] == item)
{
@@ -1779,8 +1777,6 @@ namespace Barotrauma
item.SetTransform(currItemPos, itemAngle + itemAngleRelativeToHoldAngle * Dir);
if (character.IsClimbing) return;
if (!isClimbing)
{
for (int i = 0; i < 2; i++)
@@ -1147,7 +1147,7 @@ namespace Barotrauma
{
//Limb head = AnimController.GetLimb(LimbType.Head);
// Values lower than this seem to cause constantious flipping when the mouse is near the player and the player is running, because the root collider moves after flipping.
float followMargin = 30;
float followMargin = 40;
if (dontFollowCursor)
{
AnimController.TargetDir = Direction.Right;
@@ -2187,7 +2187,7 @@ namespace Barotrauma
if (limbHit == null) return new AttackResult();
limbHit.body?.ApplyLinearImpulse(attack.TargetImpulseWorld + attack.TargetForceWorld * deltaTime);
limbHit.body?.ApplyLinearImpulse(attack.TargetImpulseWorld + attack.TargetForceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
#if SERVER
if (attacker is Character attackingCharacter && attackingCharacter.AIController == null)
{
@@ -2273,7 +2273,8 @@ namespace Barotrauma
{
Vector2 diff = dir;
if (diff == Vector2.Zero) diff = Rand.Vector(1.0f);
hitLimb.body.ApplyLinearImpulse(Vector2.Normalize(diff) * attackImpulse, hitLimb.SimPosition + ConvertUnits.ToSimUnits(diff));
hitLimb.body.ApplyLinearImpulse(Vector2.Normalize(diff) * attackImpulse, hitLimb.SimPosition + ConvertUnits.ToSimUnits(diff),
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir);
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound);
@@ -14,17 +14,17 @@ namespace Barotrauma
public readonly AnimController.Animation Animation;
public CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, rotation, velocity, angularVelocity, 0, time, dir, interact, animation)
public CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, float time, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, rotation, velocity, angularVelocity, 0, time, dir, selectedCharacter, selectedItem, animation)
{
}
public CharacterStateInfo(Vector2 pos, float? rotation, UInt16 ID, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, interact, animation)
public CharacterStateInfo(Vector2 pos, float? rotation, UInt16 ID, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, selectedCharacter, selectedItem, animation)
{
}
protected CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, UInt16 ID, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
protected CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, UInt16 ID, float time, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None)
: base(pos, rotation, velocity, angularVelocity, ID, time)
{
Direction = dir;
@@ -626,13 +626,17 @@ namespace Barotrauma
Limb limb = character.AnimController.Limbs[limbIndex];
Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA;
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos);
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos,
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
}
else
{
Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
body.ApplyLinearImpulse(Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos);
body.ApplyLinearImpulse(
Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition),
forcePos,
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
}
return wasHit;
@@ -1,4 +1,5 @@
using FarseerPhysics;
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Collision;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
@@ -164,11 +165,12 @@ namespace Barotrauma.Items.Components
//set the rotation of the projectile again because dropping the projectile resets the rotation
projectile.Item.SetTransform(projectilePos,
rotation + ((item.body.Dir == 1.0f) ? projectile.LaunchRotationRadians : projectile.LaunchRotationRadians - MathHelper.Pi));
rotation + (projectile.Item.body.Dir * projectile.LaunchRotationRadians));
//recoil
item.body.ApplyLinearImpulse(
new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f);
new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f,
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
item.RemoveContained(projectile.Item);
@@ -303,10 +303,8 @@ namespace Barotrauma.Items.Components
// Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition) / 2);
}
else if (!character.IsClimbing)
else
{
// Close enough -> stop if not in ladders.
// In ladders, we most likely want to move back and forth, because we cannot aim -> if the leak is on the side, it should get fixed.
character.AIController.SteeringManager.Reset();
}
}
@@ -317,7 +315,7 @@ namespace Barotrauma.Items.Components
// Press the trigger only when the tool is approximately facing the target.
// If the character is climbing, ignore the check, because we cannot aim while climbing.
if (character.IsClimbing || VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
if (VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
{
Use(deltaTime, character);
}
@@ -106,10 +106,10 @@ namespace Barotrauma.Items.Components
#endif
Character thrower = picker;
item.Drop(thrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f);
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector*10.0f);
ac.GetLimb(LimbType.Torso).body.ApplyLinearImpulse(throwVector * 10.0f);
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
ac.GetLimb(LimbType.Torso).body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
Limb rightHand = ac.GetLimb(LimbType.RightHand);
item.body.AngularVelocity = rightHand.body.AngularVelocity;
@@ -538,7 +538,6 @@ namespace Barotrauma.Items.Components
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return 0.0f;
}
float average = skillSuccessSum / requiredSkills.Count;
float skillSuccessSum = 0.0f;
for (int i = 0; i < requiredSkills.Count; i++)
@@ -735,15 +734,13 @@ namespace Barotrauma.Items.Components
private void OverrideRequiredItems(XElement element)
{
var prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(requiredItems);
bool overrideRequiredItems = false;
requiredItems.Clear();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requireditem":
if (!overrideRequiredItems) requiredItems.Clear();
overrideRequiredItems = true;
RelatedItem newRequiredItem = RelatedItem.Load(subElement, item.Name);
if (newRequiredItem == null) continue;
@@ -90,7 +90,7 @@ namespace Barotrauma.Items.Components
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);
Vector2 currForce = new Vector2((force / 10.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 / item.MaxCondition);
@@ -9,138 +9,13 @@ using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
{
public class RequiredItem
{
public readonly ItemPrefab ItemPrefab;
public int Amount;
public readonly float MinCondition;
public readonly bool UseCondition;
public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, bool useCondition)
{
ItemPrefab = itemPrefab;
Amount = amount;
MinCondition = minCondition;
UseCondition = useCondition;
}
}
public readonly ItemPrefab TargetItem;
public readonly string DisplayName;
public readonly List<RequiredItem> RequiredItems;
private FabricationRecipe fabricatedItem;
private float timeUntilReady;
private float requiredTime;
private Character user;
private ItemContainer inputContainer, outputContainer;
public readonly List<Skill> RequiredSkills;
public FabricableItem(XElement element)
{
if (element.Attribute("name") != null)
{
string name = element.Attribute("name").Value;
DebugConsole.ThrowError("Error in fabricable item config (" + name + ") - use item identifiers instead of names");
TargetItem = MapEntityPrefab.Find(name) as ItemPrefab;
if (TargetItem == null)
{
DebugConsole.ThrowError("Error in fabricable item config - item prefab \"" + name + "\" not found.");
return;
}
}
else
{
string identifier = element.GetAttributeString("identifier", "");
TargetItem = MapEntityPrefab.Find(null, identifier) as ItemPrefab;
if (TargetItem == null)
{
DebugConsole.ThrowError("Error in fabricable item config - item prefab \"" + identifier + "\" not found.");
return;
}
}
string displayName = element.GetAttributeString("displayname", "");
DisplayName = string.IsNullOrEmpty(displayName) ? TargetItem.Name : TextManager.Get($"DisplayName.{displayName}");
RequiredSkills = new List<Skill>();
RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f);
OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
RequiredItems = new List<RequiredItem>();
foreach (XElement subElement in element.Elements())
{
if (!(me is ItemPrefab itemPrefab)) { continue; }
foreach (FabricationRecipe recipe in itemPrefab.FabricationRecipes)
{
case "requiredskill":
if (subElement.Attribute("name") != null)
{
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Use skill identifiers instead of names.");
continue;
}
RequiredSkills.Add(new Skill(
subElement.GetAttributeString("identifier", ""),
subElement.GetAttributeInt("level", 0)));
break;
case "item":
case "requireditem":
string requiredItemIdentifier = subElement.GetAttributeString("identifier", "");
if (string.IsNullOrWhiteSpace(requiredItemIdentifier))
{
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! One of the required items has no identifier.");
continue;
}
float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f);
//Substract mincondition from required item's condition or delete it regardless?
bool useCondition = subElement.GetAttributeBool("usecondition", true);
int count = subElement.GetAttributeInt("count", 1);
ItemPrefab requiredItem = MapEntityPrefab.Find(null, requiredItemIdentifier.Trim()) as ItemPrefab;
if (requiredItem == null)
{
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Required item \"" + requiredItemIdentifier + "\" not found.");
continue;
}
var existing = RequiredItems.Find(r => r.ItemPrefab == requiredItem);
if (existing == null)
{
RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition));
}
else
{
RequiredItems.Remove(existing);
RequiredItems.Add(new RequiredItem(requiredItem, existing.Amount + count, minCondition, useCondition));
}
break;
}
}
InitProjSpecific();
}
}
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
{
public const float SkillIncreaseMultiplier = 0.5f;
private List<FabricableItem> fabricableItems;
private List<FabricationRecipe> fabricationRecipes = new List<FabricationRecipe>();
private FabricableItem fabricatedItem;
private FabricationRecipe fabricatedItem;
private float timeUntilReady;
private float requiredTime;
@@ -152,6 +27,37 @@ namespace Barotrauma.Items.Components
public Fabricator(Item item, XElement element)
: base(item, element)
{
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() == "fabricableitem")
{
DebugConsole.ThrowError("Error in item " + item.Name + "! Fabrication recipes should be defined in the craftable item's xml, not in the fabricator.");
break;
}
}
foreach (MapEntityPrefab me in MapEntityPrefab.List)
{
if (!(me is ItemPrefab itemPrefab)) { continue; }
foreach (FabricationRecipe recipe in itemPrefab.FabricationRecipes)
{
if (recipe.SuitableFabricatorIdentifiers.Length > 0)
{
if (!recipe.SuitableFabricatorIdentifiers.Any(i => item.prefab.Identifier == i || item.HasTag(i)))
{
continue;
}
}
fabricationRecipes.Add(recipe);
}
}
InitProjSpecific();
}
public override void OnItemLoaded()
{
var containers = item.GetComponents<ItemContainer>().ToList();
if (containers.Count < 2)
@@ -175,30 +81,6 @@ namespace Barotrauma.Items.Components
OnItemLoadedProjSpecific();
}
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();
@@ -217,7 +99,7 @@ namespace Barotrauma.Items.Components
return (picker != null);
}
private void StartFabricating(FabricableItem selectedItem, Character user)
private void StartFabricating(FabricationRecipe selectedItem, Character user)
{
if (selectedItem == null) return;
if (!outputContainer.Inventory.IsEmpty()) return;
@@ -302,7 +184,7 @@ namespace Barotrauma.Items.Components
if (timeUntilReady > 0.0f) { return; }
var availableIngredients = GetAvailableIngredients();
foreach (FabricableItem.RequiredItem ingredient in fabricatedItem.RequiredItems)
foreach (FabricationRecipe.RequiredItem ingredient in fabricatedItem.RequiredItems)
{
for (int i = 0; i < ingredient.Amount; i++)
{
@@ -328,13 +210,8 @@ namespace Barotrauma.Items.Components
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
}
bool isNotClient = true;
#if CLIENT
isNotClient = GameMain.Client == null;
#endif
if (isNotClient && user != null)
if ((GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) && user != null && !user.Removed)
{
foreach (Skill skill in fabricatedItem.RequiredSkills)
{
@@ -345,17 +222,17 @@ namespace Barotrauma.Items.Components
CancelFabricating(null);
}
private bool CanBeFabricated(FabricableItem fabricableItem)
private bool CanBeFabricated(FabricationRecipe fabricableItem)
{
if (fabricableItem == null) { return false; }
List<Item> availableIngredients = GetAvailableIngredients();
return CanBeFabricated(fabricableItem, availableIngredients);
}
private bool CanBeFabricated(FabricableItem fabricableItem, IEnumerable<Item> availableIngredients)
private bool CanBeFabricated(FabricationRecipe fabricableItem, IEnumerable<Item> availableIngredients)
{
if (fabricableItem == null) { return false; }
foreach (FabricableItem.RequiredItem requiredItem in fabricableItem.RequiredItems)
foreach (FabricationRecipe.RequiredItem requiredItem in fabricableItem.RequiredItems)
{
if (availableIngredients.Count(it => IsItemValidIngredient(it, requiredItem)) < requiredItem.Amount)
{
@@ -365,7 +242,7 @@ namespace Barotrauma.Items.Components
return true;
}
private float GetRequiredTime(FabricableItem fabricableItem, Character user)
private float GetRequiredTime(FabricationRecipe fabricableItem, Character user)
{
float degreeOfSuccess = DegreeOfSuccess(user, fabricableItem.RequiredSkills);
@@ -407,7 +284,7 @@ namespace Barotrauma.Items.Components
/// 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)
private void MoveIngredientsToInputContainer(FabricationRecipe targetItem)
{
//required ingredients that are already present in the input container
List<Item> usedItems = new List<Item>();
@@ -438,7 +315,7 @@ namespace Barotrauma.Items.Components
}
}
private bool IsItemValidIngredient(Item item, FabricableItem.RequiredItem requiredItem)
private bool IsItemValidIngredient(Item item, FabricationRecipe.RequiredItem requiredItem)
{
return
item != null &&
@@ -377,23 +377,14 @@ namespace Barotrauma.Items.Components
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
return true;
}
else if (target.Body.UserData is Limb limb)
{
//severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive)
if (limb.IsSevered)
{
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
return true;
}
limb.character.LastDamageSource = item;
attackResult = attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f);
if (limb.character != null) character = limb.character;
}
else if (target.Body.UserData is Structure structure)
{
attackResult = attack.DoDamage(User, structure, item.WorldPosition, 1.0f);
}
limb.character.LastDamageSource = item;
if (attack != null) { attackResult = attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f); }
if (limb.character != null) { character = limb.character; }
}
else if (target.Body.UserData is Structure structure)
{
if (attack != null) { attackResult = attack.DoDamage(User, structure, item.WorldPosition, 1.0f); }
}
if (character != null) character.LastDamageSource = item;
@@ -22,6 +22,10 @@ namespace Barotrauma.Items.Components
private float blinkTimer;
private bool itemLoaded;
private float blinkTimer;
public PhysicsBody ParentBody;
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 2048.0f), Serialize(100.0f, true)]
@@ -77,7 +81,7 @@ namespace Barotrauma.Items.Components
IsActive = value;
#if SERVER
if (GameMain.Server != null && GameMain.Server.GameStarted) { item.CreateServerEvent(this); }
if (GameMain.Server != null && itemLoaded) { item.CreateServerEvent(this); }
#endif
}
}
@@ -315,7 +315,7 @@ namespace Barotrauma.Items.Components
Vector2 diff = nodes[nodes.Count - 1] - newNodePos;
Vector2 pullBackDir = diff == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(diff);
user.AnimController.Collider.ApplyForce(pullBackDir * user.Mass * 50.0f);
user.AnimController.Collider.ApplyForce(pullBackDir * user.Mass * 50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
user.AnimController.UpdateUseItem(true, user.WorldPosition + pullBackDir * 200.0f);
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
@@ -491,6 +491,9 @@ namespace Barotrauma
case "levelcommonness":
case "suitabletreatment":
case "containedsprite":
case "fabricate":
case "fabricable":
case "fabricableitem":
break;
case "staticbody":
StaticBodyConfig = subElement;
@@ -31,6 +31,100 @@ namespace Barotrauma
}
}
class FabricationRecipe
{
public class RequiredItem
{
public readonly ItemPrefab ItemPrefab;
public int Amount;
public readonly float MinCondition;
public readonly bool UseCondition;
public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, bool useCondition)
{
ItemPrefab = itemPrefab;
Amount = amount;
MinCondition = minCondition;
UseCondition = useCondition;
}
}
public readonly ItemPrefab TargetItem;
public readonly string DisplayName;
public readonly List<RequiredItem> RequiredItems;
public readonly string[] SuitableFabricatorIdentifiers;
public readonly float RequiredTime;
public readonly float OutCondition; //Percentage-based from 0 to 1
public readonly List<Skill> RequiredSkills;
public FabricationRecipe(XElement element, ItemPrefab itemPrefab)
{
TargetItem = itemPrefab;
string displayName = element.GetAttributeString("displayname", "");
DisplayName = string.IsNullOrEmpty(displayName) ? itemPrefab.Name : TextManager.Get($"DisplayName.{displayName}");
SuitableFabricatorIdentifiers = element.GetAttributeStringArray("suitablefabricators", new string[0]);
RequiredSkills = new List<Skill>();
RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f);
OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
RequiredItems = new List<RequiredItem>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requiredskill":
if (subElement.Attribute("name") != null)
{
DebugConsole.ThrowError("Error in fabricable item " + itemPrefab.Name + "! Use skill identifiers instead of names.");
continue;
}
RequiredSkills.Add(new Skill(
subElement.GetAttributeString("identifier", ""),
subElement.GetAttributeInt("level", 0)));
break;
case "item":
case "requireditem":
string requiredItemIdentifier = subElement.GetAttributeString("identifier", "");
if (string.IsNullOrWhiteSpace(requiredItemIdentifier))
{
DebugConsole.ThrowError("Error in fabricable item " + itemPrefab.Name + "! One of the required items has no identifier.");
continue;
}
float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f);
//Substract mincondition from required item's condition or delete it regardless?
bool useCondition = subElement.GetAttributeBool("usecondition", true);
int count = subElement.GetAttributeInt("count", 1);
ItemPrefab requiredItem = MapEntityPrefab.Find(null, requiredItemIdentifier.Trim()) as ItemPrefab;
if (requiredItem == null)
{
DebugConsole.ThrowError("Error in fabricable item " + itemPrefab.Name + "! Required item \"" + requiredItemIdentifier + "\" not found.");
continue;
}
var existing = RequiredItems.Find(r => r.ItemPrefab == requiredItem);
if (existing == null)
{
RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition));
}
else
{
RequiredItems.Remove(existing);
RequiredItems.Add(new RequiredItem(requiredItem, existing.Amount + count, minCondition, useCondition));
}
break;
}
}
}
}
partial class ItemPrefab : MapEntityPrefab
{
private readonly string configFile;
@@ -48,6 +142,8 @@ namespace Barotrauma
//the construction can be Activated() by a Character inside the area
public List<Rectangle> Triggers;
private List<XElement> fabricationRecipeElements = new List<XElement>();
public string ConfigFile
{
get { return configFile; }
@@ -315,6 +411,18 @@ namespace Barotrauma
new ItemPrefab(doc.Root, filePath);
}
}
//initialize item requirements for fabrication recipes
//(has to be done after all the item prefabs have been loaded, because the
//recipe ingredients may not have been loaded yet when loading the prefab)
foreach (MapEntityPrefab me in List)
{
if (!(me is ItemPrefab itemPrefab)) { continue; }
foreach (XElement fabricationRecipe in itemPrefab.fabricationRecipeElements)
{
itemPrefab.FabricationRecipes.Add(new FabricationRecipe(fabricationRecipe, itemPrefab));
}
}
}
public ItemPrefab(XElement element, string filePath)
@@ -568,7 +568,7 @@ namespace Barotrauma
public void Extinguish(float deltaTime, float amount, Vector2 position)
{
for (int i = FireSources.Count - 1; i >= 0; i-- )
for (int i = FireSources.Count - 1; i >= 0; i--)
{
FireSources[i].Extinguish(deltaTime, amount, position);
}
@@ -543,19 +543,19 @@ namespace Barotrauma
if (ForceVelocityLimit < 1000.0f)
body.ApplyForce(Force * currentForceFluctuation * distFactor, ForceVelocityLimit);
else
body.ApplyForce(Force * currentForceFluctuation * distFactor);
body.ApplyForce(Force * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
break;
case TriggerForceMode.Acceleration:
if (ForceVelocityLimit < 1000.0f)
body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor, ForceVelocityLimit);
else
body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor);
body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
break;
case TriggerForceMode.Impulse:
if (ForceVelocityLimit < 1000.0f)
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, ForceVelocityLimit);
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, maxVelocity: ForceVelocityLimit);
else
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor);
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
break;
case TriggerForceMode.LimitVelocity:
float maxVel = ForceVelocityLimit * currentForceFluctuation * distFactor;
@@ -563,7 +563,8 @@ namespace Barotrauma
{
body.ApplyForce(
Vector2.Normalize(-body.LinearVelocity) *
Force.Length() * body.Mass * currentForceFluctuation * distFactor);
Force.Length() * body.Mass * currentForceFluctuation * distFactor,
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
break;
}
@@ -74,5 +74,23 @@ namespace Barotrauma.Networking
if (lengthSqr > 1000.0f) { return Vector2.Zero; }
return cursorPositionError *= 0.7f;
}
public static Vector2 Quantize(Vector2 value, float min, float max, int numberOfBits)
{
return new Vector2(
Quantize(value.X, min, max, numberOfBits),
Quantize(value.Y, min, max, numberOfBits));
}
public static float Quantize(float value, float min, float max, int numberOfBits)
{
float step = (max - min) / (1 << (numberOfBits + 1));
if (Math.Abs(value) < step + 0.00001f)
{
return 0.0f;
}
return MathUtils.RoundTowardsClosest(MathHelper.Clamp(value, min, max), step);
}
}
}
@@ -605,7 +605,7 @@ namespace Barotrauma
{
if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return;
if (!IsValidValue(point, "point")) return;
if (!IsValidValue(impulse / body.Mass, "new velocity")) return;
if (!IsValidValue(impulse / body.Mass, "new velocity", -1000.0f, 1000.0f)) return;
body.ApplyLinearImpulse(impulse, point);
}
@@ -649,11 +649,14 @@ namespace Barotrauma
if (!IsValidValue(force, "force", -1e10f, 1e10f)) return;
if (!IsValidValue(maxVelocity, "max velocity")) return;
float currSpeed = body.LinearVelocity.Length();
Vector2 velocityAddition = force / Mass * (float)Timing.Step;
Vector2 newVelocity = body.LinearVelocity + velocityAddition;
newVelocity = newVelocity.ClampLength(Math.Max(currSpeed, maxVelocity));
float newSpeedSqr = newVelocity.LengthSquared();
if (newSpeedSqr > maxVelocity * maxVelocity)
{
newVelocity = newVelocity.ClampLength(maxVelocity);
}
body.ApplyForce((newVelocity - body.LinearVelocity) * Mass / (float)Timing.Step);
}