(f0d812055) v0.9.9.0
This commit is contained in:
@@ -58,6 +58,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "If set to true, this docking port is used when spawning the submarine docked to an outpost (if possible).")]
|
||||
public bool MainDockingPort
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public DockingPort DockingTarget { get; private set; }
|
||||
|
||||
public bool Docked
|
||||
@@ -234,12 +241,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
Vector2 jointDiff = joint.WorldAnchorB - joint.WorldAnchorA;
|
||||
if (item.Submarine.PhysicsBody.Mass < DockingTarget.item.Submarine.PhysicsBody.Mass ||
|
||||
DockingTarget.item.Submarine.IsOutpost)
|
||||
DockingTarget.item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
item.Submarine.SubBody.SetPosition(item.Submarine.SubBody.Position + ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
else if (DockingTarget.item.Submarine.PhysicsBody.Mass < item.Submarine.PhysicsBody.Mass ||
|
||||
item.Submarine.IsOutpost)
|
||||
item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
DockingTarget.item.Submarine.SubBody.SetPosition(DockingTarget.item.Submarine.SubBody.Position - ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
@@ -873,12 +880,12 @@ namespace Barotrauma.Items.Components
|
||||
float closestDist = 30.0f * 30.0f;
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != item.Submarine) continue;
|
||||
if (it.Submarine != item.Submarine) { continue; }
|
||||
|
||||
var doorComponent = it.GetComponent<Door>();
|
||||
if (doorComponent == null) continue;
|
||||
if (doorComponent == null || doorComponent.IsHorizontal == IsHorizontal) { continue; }
|
||||
|
||||
float distSqr = Vector2.Distance(item.Position, it.Position);
|
||||
float distSqr = Vector2.DistanceSquared(item.Position, it.Position);
|
||||
if (distSqr < closestDist)
|
||||
{
|
||||
door = doorComponent;
|
||||
@@ -951,12 +958,12 @@ namespace Barotrauma.Items.Components
|
||||
if (docked)
|
||||
{
|
||||
if (item.Submarine != null && DockingTarget?.item?.Submarine != null)
|
||||
GameServer.Log(sender.LogName + " docked " + item.Submarine.Name + " to " + DockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(sender) + " docked " + item.Submarine.Info.Name + " to " + DockingTarget.item.Submarine.Info.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.Submarine != null && prevDockingTarget?.item?.Submarine != null)
|
||||
GameServer.Log(sender.LogName + " undocked " + item.Submarine.Name + " from " + prevDockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(sender) + " undocked " + item.Submarine.Info.Name + " from " + prevDockingTarget.item.Submarine.Info.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -594,7 +594,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (sender != null && wasOpen != isOpen)
|
||||
{
|
||||
GameServer.Log(sender.LogName + (isOpen ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(sender) + (isOpen ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
|
||||
#if SERVER
|
||||
if (!alreadyEquipped) GameServer.Log(character.LogName + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
if (!alreadyEquipped) GameServer.Log(GameServer.CharacterLogName(character) + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -355,7 +355,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
picker.DeselectItem(item);
|
||||
#if SERVER
|
||||
GameServer.Log(character.LogName + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(character) + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
|
||||
item.body.PhysEnabled = true;
|
||||
@@ -419,7 +419,7 @@ namespace Barotrauma.Items.Components
|
||||
item.CreateServerEvent(this);
|
||||
if (picker != null)
|
||||
{
|
||||
GameServer.Log(picker.LogName + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(picker) + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -54,7 +55,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "attack") { continue; }
|
||||
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
attack = new Attack(subElement, item.Name + ", MeleeWeapon");
|
||||
}
|
||||
item.IsShootable = true;
|
||||
@@ -310,70 +311,7 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attack != null)
|
||||
{
|
||||
if (targetLimb == null && targetCharacter == null && targetStructure == null && (targetItem == null || ! targetItem.Prefab.DamagedByMeleeWeapons))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetLimb != null)
|
||||
{
|
||||
targetLimb.character.LastDamageSource = item;
|
||||
attack.DoDamageToLimb(User, targetLimb, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
targetCharacter.LastDamageSource = item;
|
||||
attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetStructure != null)
|
||||
{
|
||||
attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons)
|
||||
{
|
||||
attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return true; }
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
|
||||
{
|
||||
|
||||
GameMain.Server.CreateEntityEvent(item, new object[]
|
||||
{
|
||||
Networking.NetEntityEvent.Type.ApplyStatusEffect,
|
||||
ActionType.OnUse,
|
||||
null, //itemcomponent
|
||||
targetCharacter.ID, targetLimb
|
||||
});
|
||||
|
||||
string logStr = picker?.LogName + " used " + item.Name;
|
||||
if (item.ContainedItems != null && item.ContainedItems.Any())
|
||||
{
|
||||
logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
|
||||
}
|
||||
logStr += " on " + targetCharacter.LogName + ".";
|
||||
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: User);
|
||||
}
|
||||
|
||||
if (DeleteOnUse)
|
||||
{
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
}
|
||||
impactQueue.Enqueue(f2);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -414,7 +352,7 @@ namespace Barotrauma.Items.Components
|
||||
if (targetStructure.Removed) { return; }
|
||||
attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons)
|
||||
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
|
||||
{
|
||||
if (targetItem.Removed) { return; }
|
||||
attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
|
||||
|
||||
+58
-44
@@ -29,6 +29,13 @@ namespace Barotrauma.Items.Components
|
||||
set { reload = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Serialize(1, false, description: "How projectiles the weapon launches when fired once.")]
|
||||
public int ProjectileCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "Random spread applied to the firing angle of the projectiles when used by a character with sufficient skills to use the weapon (in degrees).")]
|
||||
public float Spread
|
||||
{
|
||||
@@ -110,55 +117,62 @@ namespace Barotrauma.Items.Components
|
||||
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
}
|
||||
|
||||
Projectile projectile = FindProjectile(triggerOnUseOnContainers: true);
|
||||
if (projectile == null) { return true; }
|
||||
|
||||
float spread = GetSpread(character);
|
||||
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
|
||||
rotation += spread * Rand.Range(-0.5f, 0.5f);
|
||||
|
||||
projectile.User = character;
|
||||
//add the limbs of the shooter to the list of bodies to be ignored
|
||||
//so that the player can't shoot himself
|
||||
projectile.IgnoredBodies = new List<Body>(limbBodies);
|
||||
|
||||
Vector2 projectilePos = item.SimPosition;
|
||||
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
|
||||
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
|
||||
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
|
||||
if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
|
||||
for (int i = 0; i < ProjectileCount; i++)
|
||||
{
|
||||
//no obstacles -> we can spawn the projectile at the barrel
|
||||
projectilePos = barrelPos;
|
||||
Projectile projectile = FindProjectile(triggerOnUseOnContainers: true);
|
||||
if (projectile == null) { return true; }
|
||||
|
||||
float spread = GetSpread(character);
|
||||
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
|
||||
rotation += spread * Rand.Range(-0.5f, 0.5f);
|
||||
|
||||
projectile.User = character;
|
||||
//add the limbs of the shooter to the list of bodies to be ignored
|
||||
//so that the player can't shoot himself
|
||||
projectile.IgnoredBodies = new List<Body>(limbBodies);
|
||||
|
||||
Vector2 projectilePos = item.SimPosition;
|
||||
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
|
||||
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
|
||||
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
|
||||
if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
|
||||
{
|
||||
//no obstacles -> we can spawn the projectile at the barrel
|
||||
projectilePos = barrelPos;
|
||||
}
|
||||
else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f)
|
||||
{
|
||||
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
|
||||
projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f);
|
||||
}
|
||||
|
||||
projectile.Item.body.ResetDynamics();
|
||||
projectile.Item.SetTransform(projectilePos, rotation);
|
||||
|
||||
projectile.Use(deltaTime);
|
||||
projectile.Item.GetComponent<Rope>()?.Attach(item, projectile.Item);
|
||||
if (projectile.Item.Removed) { continue; }
|
||||
projectile.User = character;
|
||||
|
||||
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
|
||||
|
||||
//set the rotation of the projectile again because dropping the projectile resets the rotation
|
||||
projectile.Item.SetTransform(projectilePos,
|
||||
rotation + (projectile.Item.body.Dir * projectile.LaunchRotationRadians));
|
||||
|
||||
item.RemoveContained(projectile.Item);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
//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,
|
||||
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
}
|
||||
else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f)
|
||||
{
|
||||
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
|
||||
projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f);
|
||||
}
|
||||
|
||||
projectile.Item.body.ResetDynamics();
|
||||
projectile.Item.SetTransform(projectilePos, rotation);
|
||||
|
||||
projectile.Use(deltaTime);
|
||||
if (projectile.Item.Removed) { return true; }
|
||||
projectile.User = character;
|
||||
|
||||
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
|
||||
|
||||
//set the rotation of the projectile again because dropping the projectile resets the rotation
|
||||
projectile.Item.SetTransform(projectilePos,
|
||||
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,
|
||||
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
|
||||
LaunchProjSpecific();
|
||||
|
||||
item.RemoveContained(projectile.Item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -440,17 +440,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (targetBody.UserData is Item targetItem)
|
||||
{
|
||||
targetItem.IsHighlighted = true;
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
|
||||
|
||||
if (targetItem.body != null && !MathUtils.NearlyEqual(TargetForce, 0.0f))
|
||||
{
|
||||
Vector2 dir = targetItem.WorldPosition - item.WorldPosition;
|
||||
dir = dir.LengthSquared() < 0.0001f ? Vector2.UnitY : Vector2.Normalize(dir);
|
||||
targetItem.body.ApplyForce(dir * TargetForce, maxVelocity: 10.0f);
|
||||
}
|
||||
|
||||
var levelResource = targetItem.GetComponent<LevelResource>();
|
||||
if (levelResource != null && levelResource.Attached &&
|
||||
levelResource.requiredItems.Any() &&
|
||||
@@ -464,7 +454,22 @@ namespace Barotrauma.Items.Components
|
||||
levelResource.DeattachTimer / levelResource.DeattachDuration,
|
||||
GUI.Style.Red, GUI.Style.Green);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!targetItem.Prefab.DamagedByRepairTools) { return false; }
|
||||
|
||||
targetItem.IsHighlighted = true;
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
|
||||
|
||||
if (targetItem.body != null && !MathUtils.NearlyEqual(TargetForce, 0.0f))
|
||||
{
|
||||
Vector2 dir = targetItem.WorldPosition - item.WorldPosition;
|
||||
dir = dir.LengthSquared() < 0.0001f ? Vector2.UnitY : Vector2.Normalize(dir);
|
||||
targetItem.body.ApplyForce(dir * TargetForce, maxVelocity: 10.0f);
|
||||
}
|
||||
|
||||
FixItemProjSpecific(user, deltaTime, targetItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!picker.IsKeyDown(InputType.Aim) && !throwing) { throwPos = 0.0f; }
|
||||
bool aim = picker.IsKeyDown(InputType.Aim) && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent<Ladder>() != null);
|
||||
|
||||
if (picker.IsUnconscious || picker.IsDead || !picker.AllowInput)
|
||||
if (picker.IsDead || !picker.AllowInput)
|
||||
{
|
||||
throwing = false;
|
||||
aim = false;
|
||||
@@ -115,7 +115,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!MathUtils.IsValid(throwVector)) { throwVector = Vector2.UnitY; }
|
||||
|
||||
#if SERVER
|
||||
GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(picker) + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
Character thrower = picker;
|
||||
item.Drop(thrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
|
||||
|
||||
@@ -68,7 +68,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected const float CorrectionDelay = 1.0f;
|
||||
protected CoroutineHandle delayedCorrectionCoroutine;
|
||||
protected float correctionTimer;
|
||||
|
||||
[Editable, Serialize(0.0f, false, description: "How long it takes to pick up the item (in seconds).")]
|
||||
public float PickingTime
|
||||
@@ -81,7 +80,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public Action<bool> OnActiveStateChanged;
|
||||
|
||||
public float IsActiveTimer;
|
||||
public virtual bool IsActive
|
||||
{
|
||||
get { return isActive; }
|
||||
@@ -222,7 +220,6 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).
|
||||
/// </summary>
|
||||
@@ -632,7 +629,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only checks the id card(s). Much simpler and a bit different than HasRequiredItems.
|
||||
/// Only checks if any of the Picked requirements are matched (used for checking id card(s)). Much simpler and a bit different than HasRequiredItems.
|
||||
/// </summary>
|
||||
public bool HasAccess(Character character)
|
||||
{
|
||||
@@ -641,7 +638,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (Item item in character.Inventory.Items)
|
||||
{
|
||||
if (item?.Prefab.Identifier == "idcard" && requiredItems.Any(ri => ri.Value.Any(r => r.MatchesItem(item))))
|
||||
if (requiredItems.Any(ri => ri.Value.Any(r => r.Type == RelatedItem.RelationType.Picked && r.MatchesItem(item))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -741,14 +738,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (XAttribute attribute in componentElement.Attributes())
|
||||
{
|
||||
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
|
||||
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) { continue; }
|
||||
property.TrySetValue(this, attribute.Value);
|
||||
}
|
||||
ParseMsg();
|
||||
OverrideRequiredItems(componentElement);
|
||||
}
|
||||
|
||||
if (item.Submarine != null) { SerializableProperty.UpgradeGameVersion(this, originalElement, item.Submarine.GameVersion); }
|
||||
if (item.Submarine != null) { SerializableProperty.UpgradeGameVersion(this, originalElement, item.Submarine.Info.GameVersion); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -52,6 +52,9 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool AccessOnlyWhenBroken { get; set; }
|
||||
|
||||
[Serialize(5, false, description: "How many inventory slots the inventory has per row.")]
|
||||
public int SlotsPerRow { get; set; }
|
||||
|
||||
@@ -197,10 +200,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasRequiredItems(Character character, bool addMessage, string msg = null)
|
||||
{
|
||||
return (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
|
||||
}
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
if (item.Container != null) { return false; }
|
||||
|
||||
if (AccessOnlyWhenBroken)
|
||||
{
|
||||
if (item.Condition > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (AutoInteractWithContained && character.SelectedConstruction == null)
|
||||
{
|
||||
foreach (Item contained in Inventory.Items)
|
||||
@@ -218,6 +232,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (AccessOnlyWhenBroken)
|
||||
{
|
||||
if (item.Condition > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (AutoInteractWithContained)
|
||||
{
|
||||
foreach (Item contained in Inventory.Items)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -209,7 +210,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool SecondaryUse(float deltaTime, Character character = null)
|
||||
{
|
||||
if (this.user != character)
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -55,6 +55,15 @@ namespace Barotrauma.Items.Components
|
||||
get { return Math.Abs((force / 100.0f) * (MinVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage / MinVoltage, 1.0f))); }
|
||||
}
|
||||
|
||||
public float CurrentBrokenVolume
|
||||
{
|
||||
get
|
||||
{
|
||||
if (item.ConditionPercentage > 10.0f) { return 0.0f; }
|
||||
return Math.Abs(targetForce / 100.0f) * (1.0f - item.ConditionPercentage / 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public Engine(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() == "fabricableitem")
|
||||
if (subElement.Name.ToString().Equals("fabricableitem", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in item " + item.Name + "! Fabrication recipes should be defined in the craftable item's xml, not in the fabricator.");
|
||||
break;
|
||||
@@ -180,7 +180,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -216,7 +216,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -64,11 +64,6 @@ namespace Barotrauma.Items.Components
|
||||
float hullPercentage = 0.0f;
|
||||
if (item.CurrentHull != null) { hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f; }
|
||||
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
|
||||
|
||||
if (pumpSpeedLockTimer <= 0.0f)
|
||||
{
|
||||
targetLevel = null;
|
||||
}
|
||||
}
|
||||
|
||||
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
|
||||
@@ -112,6 +107,7 @@ namespace Barotrauma.Items.Components
|
||||
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
|
||||
{
|
||||
flowPercentage = MathHelper.Clamp(tempSpeed, -100.0f, 100.0f);
|
||||
targetLevel = null;
|
||||
pumpSpeedLockTimer = 0.1f;
|
||||
}
|
||||
}
|
||||
@@ -131,7 +127,7 @@ namespace Barotrauma.Items.Components
|
||||
if (GameMain.Client != null) { return false; }
|
||||
#endif
|
||||
|
||||
if (objective.Option.ToLowerInvariant() == "stoppumping")
|
||||
if (objective.Option.Equals("stoppumping", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
#if SERVER
|
||||
if (FlowPercentage > 0.0f)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -189,7 +190,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (Timing.TotalTime >= (float)nextServerLogWriteTime)
|
||||
{
|
||||
GameServer.Log(lastUser.LogName + " adjusted reactor settings: " +
|
||||
GameServer.Log(GameServer.CharacterLogName(lastUser) + " adjusted reactor settings: " +
|
||||
"Temperature: " + (int)(temperature * 100.0f) +
|
||||
", Fission rate: " + (int)targetFissionRate +
|
||||
", Turbine output: " + (int)targetTurbineOutput +
|
||||
@@ -651,6 +652,20 @@ namespace Barotrauma.Items.Components
|
||||
unsentChanges = true;
|
||||
}
|
||||
break;
|
||||
case "set_fissionrate":
|
||||
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
|
||||
{
|
||||
FissionRate = newFissionRate;
|
||||
unsentChanges = true;
|
||||
}
|
||||
break;
|
||||
case "set_turbineoutput":
|
||||
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
|
||||
{
|
||||
TurbineOutput = newTurbineOutput;
|
||||
unsentChanges = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
#region Docking
|
||||
public List<DockingPort> DockingSources = new List<DockingPort>();
|
||||
public DockingPort ActiveDockingSource, DockingTarget;
|
||||
private bool searchedConnectedDockingPort;
|
||||
|
||||
private bool dockingModeEnabled;
|
||||
@@ -200,7 +199,7 @@ namespace Barotrauma.Items.Components
|
||||
if (dockingConnection != null)
|
||||
{
|
||||
var connectedPorts = item.GetConnectedComponentsRecursive<DockingPort>(dockingConnection);
|
||||
DockingSources.AddRange(connectedPorts.Where(p => p.Item.Submarine != null && !p.Item.Submarine.IsOutpost));
|
||||
DockingSources.AddRange(connectedPorts.Where(p => p.Item.Submarine != null && !p.Item.Submarine.Info.IsOutpost));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -344,6 +343,7 @@ namespace Barotrauma.Items.Components
|
||||
autopilotRecalculatePathTimer = RecalculatePathInterval;
|
||||
}
|
||||
|
||||
if (steeringPath == null) { return; }
|
||||
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(controlledSub.WorldPosition), 10.0f);
|
||||
|
||||
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
|
||||
@@ -475,6 +475,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void UpdatePath()
|
||||
{
|
||||
if (Level.Loaded == null) { return; }
|
||||
|
||||
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||
|
||||
Vector2 target;
|
||||
@@ -536,7 +538,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool aiDockingToggled;
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (objective.Override)
|
||||
@@ -572,7 +573,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
break;
|
||||
case "navigateback":
|
||||
if (!aiDockingToggled && DockingSources.Any(d => d.Docked))
|
||||
if (DockingSources.Any(d => d.Docked))
|
||||
{
|
||||
item.SendSignal(0, "1", "toggle_docking", sender: null);
|
||||
}
|
||||
@@ -586,7 +587,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
break;
|
||||
case "navigatetodestination":
|
||||
if (!aiDockingToggled && DockingSources.Any(d => d.Docked))
|
||||
if (DockingSources.Any(d => d.Docked))
|
||||
{
|
||||
item.SendSignal(0, "1", "toggle_docking", sender: null);
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (HasBeenTuned) { return true; }
|
||||
|
||||
if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge")
|
||||
if (string.IsNullOrEmpty(objective.Option) || objective.Option.Equals("charge", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * aiRechargeTargetRatio) > 0.05f)
|
||||
{
|
||||
|
||||
@@ -140,6 +140,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
currPowerConsumption = powerConsumption;
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Projectile : ItemComponent
|
||||
partial class Projectile : ItemComponent, IServerSerializable
|
||||
{
|
||||
struct HitscanResult
|
||||
{
|
||||
@@ -32,11 +32,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public Fixture Fixture;
|
||||
public Vector2 Normal;
|
||||
public Vector2 LinearVelocity;
|
||||
|
||||
public Impact(Fixture fixture, Vector2 normal)
|
||||
public Impact(Fixture fixture, Vector2 normal, Vector2 velocity)
|
||||
{
|
||||
Fixture = fixture;
|
||||
Normal = normal;
|
||||
LinearVelocity = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,16 +49,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//a duration during which the projectile won't drop from the body it's stuck to
|
||||
private const float PersistentStickJointDuration = 1.0f;
|
||||
|
||||
private float launchImpulse;
|
||||
|
||||
private PrismaticJoint stickJoint;
|
||||
private Body stickTarget;
|
||||
|
||||
private readonly Attack attack;
|
||||
|
||||
private Vector2 launchPos;
|
||||
|
||||
private readonly HashSet<Body> hits = new HashSet<Body>();
|
||||
|
||||
public List<Body> IgnoredBodies;
|
||||
|
||||
private Character user;
|
||||
@@ -70,14 +70,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Body> Hits
|
||||
{
|
||||
get { return hits; }
|
||||
}
|
||||
|
||||
private float persistentStickJointTimer;
|
||||
|
||||
[Serialize(10.0f, false, description: "The impulse applied to the physics body of the item when it's launched. Higher values make the projectile faster.")]
|
||||
public float LaunchImpulse
|
||||
{
|
||||
get { return launchImpulse; }
|
||||
set { launchImpulse = value; }
|
||||
}
|
||||
public float LaunchImpulse { get; set; }
|
||||
|
||||
[Serialize(0.0f, false, description: "The rotation of the item relative to the rotation of the weapon when launched (in degrees).")]
|
||||
public float LaunchRotation
|
||||
@@ -100,6 +101,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "When set to true, the item won't fall of a target it's stuck to unless removed.")]
|
||||
public bool StickPermanently
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Can the item stick to the character it hits.")]
|
||||
public bool StickToCharacters
|
||||
{
|
||||
@@ -138,6 +146,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1, false, description: "How many targets the projectile can hit before it stops.")]
|
||||
public int MaxTargetsToHit
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Should the item be deleted when it hits something.")]
|
||||
public bool RemoveOnHit
|
||||
{
|
||||
@@ -152,6 +167,17 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
public Body StickTarget
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IsStuckToTarget
|
||||
{
|
||||
get { return StickTarget != null; }
|
||||
}
|
||||
|
||||
public Projectile(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
@@ -159,7 +185,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
|
||||
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
attack = new Attack(subElement, item.Name + ", Projectile");
|
||||
}
|
||||
}
|
||||
@@ -203,7 +229,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
Launch(launchDir * launchImpulse * item.body.Mass);
|
||||
Launch(launchDir * LaunchImpulse * item.body.Mass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +240,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void Launch(Vector2 impulse)
|
||||
{
|
||||
hits.Clear();
|
||||
MaxTargetsToHit = 2;
|
||||
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
item.AiTarget.SightRange = item.AiTarget.MaxSightRange;
|
||||
@@ -237,7 +266,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
|
||||
stickTarget = null;
|
||||
StickTarget = null;
|
||||
GameMain.World.Remove(stickJoint);
|
||||
stickJoint = null;
|
||||
}
|
||||
@@ -265,6 +294,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//shooting indoors, do a hitscan outside as well
|
||||
hits.AddRange(DoRayCast(rayStart + item.Submarine.SimPosition, rayEnd + item.Submarine.SimPosition));
|
||||
//also in the coordinate space of docked subs
|
||||
foreach (Submarine dockedSub in item.Submarine.DockedTo)
|
||||
{
|
||||
if (dockedSub == item.Submarine) { continue; }
|
||||
hits.AddRange(DoRayCast(rayStart + item.Submarine.SimPosition - dockedSub.SimPosition, rayEnd + item.Submarine.SimPosition - dockedSub.SimPosition));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -291,7 +326,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (HitscanResult h in hits)
|
||||
{
|
||||
item.body.SetTransform(h.Point, rotation);
|
||||
if (HandleProjectileCollision(h.Fixture, h.Normal))
|
||||
if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero))
|
||||
{
|
||||
hitSomething = true;
|
||||
break;
|
||||
@@ -325,7 +360,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//ignore sensors and items
|
||||
if (fixture?.Body == null || fixture.IsSensor) { return true; }
|
||||
if (fixture.Body.UserData is Item) { return true; }
|
||||
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; }
|
||||
if (fixture.Body?.UserData as string == "ruinroom") { return true; }
|
||||
|
||||
//ignore everything else than characters, sub walls and level walls
|
||||
@@ -345,7 +380,7 @@ namespace Barotrauma.Items.Components
|
||||
//ignore sensors and items
|
||||
if (fixture?.Body == null || fixture.IsSensor) { return -1; }
|
||||
|
||||
if (fixture.Body.UserData is Item item && item.GetComponent<Door>() == null) { return -1; }
|
||||
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; }
|
||||
if (fixture.Body?.UserData as string == "ruinroom") { return -1; }
|
||||
|
||||
//ignore everything else than characters, sub walls and level walls
|
||||
@@ -368,7 +403,7 @@ namespace Barotrauma.Items.Components
|
||||
while (impactQueue.Count > 0)
|
||||
{
|
||||
var impact = impactQueue.Dequeue();
|
||||
HandleProjectileCollision(impact.Fixture, impact.Normal);
|
||||
HandleProjectileCollision(impact.Fixture, impact.Normal, impact.LinearVelocity);
|
||||
}
|
||||
|
||||
if (item.body != null && item.body.FarseerBody.IsBullet)
|
||||
@@ -383,25 +418,31 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
|
||||
if (persistentStickJointTimer > 0.0f)
|
||||
if (persistentStickJointTimer > 0.0f && !StickPermanently)
|
||||
{
|
||||
persistentStickJointTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
stickTarget = null;
|
||||
if (stickJoint != null)
|
||||
if (StickTargetRemoved() ||
|
||||
(!StickPermanently && (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)))
|
||||
{
|
||||
if (GameMain.World.JointList.Contains(stickJoint))
|
||||
{
|
||||
GameMain.World.Remove(stickJoint);
|
||||
}
|
||||
stickJoint = null;
|
||||
Unstick();
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
if (!item.body.FarseerBody.IsBullet) { IsActive = false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool StickTargetRemoved()
|
||||
{
|
||||
if (StickTarget == null) { return true; }
|
||||
if (StickTarget.UserData is Limb limb) { return limb.character.Removed; }
|
||||
if (StickTarget.UserData is Entity entity) { return entity.Removed; }
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -409,6 +450,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (User != null && User.Removed) { User = null; return false; }
|
||||
if (IgnoredBodies.Contains(target.Body)) { return false; }
|
||||
//ignore character colliders (the projectile only hits limbs)
|
||||
if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (hits.Contains(target.Body)) { return false; }
|
||||
if (target.Body.UserData is Submarine sub)
|
||||
{
|
||||
Vector2 dir = item.body.LinearVelocity.LengthSquared() < 0.001f ?
|
||||
@@ -424,6 +471,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0)
|
||||
{
|
||||
target = wallBody.FixtureList.First();
|
||||
if (hits.Contains(target.Body)) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -446,18 +494,23 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
impactQueue.Enqueue(new Impact(target, contact.Manifold.LocalNormal));
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
|
||||
return true;
|
||||
hits.Add(target.Body);
|
||||
impactQueue.Enqueue(new Impact(target, contact.Manifold.LocalNormal, item.body.LinearVelocity));
|
||||
if (hits.Count() >= MaxTargetsToHit)
|
||||
{
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal)
|
||||
private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal, Vector2 velocity)
|
||||
{
|
||||
if (User != null && User.Removed) { User = null; }
|
||||
|
||||
if (IgnoredBodies.Contains(target.Body)) { return false; }
|
||||
|
||||
//ignore character colliders (the projectile only hits limbs)
|
||||
if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
|
||||
{
|
||||
@@ -478,7 +531,6 @@ namespace Barotrauma.Items.Components
|
||||
//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;
|
||||
}
|
||||
|
||||
@@ -488,7 +540,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (target.Body.UserData is Item targetItem)
|
||||
{
|
||||
if (attack != null && targetItem.Prefab.DamagedByProjectiles)
|
||||
if (attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0)
|
||||
{
|
||||
attackResult = attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
|
||||
}
|
||||
@@ -558,21 +610,30 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
target.Body.ApplyLinearImpulse(velocity * item.body.Mass);
|
||||
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
|
||||
IgnoredBodies.Clear();
|
||||
|
||||
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
|
||||
if (hits.Count() >= MaxTargetsToHit)
|
||||
{
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0)
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionCharacter;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
}
|
||||
IgnoredBodies.Clear();
|
||||
}
|
||||
|
||||
if (attackResult.AppliedDamageModifiers != null &&
|
||||
attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles))
|
||||
{
|
||||
item.body.LinearVelocity *= 0.1f;
|
||||
}
|
||||
else if (Vector2.Dot(item.body.LinearVelocity, collisionNormal) < 0.0f &&
|
||||
else if (Vector2.Dot(velocity, collisionNormal) < 0.0f && hits.Count() >= MaxTargetsToHit &&
|
||||
(DoesStick ||
|
||||
(StickToCharacters && target.Body.UserData is Limb) ||
|
||||
(StickToStructures && target.Body.UserData is Structure) ||
|
||||
@@ -581,8 +642,24 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 dir = new Vector2(
|
||||
(float)Math.Cos(item.body.Rotation),
|
||||
(float)Math.Sin(item.body.Rotation));
|
||||
|
||||
StickToTarget(target.Body, dir);
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
if (target.Body.UserData is Structure structure && structure.Submarine != item.Submarine && structure.Submarine != null)
|
||||
{
|
||||
StickToTarget(structure.Submarine.PhysicsBody.FarseerBody, dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
StickToTarget(target.Body, dir);
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
item.body.LinearVelocity *= 0.5f;
|
||||
|
||||
return Hitscan;
|
||||
@@ -614,7 +691,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void StickToTarget(Body targetBody, Vector2 axis)
|
||||
{
|
||||
if (stickJoint != null) return;
|
||||
if (stickJoint != null) { return; }
|
||||
|
||||
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, true)
|
||||
{
|
||||
@@ -622,19 +699,39 @@ namespace Barotrauma.Items.Components
|
||||
MaxMotorForce = 30.0f,
|
||||
LimitEnabled = true
|
||||
};
|
||||
if (item.Sprite != null)
|
||||
|
||||
if (StickPermanently)
|
||||
{
|
||||
stickJoint.LowerLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * -0.3f);
|
||||
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * 0.3f);
|
||||
stickJoint.LowerLimit = stickJoint.UpperLimit = 0.0f;
|
||||
item.body.ResetDynamics();
|
||||
}
|
||||
else if (item.Sprite != null)
|
||||
{
|
||||
stickJoint.LowerLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * -0.3f * item.Scale);
|
||||
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * 0.3f * item.Scale);
|
||||
}
|
||||
|
||||
persistentStickJointTimer = PersistentStickJointDuration;
|
||||
stickTarget = targetBody;
|
||||
StickTarget = targetBody;
|
||||
GameMain.World.Add(stickJoint);
|
||||
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
private void Unstick()
|
||||
{
|
||||
StickTarget = null;
|
||||
if (stickJoint != null)
|
||||
{
|
||||
if (GameMain.World.JointList.Contains(stickJoint))
|
||||
{
|
||||
GameMain.World.Remove(stickJoint);
|
||||
}
|
||||
stickJoint = null;
|
||||
}
|
||||
if (!item.body.FarseerBody.IsBullet) { IsActive = false; }
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
if (stickJoint != null)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
@@ -111,7 +112,7 @@ namespace Barotrauma.Items.Components
|
||||
element.GetAttributeString("name", "");
|
||||
|
||||
//backwards compatibility
|
||||
var showRepairUIAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().ToLowerInvariant() == "showrepairuithreshold");
|
||||
var showRepairUIAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("showrepairuithreshold", StringComparison.OrdinalIgnoreCase));
|
||||
if (showRepairUIAttribute != null)
|
||||
{
|
||||
float repairThreshold;
|
||||
@@ -130,7 +131,26 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check if the character manages to succesfully repair the item
|
||||
/// </summary>
|
||||
public bool CheckCharacterSuccess(Character character)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
|
||||
if (statusEffectLists == null || statusEffectLists.None(s => s.Key == ActionType.OnFailure)) { return true; }
|
||||
|
||||
// unpowered (electrical) items can be repaired without a risk of electrical shock
|
||||
if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)) &&
|
||||
item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f) { return true; }
|
||||
|
||||
if (Rand.Range(0.0f, 0.5f) < DegreeOfSuccess(character)) { return true; }
|
||||
|
||||
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool StartRepairing(Character character, FixActions action)
|
||||
{
|
||||
if (character == null || character.IsDead || action == FixActions.None)
|
||||
@@ -143,8 +163,15 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (CurrentFixer != character || currentFixerAction != action)
|
||||
{
|
||||
if (!CheckCharacterSuccess(character))
|
||||
{
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID });
|
||||
return false;
|
||||
}
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#else
|
||||
if (GameMain.Client == null && (CurrentFixer != character || currentFixerAction != action) && !CheckCharacterSuccess(character)) { return false; }
|
||||
#endif
|
||||
CurrentFixer = character;
|
||||
CurrentFixerAction = action;
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Rope : ItemComponent, IServerSerializable
|
||||
{
|
||||
private Item source, target;
|
||||
|
||||
private float snapTimer;
|
||||
private const float SnapAnimDuration = 1.0f;
|
||||
|
||||
private float raycastTimer;
|
||||
private const float RayCastInterval = 0.2f;
|
||||
|
||||
[Serialize(0.0f, false, description: "How much force is applied to pull the projectile the rope is attached to.")]
|
||||
public float ProjectilePullForce
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How much force is applied to pull the target the rope is attached to.")]
|
||||
public float TargetPullForce
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How much force is applied to pull the source the rope is attached to.")]
|
||||
public float SourcePullForce
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1000.0f, false, description: "How far the source item can be from the projectile until the rope breaks.")]
|
||||
public float MaxLength
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, false, description: "Should the rope snap when it collides with a structure/submarine (if not, it will just go through it).")]
|
||||
public bool SnapOnCollision
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool snapped;
|
||||
public bool Snapped
|
||||
{
|
||||
get { return snapped; }
|
||||
set
|
||||
{
|
||||
if (snapped == value) { return; }
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
if (GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
snapped = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Rope(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
|
||||
public void Attach(Item source, Item target)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(source != null);
|
||||
System.Diagnostics.Debug.Assert(target != null);
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, worldPosition: item.WorldPosition);
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (source == null || source.Removed || target == null || target.Removed)
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Snapped)
|
||||
{
|
||||
snapTimer += deltaTime;
|
||||
if (snapTimer >= SnapAnimDuration)
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 diff = target.WorldPosition - source.WorldPosition;
|
||||
if (diff.LengthSquared() > MaxLength * MaxLength)
|
||||
{
|
||||
Snapped = true;
|
||||
return;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
|
||||
if (SnapOnCollision)
|
||||
{
|
||||
raycastTimer += deltaTime;
|
||||
if (raycastTimer > RayCastInterval)
|
||||
{
|
||||
if (Submarine.PickBody(ConvertUnits.ToSimUnits(source.WorldPosition), ConvertUnits.ToSimUnits(target.WorldPosition),
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall,
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
var projectile = target?.GetComponent<Projectile>();
|
||||
if (projectile != null)
|
||||
{
|
||||
foreach (Body body in projectile.Hits)
|
||||
{
|
||||
Submarine alreadyHitSub = null;
|
||||
if (body.UserData is Structure hitStructure)
|
||||
{
|
||||
alreadyHitSub = hitStructure.Submarine;
|
||||
}
|
||||
else if (body.UserData is Submarine hitSub)
|
||||
{
|
||||
alreadyHitSub = hitSub;
|
||||
}
|
||||
if (alreadyHitSub != null)
|
||||
{
|
||||
if (f.Body?.UserData is MapEntity me && me.Submarine == alreadyHitSub) { return false; }
|
||||
if (f.Body?.UserData as Submarine == alreadyHitSub) { return false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Submarine targetSub = target?.GetComponent<Projectile>()?.StickTarget?.UserData as Submarine ?? target.Submarine;
|
||||
|
||||
if (f.Body?.UserData is MapEntity mapEntity && mapEntity.Submarine != null)
|
||||
{
|
||||
if (mapEntity.Submarine == targetSub || mapEntity.Submarine == source.Submarine)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (f.Body?.UserData is Submarine sub)
|
||||
{
|
||||
if (sub == targetSub || sub == source.Submarine)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}) != null)
|
||||
{
|
||||
Snapped = true;
|
||||
return;
|
||||
}
|
||||
raycastTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 forceDir = diff;
|
||||
if (forceDir.LengthSquared() > 0.01f)
|
||||
{
|
||||
forceDir = Vector2.Normalize(forceDir);
|
||||
}
|
||||
|
||||
if (Math.Abs(ProjectilePullForce) > 0.001f)
|
||||
{
|
||||
var projectile = target.GetComponent<Projectile>();
|
||||
projectile?.Item?.body?.ApplyForce(-forceDir * ProjectilePullForce);
|
||||
}
|
||||
|
||||
if (Math.Abs(SourcePullForce) > 0.001f)
|
||||
{
|
||||
var sourceBody = GetBodyToPull(source);
|
||||
if (sourceBody != null)
|
||||
{
|
||||
sourceBody.ApplyForce(forceDir * SourcePullForce);
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.Abs(TargetPullForce) > 0.001f)
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
if (targetBody != null)
|
||||
{
|
||||
targetBody.ApplyForce(-forceDir * TargetPullForce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
base.UpdateBroken(deltaTime, cam);
|
||||
if (Snapped)
|
||||
{
|
||||
snapTimer += deltaTime;
|
||||
if (snapTimer >= SnapAnimDuration)
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PhysicsBody GetBodyToPull(Item target)
|
||||
{
|
||||
if (target.ParentInventory is CharacterInventory characterInventory &&
|
||||
characterInventory.Owner is Character ownerCharacter)
|
||||
{
|
||||
if (ownerCharacter.Removed) { return null; }
|
||||
return ownerCharacter.AnimController.Collider;
|
||||
}
|
||||
var projectile = target.GetComponent<Projectile>();
|
||||
if (projectile != null)
|
||||
{
|
||||
if (projectile.StickTarget?.UserData is Structure structure)
|
||||
{
|
||||
return structure.Submarine?.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget?.UserData is Submarine sub)
|
||||
{
|
||||
return sub?.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget?.UserData is Character character)
|
||||
{
|
||||
return character.AnimController.Collider;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (target.body != null) { return target.body; }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (XElement subElement in item.Prefab.ConfigElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "connectionpanel") { continue; }
|
||||
if (!subElement.Name.ToString().Equals("connectionpanel", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
foreach (XElement connectionElement in subElement.Elements())
|
||||
{
|
||||
|
||||
+25
-1
@@ -192,6 +192,8 @@ namespace Barotrauma.Items.Components
|
||||
public bool CheckCharacterSuccess(Character character)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
//no electrocution in sub editor
|
||||
if (Screen.Selected == GameMain.SubEditorScreen) { return true; }
|
||||
|
||||
var powered = item.GetComponent<Powered>();
|
||||
if (powered != null)
|
||||
@@ -203,7 +205,7 @@ namespace Barotrauma.Items.Components
|
||||
float degreeOfSuccess = DegreeOfSuccess(character);
|
||||
if (Rand.Range(0.0f, 0.5f) < degreeOfSuccess) { return true; }
|
||||
|
||||
item.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -262,7 +264,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (wire.OtherConnection(null) == null) //wire not connected to anything else
|
||||
{
|
||||
#if CLIENT
|
||||
if (SubEditorScreen.IsSubEditor())
|
||||
{
|
||||
wire.Item.Remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
wire.Item.Drop(null);
|
||||
}
|
||||
#else
|
||||
wire.Item.Drop(null);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +288,18 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (wire.OtherConnection(c) == null) //wire not connected to anything else
|
||||
{
|
||||
#if CLIENT
|
||||
if (SubEditorScreen.IsSubEditor())
|
||||
{
|
||||
wire.Item.Remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
wire.Item.Drop(null);
|
||||
}
|
||||
#else
|
||||
wire.Item.Drop(null);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+17
-2
@@ -12,6 +12,7 @@ namespace Barotrauma.Items.Components
|
||||
public bool ContinuousSignal;
|
||||
public bool State;
|
||||
public string ConnectionName;
|
||||
public string PropertyName;
|
||||
public Connection Connection;
|
||||
[Serialize("", false, translationTextTag: "Label.", description: "The text displayed on this button/tickbox."), Editable]
|
||||
public string Label { get; set; }
|
||||
@@ -28,11 +29,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Label = element.GetAttributeString("text", "");
|
||||
ConnectionName = element.GetAttributeString("connection", "");
|
||||
PropertyName = element.GetAttributeString("propertyname", "").ToLowerInvariant();
|
||||
Signal = element.GetAttributeString("signal", "1");
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() == "statuseffect")
|
||||
if (subElement.Name.ToString().Equals("statuseffect", System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName: "custom interface element (label " + Label + ")"));
|
||||
}
|
||||
@@ -89,6 +91,7 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "button":
|
||||
case "textbox":
|
||||
var button = new CustomInterfaceElement(subElement)
|
||||
{
|
||||
ContinuousSignal = false
|
||||
@@ -106,7 +109,7 @@ namespace Barotrauma.Items.Components
|
||||
};
|
||||
if (string.IsNullOrEmpty(tickBox.Label))
|
||||
{
|
||||
tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => !e.ContinuousSignal);
|
||||
tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => e.ContinuousSignal);
|
||||
}
|
||||
customInterfaceElementList.Add(tickBox);
|
||||
break;
|
||||
@@ -168,6 +171,18 @@ namespace Barotrauma.Items.Components
|
||||
tickBoxElement.State = state;
|
||||
}
|
||||
|
||||
private void TextChanged(CustomInterfaceElement textElement, string text)
|
||||
{
|
||||
textElement.Signal = text;
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (e.SerializableProperties.ContainsKey(textElement.PropertyName))
|
||||
{
|
||||
e.SerializableProperties[textElement.PropertyName].TrySetValue(e, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
UpdateProjSpecific();
|
||||
|
||||
+33
-23
@@ -15,13 +15,13 @@ namespace Barotrauma.Items.Components
|
||||
private float lightBrightness;
|
||||
private float blinkFrequency;
|
||||
private float range;
|
||||
private float flicker;
|
||||
private float flicker, flickerState;
|
||||
private bool castShadows;
|
||||
private bool drawBehindSubs;
|
||||
|
||||
private float blinkTimer;
|
||||
|
||||
private bool itemLoaded;
|
||||
private double lastToggleSignalTime;
|
||||
|
||||
public PhysicsBody ParentBody;
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
if (light != null) { light.Range = range; }
|
||||
#endif
|
||||
}
|
||||
@@ -78,13 +79,11 @@ namespace Barotrauma.Items.Components
|
||||
if (IsActive == value) { return; }
|
||||
|
||||
IsActive = value;
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && itemLoaded) { item.CreateServerEvent(this); }
|
||||
#endif
|
||||
OnStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How heavily the light flickers. 0 = no flickering, 1 = the light will alternate between completely dark and full brightness.")]
|
||||
[Editable, Serialize(0.0f, false, description: "How heavily the light flickers. 0 = no flickering, 1 = the light will alternate between completely dark and full brightness.")]
|
||||
public float Flicker
|
||||
{
|
||||
get { return flicker; }
|
||||
@@ -94,6 +93,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(1.0f, false, description: "How fast the light flickers.")]
|
||||
public float FlickerSpeed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(0.0f, true, description: "How rapidly the light blinks on and off (in Hz). 0 = no blinking.")]
|
||||
public float BlinkFrequency
|
||||
{
|
||||
@@ -117,6 +123,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "If enabled, the component will ignore continuous signals received in the toggle input (i.e. a continuous signal will only toggle it once).")]
|
||||
public bool IgnoreContinuousToggle
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -158,14 +171,7 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = IsOn;
|
||||
item.AddTag("light");
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
itemLoaded = true;
|
||||
SetLightSourceState(IsActive, lightBrightness);
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.AiTarget != null)
|
||||
@@ -219,7 +225,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
lightBrightness = MathHelper.Lerp(lightBrightness, Math.Min(Voltage, 1.0f), 0.1f);
|
||||
lightBrightness = MathHelper.Lerp(lightBrightness, powerConsumption <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f), 0.1f);
|
||||
}
|
||||
|
||||
if (blinkFrequency > 0.0f)
|
||||
@@ -233,7 +239,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
SetLightSourceState(true, lightBrightness * (1.0f - Rand.Range(0.0f, flicker)));
|
||||
flickerState += deltaTime * FlickerSpeed;
|
||||
flickerState %= 255;
|
||||
float noise = PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * flicker;
|
||||
SetLightSourceState(true, lightBrightness * (1.0f - noise));
|
||||
}
|
||||
|
||||
if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; }
|
||||
@@ -249,15 +258,21 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
partial void OnStateChanged();
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "toggle":
|
||||
IsActive = !IsActive;
|
||||
if (!IgnoreContinuousToggle || lastToggleSignalTime < Timing.TotalTime - 0.1)
|
||||
{
|
||||
IsOn = !IsOn;
|
||||
}
|
||||
lastToggleSignalTime = Timing.TotalTime;
|
||||
break;
|
||||
case "set_state":
|
||||
IsActive = (signal != "0");
|
||||
IsOn = signal != "0";
|
||||
break;
|
||||
case "set_color":
|
||||
LightColor = XMLExtensions.ParseColor(signal, false);
|
||||
@@ -265,11 +280,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(IsOn);
|
||||
}
|
||||
|
||||
private void UpdateAITarget(AITarget target)
|
||||
{
|
||||
if (!IsActive) { return; }
|
||||
|
||||
@@ -40,6 +40,9 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
rangeX = MathHelper.Clamp(value, 0.0f, 1000.0f);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
[InGameEditable, Serialize(0.0f, true, description: "Vertical movement detection range.")]
|
||||
|
||||
@@ -26,7 +26,13 @@ namespace Barotrauma.Items.Components
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
set { range = Math.Max(value, 0.0f); }
|
||||
set
|
||||
{
|
||||
range = Math.Max(value, 0.0f);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize(1, true, description: "WiFi components can only communicate with components that use the same channel.")]
|
||||
@@ -39,6 +45,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Serialize(false, false, description: "Can the component communicate with wifi components in another team's submarine (e.g. enemy sub in Combat missions, respawn shuttle). Needs to be enabled on both the component transmitting the signal and the component receiving it.")]
|
||||
public bool AllowCrossTeamCommunication
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, false, description: "If enabled, any signals received from another chat-linked wifi component are displayed " +
|
||||
"as chat messages in the chatbox of the player holding the item.")]
|
||||
public bool LinkToChat
|
||||
@@ -84,7 +98,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (sender == null || sender.channel != channel) { return false; }
|
||||
|
||||
if (sender.TeamID != TeamID)
|
||||
if (sender.TeamID != TeamID && !AllowCrossTeamCommunication)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -97,6 +111,10 @@ namespace Barotrauma.Items.Components
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
chatMsgCooldown -= deltaTime;
|
||||
if (chatMsgCooldown <= 0.0f)
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void TransmitSignal(int stepsTaken, string signal, Item source, Character sender, bool sendToChat, float signalStrength = 1.0f)
|
||||
@@ -164,7 +182,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chatMsgSent) chatMsgCooldown = MinChatMessageInterval;
|
||||
if (chatMsgSent)
|
||||
{
|
||||
chatMsgCooldown = MinChatMessageInterval;
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
prevSignal = signal;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -39,7 +42,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
const float MaxAttachDistance = 150.0f;
|
||||
|
||||
const float MinNodeDistance = 15.0f;
|
||||
const float MinNodeDistance = 7.0f;
|
||||
|
||||
const int MaxNodeCount = 255;
|
||||
const int MaxNodesPerNetworkEvent = 30;
|
||||
@@ -177,6 +180,8 @@ namespace Barotrauma.Items.Components
|
||||
newConnection.Item.Position :
|
||||
newConnection.Item.Position - refSub.HiddenSubPosition;
|
||||
|
||||
nodePos = RoundNode(nodePos);
|
||||
|
||||
if (nodes.Count > 0 && nodes[0] == nodePos) { break; }
|
||||
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { break; }
|
||||
|
||||
@@ -334,7 +339,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
bool disableGrid = SubEditorScreen.IsSubEditor() && (PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift));
|
||||
newNodePos = disableGrid ? item.Position : RoundNode(item.Position);
|
||||
#else
|
||||
newNodePos = RoundNode(item.Position);
|
||||
#endif
|
||||
if (sub != null) { newNodePos -= sub.HiddenSubPosition; }
|
||||
canPlaceNode = true;
|
||||
}
|
||||
@@ -497,6 +507,9 @@ namespace Barotrauma.Items.Components
|
||||
sectionExtents.Y = Math.Max(Math.Abs(nodes[i].Y - item.Position.Y), sectionExtents.Y);
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ClearConnections(Character user = null)
|
||||
@@ -507,7 +520,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(this))
|
||||
if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(this) && !item.Removed)
|
||||
{
|
||||
#if SERVER
|
||||
item.CreateServerEvent(connectionPanel);
|
||||
@@ -526,18 +539,18 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (connections[0] != null && connections[1] != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " disconnected a wire from " +
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
|
||||
connections[0].Item.Name + " (" + connections[0].Name + ") to "+
|
||||
connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
else if (connections[0] != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " disconnected a wire from " +
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
|
||||
connections[0].Item.Name + " (" + connections[0].Name + ")", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
else if (connections[1] != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " disconnected a wire from " +
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
|
||||
connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private int failedLaunchAttempts;
|
||||
|
||||
private readonly List<Item> activeProjectiles = new List<Item>();
|
||||
public IEnumerable<Item> ActiveProjectiles => activeProjectiles;
|
||||
|
||||
private Character user;
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
}
|
||||
|
||||
[Serialize("0,0", false, description: "The position of the barrel relative to the upper left corner of the base sprite (in pixels).")]
|
||||
public Vector2 BarrelPos
|
||||
@@ -72,6 +80,41 @@ namespace Barotrauma.Items.Components
|
||||
set { reloadTime = value; }
|
||||
}
|
||||
|
||||
[Editable(0.1f, 10f), Serialize(1.0f, false, description: "Modifies the duration of retraction of the barrell after recoil to get back to the original position after shooting. Reload time affects this too.")]
|
||||
public float RetractionDurationMultiplier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(0.1f, 10f), Serialize(0.1f, false, description: "How quickly the recoil moves the barrel after launching.")]
|
||||
public float RecoilTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(0f, 1000f), Serialize(0f, false, description: "How long the barrell stays in place after the recoil and before retracting back to the original position.")]
|
||||
public float RetractionDelay
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1, false, description: "How projectiles the weapon launches when fired once.")]
|
||||
public int ProjectileCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Can the turret be fired without projectiles (causing it just to execute the OnUse effects and the firing animation without actually firing anything).")]
|
||||
public bool LaunchWithoutProjectile
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0.0,0.0", true, description: "The range at which the barrel can rotate. TODO")]
|
||||
public Vector2 RotationLimits
|
||||
{
|
||||
@@ -95,6 +138,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "Random spread applied to the firing angle of the projectiles (in degrees).")]
|
||||
public float Spread
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(0.0f, 1000.0f, DecimalCount = 2),
|
||||
Serialize(5.0f, false, description: "How much torque is applied to rotate the barrel when the item is used by a character"
|
||||
+ " with insufficient skills to operate it. Higher values make the barrel rotate faster.")]
|
||||
@@ -155,7 +205,21 @@ namespace Barotrauma.Items.Components
|
||||
UpdateTransformedBarrelPos();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Serialize(3000.0f, true, description: "How close to a target the turret has to be for an AI character to fire it.")]
|
||||
public float AIRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-1, true, description: "The turret won't fire additional projectiles if the number of previously fired, still active projectiles reaches this limit. If set to -1, there is no limit to the number of projectiles.")]
|
||||
public int MaxActiveProjectiles
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Turret(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -187,6 +251,7 @@ namespace Barotrauma.Items.Components
|
||||
//if (item.FlippedY) flippedRotation = 180.0f - flippedRotation;
|
||||
transformedBarrelPos = MathUtils.RotatePointAroundTarget(barrelPos * item.Scale, new Vector2(item.Rect.Width / 2, item.Rect.Height / 2), flippedRotation);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
item.SpriteRotation = MathHelper.ToRadians(flippedRotation);
|
||||
#endif
|
||||
}
|
||||
@@ -213,13 +278,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
this.cam = cam;
|
||||
|
||||
if (reload > 0.0f) reload -= deltaTime;
|
||||
if (reload > 0.0f) { reload -= deltaTime; }
|
||||
|
||||
if (user != null && user.Removed)
|
||||
{
|
||||
user = null;
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
UpdateProjSpecific(deltaTime);
|
||||
|
||||
if (minRotation == maxRotation) return;
|
||||
if (minRotation == maxRotation) { return; }
|
||||
|
||||
float targetMidDiff = MathHelper.WrapAngle(targetRotation - (minRotation + maxRotation) / 2.0f);
|
||||
|
||||
@@ -230,12 +300,19 @@ namespace Barotrauma.Items.Components
|
||||
targetRotation = (targetMidDiff < 0.0f) ? minRotation : maxRotation;
|
||||
}
|
||||
|
||||
float degreeOfSuccess = user == null ? 0.5f : DegreeOfSuccess(user);
|
||||
if (degreeOfSuccess < 0.5f) degreeOfSuccess *= degreeOfSuccess; //the ease of aiming drops quickly with insufficient skill levels
|
||||
float degreeOfSuccess = user == null ? 0.5f : DegreeOfSuccess(user);
|
||||
if (degreeOfSuccess < 0.5f) { degreeOfSuccess *= degreeOfSuccess; } //the ease of aiming drops quickly with insufficient skill levels
|
||||
float springStiffness = MathHelper.Lerp(SpringStiffnessLowSkill, SpringStiffnessHighSkill, degreeOfSuccess);
|
||||
float springDamping = MathHelper.Lerp(SpringDampingLowSkill, SpringDampingHighSkill, degreeOfSuccess);
|
||||
float rotationSpeed = MathHelper.Lerp(RotationSpeedLowSkill, RotationSpeedHighSkill, degreeOfSuccess);
|
||||
|
||||
if (user?.Info != null)
|
||||
{
|
||||
user.Info.IncreaseSkillLevel("weapons",
|
||||
SkillSettings.Current.SkillIncreasePerSecondWhenOperatingTurret * deltaTime / Math.Max(user.GetSkillLevel("weapons"), 1.0f),
|
||||
user.WorldPosition + Vector2.UnitY * 150.0f);
|
||||
}
|
||||
|
||||
angularVelocity +=
|
||||
(MathHelper.WrapAngle(targetRotation - rotation) * springStiffness - angularVelocity * springDamping) * deltaTime;
|
||||
angularVelocity = MathHelper.Clamp(angularVelocity, -rotationSpeed, rotationSpeed);
|
||||
@@ -265,95 +342,110 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (!characterUsable && character != null) return false;
|
||||
if (!characterUsable && character != null) { return false; }
|
||||
return TryLaunch(deltaTime, character);
|
||||
}
|
||||
|
||||
private bool TryLaunch(float deltaTime, Character character = null)
|
||||
private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null) return false;
|
||||
#endif
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
|
||||
if (reload > 0.0f) return false;
|
||||
if (reload > 0.0f) { return false; }
|
||||
|
||||
if (GetAvailableBatteryPower() < powerConsumption)
|
||||
if (MaxActiveProjectiles >= 0)
|
||||
{
|
||||
#if CLIENT
|
||||
if (!flashLowPower && character != null && character == Character.Controlled)
|
||||
activeProjectiles.RemoveAll(it => it.Removed);
|
||||
if (activeProjectiles.Count >= MaxActiveProjectiles)
|
||||
{
|
||||
flashLowPower = true;
|
||||
GUI.PlayUISound(GUISoundType.PickItemFail);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (MapEntity e in item.linkedTo)
|
||||
if (!ignorePower)
|
||||
{
|
||||
//use linked projectile containers in case they have to react to the turret being launched somehow
|
||||
//(play a sound, spawn more projectiles)
|
||||
if (!(e is Item linkedItem)) continue;
|
||||
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (projectileContainer != null)
|
||||
if (GetAvailableBatteryPower() < powerConsumption)
|
||||
{
|
||||
linkedItem.Use(deltaTime, null);
|
||||
var repairable = linkedItem.GetComponent<Repairable>();
|
||||
if (repairable != null)
|
||||
{
|
||||
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var projectiles = GetLoadedProjectiles(true);
|
||||
if (projectiles.Count == 0)
|
||||
{
|
||||
//coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched,
|
||||
//causing a one frame delay before the gun can be launched (or more in multiplayer where there may be a longer delay)
|
||||
// -> attempt to launch the gun multiple times before showing the "no ammo" flash
|
||||
failedLaunchAttempts++;
|
||||
#if CLIENT
|
||||
if (!flashNoAmmo && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
|
||||
{
|
||||
flashNoAmmo = true;
|
||||
failedLaunchAttempts = 0;
|
||||
GUI.PlayUISound(GUISoundType.PickItemFail);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
failedLaunchAttempts = 0;
|
||||
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
float neededPower = powerConsumption;
|
||||
|
||||
while (neededPower > 0.0001f && batteries.Count > 0)
|
||||
{
|
||||
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
|
||||
float takePower = neededPower / batteries.Count;
|
||||
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
|
||||
foreach (PowerContainer battery in batteries)
|
||||
{
|
||||
neededPower -= takePower;
|
||||
battery.Charge -= takePower / 3600.0f;
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
if (!flashLowPower && character != null && character == Character.Controlled)
|
||||
{
|
||||
battery.Item.CreateServerEvent(battery);
|
||||
flashLowPower = true;
|
||||
GUI.PlayUISound(GUISoundType.PickItemFail);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Launch(projectiles[0].Item, character);
|
||||
Projectile launchedProjectile = null;
|
||||
for (int i = 0; i < ProjectileCount; i++)
|
||||
{
|
||||
foreach (MapEntity e in item.linkedTo)
|
||||
{
|
||||
//use linked projectile containers in case they have to react to the turret being launched somehow
|
||||
//(play a sound, spawn more projectiles)
|
||||
if (!(e is Item linkedItem)) { continue; }
|
||||
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (projectileContainer != null)
|
||||
{
|
||||
linkedItem.Use(deltaTime, null);
|
||||
var repairable = linkedItem.GetComponent<Repairable>();
|
||||
if (repairable != null && failedLaunchAttempts < 2)
|
||||
{
|
||||
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
var projectiles = GetLoadedProjectiles(true);
|
||||
if (projectiles.Count == 0 && !LaunchWithoutProjectile)
|
||||
{
|
||||
//coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched,
|
||||
//causing a one frame delay before the gun can be launched (or more in multiplayer where there may be a longer delay)
|
||||
// -> attempt to launch the gun multiple times before showing the "no ammo" flash
|
||||
failedLaunchAttempts++;
|
||||
#if CLIENT
|
||||
if (!flashNoAmmo && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
|
||||
{
|
||||
flashNoAmmo = true;
|
||||
failedLaunchAttempts = 0;
|
||||
GUI.PlayUISound(GUISoundType.PickItemFail);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
failedLaunchAttempts = 0;
|
||||
launchedProjectile = projectiles.FirstOrDefault();
|
||||
|
||||
if (!ignorePower)
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
float neededPower = powerConsumption;
|
||||
while (neededPower > 0.0001f && batteries.Count > 0)
|
||||
{
|
||||
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
|
||||
float takePower = neededPower / batteries.Count;
|
||||
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
|
||||
foreach (PowerContainer battery in batteries)
|
||||
{
|
||||
neededPower -= takePower;
|
||||
battery.Charge -= takePower / 3600.0f;
|
||||
#if SERVER
|
||||
battery.Item.CreateServerEvent(battery);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (launchedProjectile != null || LaunchWithoutProjectile)
|
||||
{
|
||||
Launch(launchedProjectile?.Item, character);
|
||||
}
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (character != null)
|
||||
if (character != null && launchedProjectile != null)
|
||||
{
|
||||
string msg = character.LogName + " launched " + item.Name + " (projectile: " + projectiles[0].Item.Name;
|
||||
var containedItems = projectiles[0].Item.ContainedItems;
|
||||
string msg = GameServer.CharacterLogName(character) + " launched " + item.Name + " (projectile: " + launchedProjectile.Item.Name;
|
||||
var containedItems = launchedProjectile.Item.ContainedItems;
|
||||
if (containedItems == null || !containedItems.Any())
|
||||
{
|
||||
msg += ")";
|
||||
@@ -369,29 +461,38 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Launch(Item projectile, Character user = null)
|
||||
private void Launch(Item projectile, Character user = null, float? launchRotation = null)
|
||||
{
|
||||
reload = reloadTime;
|
||||
|
||||
projectile.Drop(null);
|
||||
if (projectile.body == null) { return; }
|
||||
|
||||
projectile.body.Dir = 1.0f;
|
||||
projectile.body.ResetDynamics();
|
||||
projectile.body.Enabled = true;
|
||||
projectile.SetTransform(ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)), -rotation);
|
||||
projectile.UpdateTransform();
|
||||
projectile.Submarine = projectile.body.Submarine;
|
||||
|
||||
Projectile projectileComponent = projectile.GetComponent<Projectile>();
|
||||
if (projectileComponent != null)
|
||||
if (projectile != null)
|
||||
{
|
||||
projectileComponent.Use((float)Timing.Step);
|
||||
projectileComponent.User = user;
|
||||
}
|
||||
activeProjectiles.Add(projectile);
|
||||
projectile.Drop(null);
|
||||
if (projectile.body != null)
|
||||
{
|
||||
projectile.body.Dir = 1.0f;
|
||||
projectile.body.ResetDynamics();
|
||||
projectile.body.Enabled = true;
|
||||
}
|
||||
|
||||
if (projectile.Container != null) projectile.Container.RemoveContained(projectile);
|
||||
|
||||
float spread = MathHelper.ToRadians(Spread) * Rand.Range(-0.5f, 0.5f);
|
||||
projectile.SetTransform(
|
||||
ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)),
|
||||
-(launchRotation ?? rotation) + spread);
|
||||
projectile.UpdateTransform();
|
||||
projectile.Submarine = projectile.body?.Submarine;
|
||||
|
||||
Projectile projectileComponent = projectile.GetComponent<Projectile>();
|
||||
if (projectileComponent != null)
|
||||
{
|
||||
projectileComponent.Use((float)Timing.Step);
|
||||
projectile.GetComponent<Rope>()?.Attach(item, projectile);
|
||||
projectileComponent.User = user;
|
||||
}
|
||||
|
||||
if (projectile.Container != null) { projectile.Container.RemoveContained(projectile); }
|
||||
}
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), projectile });
|
||||
@@ -403,6 +504,196 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void LaunchProjSpecific();
|
||||
|
||||
private float waitTimer;
|
||||
private float disorderTimer;
|
||||
|
||||
private float prevTargetRotation;
|
||||
private float updateTimer;
|
||||
private bool updatePending;
|
||||
public void ThalamusOperate(WreckAI ai, float deltaTime, bool targetHumans, bool targetOtherCreatures, bool targetSubmarines, bool ignoreDelay)
|
||||
{
|
||||
if (ai == null) { return; }
|
||||
|
||||
IsActive = true;
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (updatePending)
|
||||
{
|
||||
if (updateTimer < 0.0f)
|
||||
{
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
prevTargetRotation = targetRotation;
|
||||
updateTimer = 0.25f;
|
||||
}
|
||||
updateTimer -= deltaTime;
|
||||
}
|
||||
|
||||
if (!ignoreDelay && waitTimer > 0)
|
||||
{
|
||||
waitTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
Submarine closestSub = null;
|
||||
float maxDistance = 10000.0f;
|
||||
float shootDistance = AIRange;
|
||||
ISpatialEntity target = null;
|
||||
float closestDist = shootDistance * shootDistance;
|
||||
if (targetHumans || targetOtherCreatures)
|
||||
{
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
if (character == null || character.Removed || character.IsDead) { continue; }
|
||||
if (character.Params.Group.Equals(ai.Config.Entity, StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
bool isHuman = character.IsHuman || character.Params.Group.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
|
||||
if (isHuman)
|
||||
{
|
||||
if (!targetHumans)
|
||||
{
|
||||
// Don't target humans if not defined to.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (!targetOtherCreatures)
|
||||
{
|
||||
// Don't target other creatures if not defined to.
|
||||
continue;
|
||||
}
|
||||
float dist = Vector2.DistanceSquared(character.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
target = character;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
if (targetSubmarines)
|
||||
{
|
||||
if (target == null || target.Submarine != null)
|
||||
{
|
||||
closestDist = maxDistance * maxDistance;
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
float dist = Vector2.DistanceSquared(sub.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
closestSub = sub;
|
||||
closestDist = dist;
|
||||
}
|
||||
closestDist = shootDistance * shootDistance;
|
||||
if (closestSub != null)
|
||||
{
|
||||
foreach (var hull in Hull.hullList)
|
||||
{
|
||||
if (!closestSub.IsEntityFoundOnThisSub(hull, true)) { continue; }
|
||||
float dist = Vector2.DistanceSquared(hull.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
target = hull;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ignoreDelay)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
// Random movement
|
||||
waitTimer = Rand.Value(Rand.RandSync.Unsynced) < 0.98f ? 0f : Rand.Range(5f, 20f);
|
||||
targetRotation = Rand.Range(minRotation, maxRotation);
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
if (disorderTimer < 0)
|
||||
{
|
||||
// Random disorder
|
||||
disorderTimer = Rand.Range(0f, 3f);
|
||||
waitTimer = Rand.Range(0.25f, 1f);
|
||||
targetRotation = MathUtils.WrapAngleTwoPi(targetRotation += Rand.Range(-1f, 1f));
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
disorderTimer -= deltaTime;
|
||||
}
|
||||
}
|
||||
if (target == null) { return; }
|
||||
|
||||
float angle = -MathUtils.VectorToAngle(target.WorldPosition - item.WorldPosition);
|
||||
targetRotation = MathUtils.WrapAngleTwoPi(angle);
|
||||
|
||||
if (Math.Abs(targetRotation - prevTargetRotation) > 0.1f) { updatePending = true; }
|
||||
|
||||
if (target is Hull targetHull)
|
||||
{
|
||||
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
|
||||
if (!MathUtils.GetLineRectangleIntersection(item.WorldPosition, item.WorldPosition + barrelDir * AIRange, targetHull.WorldRect, out _))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float midRotation = (minRotation + maxRotation) / 2.0f;
|
||||
while (midRotation - angle < -MathHelper.Pi) { angle -= MathHelper.TwoPi; }
|
||||
while (midRotation - angle > MathHelper.Pi) { angle += MathHelper.TwoPi; }
|
||||
if (angle < minRotation || angle > maxRotation) { return; }
|
||||
float enemyAngle = MathUtils.VectorToAngle(target.WorldPosition - item.WorldPosition);
|
||||
float turretAngle = -rotation;
|
||||
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) { return; }
|
||||
}
|
||||
|
||||
Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition);
|
||||
Vector2 end = ConvertUnits.ToSimUnits(target.WorldPosition);
|
||||
if (target.Submarine != null)
|
||||
{
|
||||
start -= target.Submarine.SimPosition;
|
||||
end -= target.Submarine.SimPosition;
|
||||
}
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true);
|
||||
if (pickedBody == null) { return; }
|
||||
Character targetCharacter = null;
|
||||
if (pickedBody.UserData is Character c)
|
||||
{
|
||||
targetCharacter = c;
|
||||
}
|
||||
else if (pickedBody.UserData is Limb limb)
|
||||
{
|
||||
targetCharacter = limb.character;
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (targetCharacter.Params.Group.Equals(ai.Config.Entity, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Don't shoot friendly characters
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pickedBody.UserData is ISpatialEntity e)
|
||||
{
|
||||
Submarine sub = e.Submarine;
|
||||
if (sub == null) { return; }
|
||||
if (!targetSubmarines) { return; }
|
||||
if (sub == Item.Submarine) { return; }
|
||||
// Don't shoot non-player submarines, i.e. wrecks or outposts.
|
||||
if (!sub.Info.IsPlayer) { return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hit something else, probably a level wall
|
||||
return;
|
||||
}
|
||||
}
|
||||
TryLaunch(deltaTime, ignorePower: true);
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget &&
|
||||
@@ -484,7 +775,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//enough shells and power
|
||||
Character closestEnemy = null;
|
||||
float closestDist = 3000 * 3000;
|
||||
float closestDist = AIRange * AIRange;
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
// Ignore dead, friendly, and those that are inside the same sub
|
||||
@@ -526,7 +817,7 @@ namespace Barotrauma.Items.Components
|
||||
end -= closestEnemy.Submarine.SimPosition;
|
||||
}
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories);
|
||||
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true);
|
||||
if (pickedBody == null) { return false; }
|
||||
Character targetCharacter = null;
|
||||
if (pickedBody.UserData is Character c)
|
||||
@@ -537,39 +828,40 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
targetCharacter = limb.character;
|
||||
}
|
||||
if (targetCharacter != null && HumanAIController.IsFriendly(character, targetCharacter))
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
// Don't shoot friendly characters
|
||||
return false;
|
||||
if (HumanAIController.IsFriendly(character, targetCharacter))
|
||||
{
|
||||
// Don't shoot friendly characters
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (targetCharacter == null && !(pickedBody.UserData is Structure) && !(pickedBody.UserData is Item))
|
||||
else
|
||||
{
|
||||
// Hit something else than a wall or an item (probably a level wall)
|
||||
return false;
|
||||
if (pickedBody.UserData is ISpatialEntity e)
|
||||
{
|
||||
Submarine sub = e.Submarine;
|
||||
if (sub == null) { return false; }
|
||||
if (sub == Item.Submarine) { return false; }
|
||||
// Don't shoot non-player submarines, i.e. wrecks or outposts.
|
||||
if (!sub.Info.IsPlayer) { return false; }
|
||||
// Don't shoot friendly submarines.
|
||||
if (sub.TeamID == Item.Submarine.TeamID) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hit something else, probably a level wall
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (objective.Option.ToLowerInvariant() == "fireatwill")
|
||||
if (objective.Option.Equals("fireatwill", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
character?.Speak(TextManager.GetWithVariable("DialogFireTurret", "[itemname]", item.Name, true), null, 0.0f, "fireturret", 5.0f);
|
||||
character.SetInput(InputType.Shoot, true, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void GetAvailablePower(out float availableCharge, out float availableCapacity)
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
|
||||
availableCharge = 0.0f;
|
||||
availableCapacity = 0.0f;
|
||||
foreach (PowerContainer battery in batteries)
|
||||
{
|
||||
availableCharge += battery.Charge;
|
||||
availableCapacity += battery.Capacity;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
@@ -614,7 +906,7 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
//check if the contained item is another itemcontainer with projectiles inside it
|
||||
if (containedItem.ContainedItems == null) continue;
|
||||
if (containedItem.ContainedItems == null) { continue; }
|
||||
foreach (Item subContainedItem in containedItem.ContainedItems)
|
||||
{
|
||||
projectileComponent = subContainedItem.GetComponent<Projectile>();
|
||||
@@ -695,7 +987,6 @@ namespace Barotrauma.Items.Components
|
||||
TryLaunch((float)Timing.Step, sender);
|
||||
}
|
||||
break;
|
||||
case "toggle":
|
||||
case "toggle_light":
|
||||
if (lightComponent != null)
|
||||
{
|
||||
@@ -707,8 +998,25 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
{
|
||||
Item item = (Item)extraData[2];
|
||||
msg.Write(item.Removed ? (ushort)0 : item.ID);
|
||||
if (extraData.Length > 2)
|
||||
{
|
||||
msg.Write(!(extraData[2] is Item item) || item.Removed ? ushort.MaxValue : item.ID);
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(rotation, minRotation, maxRotation), minRotation, maxRotation, 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((ushort)0);
|
||||
float wrappedTargetRotation = targetRotation;
|
||||
while (wrappedTargetRotation < minRotation && MathUtils.IsValid(wrappedTargetRotation))
|
||||
{
|
||||
wrappedTargetRotation += MathHelper.TwoPi;
|
||||
}
|
||||
while (wrappedTargetRotation > maxRotation && MathUtils.IsValid(wrappedTargetRotation))
|
||||
{
|
||||
wrappedTargetRotation -= MathHelper.TwoPi;
|
||||
}
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(wrappedTargetRotation, minRotation, maxRotation), minRotation, maxRotation, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +211,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
public bool AutoEquipWhenFull { get; private set; }
|
||||
public bool DisplayContainedStatus { get; private set; }
|
||||
|
||||
public readonly int Variants;
|
||||
|
||||
@@ -264,6 +265,7 @@ namespace Barotrauma.Items.Components
|
||||
limbType = new LimbType[spriteCount];
|
||||
limb = new Limb[spriteCount];
|
||||
AutoEquipWhenFull = element.GetAttributeBool("autoequipwhenfull", true);
|
||||
DisplayContainedStatus = element.GetAttributeBool("displaycontainedstatus", false);
|
||||
int i = 0;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -284,7 +286,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (XElement lightElement in subElement.Elements())
|
||||
{
|
||||
if (lightElement.Name.ToString().ToLowerInvariant() != "lightcomponent") continue;
|
||||
if (!lightElement.Name.ToString().Equals("lightcomponent", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
wearableSprites[i].LightComponent = new LightComponent(item, lightElement)
|
||||
{
|
||||
Parent = this
|
||||
|
||||
Reference in New Issue
Block a user