OBT/1.2.0(Spring Update)
Sync with Upstream
This commit is contained in:
@@ -739,6 +739,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
picker.Inventory.FlashAllowedSlots(item, Color.Red);
|
||||
}
|
||||
else
|
||||
{
|
||||
//normally this would be done in the base.OnPicked method, but clients don't call it,
|
||||
//but instead rely on the server telling them to put the item in the inventory
|
||||
SoundPlayer.PlayUISound(GUISoundType.PickItem);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -347,18 +348,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (f2.Body.UserData is Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter == picker || targetCharacter == User) { return false; }
|
||||
if (targetCharacter.IgnoreMeleeWeapons) { return false; }
|
||||
if (HitFriendlyTarget(targetCharacter)) { return false; }
|
||||
if (AllowHitMultiple)
|
||||
{
|
||||
if (hitTargets.Contains(targetCharacter)) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hitTargets.Any(t => t is Character)) { return false; }
|
||||
}
|
||||
hitTargets.Add(targetCharacter);
|
||||
//only allow hitting limbs, not the main collider
|
||||
//otherwise it's difficult to make certain parts of the ragdoll not take hits by making them ignore collisions or melee weapons
|
||||
return false;
|
||||
}
|
||||
else if (!HitOnlyCharacters)
|
||||
{
|
||||
@@ -435,7 +427,7 @@ namespace Barotrauma.Items.Components
|
||||
Structure targetStructure = target.UserData as Structure ?? targetFixture.UserData as Structure;
|
||||
Item targetItem = target.UserData is Holdable h ? h.Item : target.UserData as Item ?? targetFixture.UserData as Item;
|
||||
Entity targetEntity = targetCharacter ?? targetStructure ?? targetItem ?? target.UserData as Entity;
|
||||
GameMain.LuaCs.Hook.Call("meleeWeapon.handleImpact", this, target);
|
||||
LuaCsSetup.Instance.EventService.PublishEvent<IEventMeleeWeaponHandleImpact>(x => x.OnMeleeWeaponHandleImpact(this, target));
|
||||
|
||||
if (Attack != null)
|
||||
{
|
||||
|
||||
@@ -380,7 +380,7 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
case "requireditem":
|
||||
case "requireditems":
|
||||
SetRequiredItems(subElement);
|
||||
SetRequiredItems(subElement, allowEmpty: true);
|
||||
break;
|
||||
case "requiredskill":
|
||||
case "requiredskills":
|
||||
@@ -1102,6 +1102,9 @@ namespace Barotrauma.Items.Components
|
||||
foreach (RelatedItem ri in DisabledRequiredItems)
|
||||
{
|
||||
XElement newElement = new XElement("requireditem");
|
||||
//if we have some actual requirements, no need to keep the empty requirement
|
||||
//as a "placeholder" for the user to add requirements in the sub editor
|
||||
if (ri.Identifiers.IsEmpty && RequiredItems.Any()) { continue; }
|
||||
ri.Save(newElement);
|
||||
componentElement.Add(newElement);
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element);
|
||||
|
||||
public void OnItemContained(Item containedItem)
|
||||
public void OnItemContained(Item containedItem, bool triggerOnInsertedEffects = true)
|
||||
{
|
||||
int index = Inventory.FindIndex(containedItem);
|
||||
RelatedItem relatedItem = null;
|
||||
@@ -444,14 +444,20 @@ namespace Barotrauma.Items.Components
|
||||
ActiveContainedItem activeContainedItem = new(containedItem, effect, containableItem.ExcludeBroken, containableItem.ExcludeFullCondition, containableItem.BlameEquipperForDeath);
|
||||
activeContainedItems.Add(activeContainedItem);
|
||||
|
||||
if (!ShouldApplyEffects(activeContainedItem) || item.Submarine is { Loading: true} || initializingLoadedItems ||
|
||||
containedItem.OnInsertedEffectsApplied)
|
||||
{
|
||||
continue;
|
||||
if (triggerOnInsertedEffects)
|
||||
{
|
||||
if (!ShouldApplyEffects(activeContainedItem) || item.Submarine is { Loading: true} || initializingLoadedItems ||
|
||||
containedItem.OnInsertedEffectsApplied)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
activeContainedItem.StatusEffect.Apply(ActionType.OnInserted, deltaTime: 1, item, targets);
|
||||
}
|
||||
activeContainedItem.StatusEffect.Apply(ActionType.OnInserted, deltaTime: 1, item, targets);
|
||||
}
|
||||
containedItem.OnInsertedEffectsApplied = true;
|
||||
if (triggerOnInsertedEffects)
|
||||
{
|
||||
containedItem.OnInsertedEffectsApplied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1119,6 +1125,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.GetComponent<Holdable>() is { Attachable: true })
|
||||
{
|
||||
//if the item is attachable to walls, we need a bit of special logic because the item can either
|
||||
//have or not have a body depending on whether it's attached.
|
||||
|
||||
//since it seems previously the contained item positions have always been configured as if the item had no body (using the top-left corner as the origin),
|
||||
//let's modify the position here to position the items correctly even when the body is active (moving the origin from the center of the body to the top-left corner)
|
||||
transformedItemPos -= item.Rect.Size.FlipY().ToVector2() / 2;
|
||||
}
|
||||
|
||||
Matrix transform = Matrix.CreateRotationZ(drawPosition ? item.body.DrawRotation : item.body.Rotation);
|
||||
if (bodyFlipped)
|
||||
{
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
#nullable enable
|
||||
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Item component used by <see cref="Controller.SpawnItemOnSelected"/> for keeping a reference to the character that is currently
|
||||
/// selecting the controller. Also provides functionality for changing the inventory sprite of the item based on the linked character.
|
||||
/// </summary>
|
||||
partial class LinkedControllerCharacterComponent : ItemComponent, IServerSerializable
|
||||
{
|
||||
#if CLIENT
|
||||
private class SpriteOverride
|
||||
{
|
||||
public readonly Sprite? Sprite;
|
||||
public readonly Identifier SpeciesName;
|
||||
public readonly Identifier SpeciesGroup;
|
||||
public SpriteOverride(ContentXElement element)
|
||||
{
|
||||
if (element.GetChildElement("Sprite") is ContentXElement spriteElement)
|
||||
{
|
||||
Sprite = new Sprite(spriteElement);
|
||||
}
|
||||
SpeciesName = element.GetAttributeIdentifier("speciesname", Identifier.Empty);
|
||||
SpeciesGroup = element.GetAttributeIdentifier("speciesgroup", Identifier.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ImmutableArray<SpriteOverride> spriteOverrides;
|
||||
#endif
|
||||
|
||||
[Serialize(0.5f, IsPropertySaveable.No, description: $"Maximum value which {nameof(DeconstructTimeMultiplier)} can be.")]
|
||||
public float MaxDeconstructTimeMultiplier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Character? Character { get; private set; }
|
||||
|
||||
public bool DoesBleed => Character?.DoesBleed == true;
|
||||
|
||||
public float DeconstructTimeMultiplier { get; private set; } = 1f;
|
||||
|
||||
public LinkedControllerCharacterComponent(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
#if CLIENT
|
||||
spriteOverrides = element.Elements()
|
||||
.Where(static e => e.Name.LocalName.ToLowerInvariant() == "spriteoverride")
|
||||
.Select(static e => new SpriteOverride(e))
|
||||
.ToImmutableArray();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdateLinkedCharacter(Character? character)
|
||||
{
|
||||
Character = character;
|
||||
|
||||
if (character != null)
|
||||
{
|
||||
var animController = character.AnimController;
|
||||
float totalLimbs = animController.Limbs.Length;
|
||||
float nonSeveredLimbs = animController.Limbs.Count(static l => !l.IsSevered);
|
||||
|
||||
// Decrease deconstruction time if the character is missing some limbs
|
||||
DeconstructTimeMultiplier *= MathF.Max(MaxDeconstructTimeMultiplier, nonSeveredLimbs / totalLimbs);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (character != null)
|
||||
{
|
||||
SpriteOverride? spriteOverride =
|
||||
spriteOverrides.Where(s => s.SpeciesName == character.SpeciesName).FirstOrDefault()
|
||||
?? spriteOverrides.Where(s => s.SpeciesGroup == character.Group).FirstOrDefault();
|
||||
|
||||
if (spriteOverride != null)
|
||||
{
|
||||
item.OverrideInventorySprite = spriteOverride.Sprite;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.OverrideInventorySprite = null;
|
||||
}
|
||||
#elif SERVER
|
||||
Item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
{
|
||||
UInt16 characterId = msg.ReadUInt16();
|
||||
if (characterId == Entity.NullEntityID)
|
||||
{
|
||||
UpdateLinkedCharacter(null);
|
||||
}
|
||||
else if (Entity.FindEntityByID(characterId) is Character character)
|
||||
{
|
||||
UpdateLinkedCharacter(character);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData? extraData = null)
|
||||
{
|
||||
if (Character != null)
|
||||
{
|
||||
msg.WriteUInt16(Character.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.WriteUInt16(Entity.NullEntityID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -45,6 +48,39 @@ namespace Barotrauma.Items.Components
|
||||
private Camera cam;
|
||||
|
||||
private Character user;
|
||||
public Character User
|
||||
{
|
||||
get { return user; }
|
||||
private set
|
||||
{
|
||||
if (user == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
user = value;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
teleportTransition = 0f;
|
||||
teleportStartPosition = user.WorldPosition;
|
||||
}
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
|
||||
#if CLIENT
|
||||
UpdateMsg();
|
||||
|
||||
if (HideAllItemComponentHUDs && Character.Controlled == user)
|
||||
{
|
||||
// Prevents any UIs in this item from briefly showing up when you select this controller, since
|
||||
// activeHUDs would take a single frame to be updated to not contain any other item component HUD
|
||||
Item.ClearActiveHUDs();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private Item focusTarget;
|
||||
private float targetRotation;
|
||||
@@ -55,11 +91,6 @@ namespace Barotrauma.Items.Components
|
||||
set { userPos = value; }
|
||||
}
|
||||
|
||||
public Character User
|
||||
{
|
||||
get { return user; }
|
||||
}
|
||||
|
||||
public IEnumerable<LimbPos> LimbPositions { get { return limbPositions; } }
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.No, description: "When enabled, the item will continuously send out a signal and interacting with it will flip the signal (making the item behave like a switch). When disabled, the item will simply send out a signal when interacted with.", alwaysUseInstanceValues: true)]
|
||||
@@ -123,6 +154,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Should the HUDs of all item components in this item be hidden when a character is using this controller.")]
|
||||
public bool HideAllItemComponentHUDs
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public enum UseEnvironment
|
||||
{
|
||||
Air, Water, Both
|
||||
@@ -152,6 +190,49 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can a character put another character into this controller by dragging them and selecting this controller?")]
|
||||
public bool AllowPuttingInOtherCharacters
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Can a character select this controller by themselves?")]
|
||||
public bool CanBeSelectedByCharacters
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "If a character selects this controller, but another character already has it selected, should it be kicked out?")]
|
||||
public bool SelectingKicksCharacterOut
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("", IsPropertySaveable.No, description: "Message displayed when there's a character inside this controller.")]
|
||||
public string KickOutCharacterMsg
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("", IsPropertySaveable.No, description: "Message displayed when you are putting a character into the controller.")]
|
||||
public string PutOtherCharacterMsg
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize("", IsPropertySaveable.No, description: "Spawns this item in the first available item container slot when a character selects this controller, if the item container is full, the character will not be able to select the controller.")]
|
||||
public Identifier SpawnItemOnSelected
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool ControlCharacterPose
|
||||
{
|
||||
get { return limbPositions.Count > 0; }
|
||||
@@ -204,6 +285,23 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine how fast the character is teleported
|
||||
/// to the item when they first select the controller.
|
||||
/// Only relevant for <see cref="ForceUserToStayAttached" />
|
||||
/// </summary>
|
||||
private const float TeleportTransitionSpeed = 8f;
|
||||
private float teleportTransition = 0f;
|
||||
private Vector2 teleportStartPosition;
|
||||
|
||||
private readonly ItemPrefab spawnItemOnSelectedPrefab;
|
||||
private readonly ItemContainer containerToSpawnOnSelectedItem;
|
||||
|
||||
/// <summary>
|
||||
/// Item spawned by <see cref="spawnItemOnSelectedPrefab"/>
|
||||
/// </summary>
|
||||
private Item spawnedItemOnSelected = null;
|
||||
|
||||
public Controller(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -211,6 +309,18 @@ namespace Barotrauma.Items.Components
|
||||
Enum.TryParse(element.GetAttributeString("direction", "None"), out dir);
|
||||
LoadLimbPositions(element);
|
||||
IsActive = true;
|
||||
|
||||
containerToSpawnOnSelectedItem = item.GetComponent<ItemContainer>();
|
||||
|
||||
if (!SpawnItemOnSelected.IsEmpty && !ItemPrefab.Prefabs.TryGet(SpawnItemOnSelected, out spawnItemOnSelectedPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find item prefab \"{SpawnItemOnSelected}\"");
|
||||
}
|
||||
|
||||
if (containerToSpawnOnSelectedItem == null && !SpawnItemOnSelected.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error - Controller has a {nameof(SpawnItemOnSelected)} but no ItemContainer defined");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -236,58 +346,77 @@ namespace Barotrauma.Items.Components
|
||||
item.SendSignal(signal, "trigger_out");
|
||||
}
|
||||
|
||||
if (forceSelectNextFrame && user != null)
|
||||
if (forceSelectNextFrame && User != null)
|
||||
{
|
||||
user.SelectedItem = item;
|
||||
User.SelectedItem = item;
|
||||
}
|
||||
forceSelectNextFrame = false;
|
||||
|
||||
userCanInteractCheckTimer -= deltaTime;
|
||||
|
||||
if (user == null
|
||||
|| user.Removed
|
||||
|| !user.IsAnySelectedItem(item)
|
||||
|| (item.ParentInventory != null && !IsAttachedUser(user))
|
||||
|| (UsableIn == UseEnvironment.Water && !user.AnimController.InWater)
|
||||
|| (UsableIn == UseEnvironment.Air && user.AnimController.InWater)
|
||||
|| !CheckUserCanInteract())
|
||||
if (User == null
|
||||
|| User.Removed
|
||||
|| (((User.Stun <= 0f && !User.IsKnockedDownOrRagdolled && !User.LockHands) || !ForceUserToStayAttached) && (!User.IsAnySelectedItem(item) || !CheckUserCanInteract()))
|
||||
|| (item.ParentInventory != null && !IsAttachedUser(User))
|
||||
|| (UsableIn == UseEnvironment.Water && !User.AnimController.InWater)
|
||||
|| (UsableIn == UseEnvironment.Air && User.AnimController.InWater)
|
||||
|| !CheckSpawnItem()
|
||||
)
|
||||
{
|
||||
if (user != null)
|
||||
if (User != null)
|
||||
{
|
||||
CancelUsing(user);
|
||||
user = null;
|
||||
CancelUsing(User);
|
||||
User = null;
|
||||
}
|
||||
if (item.Connections == null || !IsToggle || string.IsNullOrEmpty(signal)) { IsActive = false; }
|
||||
return;
|
||||
}
|
||||
|
||||
if (ForceUserToStayAttached && Vector2.DistanceSquared(item.WorldPosition, user.WorldPosition) > 0.1f)
|
||||
if (ForceUserToStayAttached)
|
||||
{
|
||||
user.TeleportTo(item.WorldPosition);
|
||||
user.AnimController.Collider.ResetDynamics();
|
||||
foreach (var limb in user.AnimController.Limbs)
|
||||
teleportTransition = MathF.Min(teleportTransition + deltaTime * TeleportTransitionSpeed, 1f);
|
||||
|
||||
if (teleportTransition >= 1f)
|
||||
{
|
||||
if (limb.Removed || limb.IsSevered) { continue; }
|
||||
limb.body?.ResetDynamics();
|
||||
// Transition is complete, if someone was holding this character, force them to deselect
|
||||
// so they aren't holding the character that is now attached to the controller
|
||||
if (User.SelectedBy != null)
|
||||
{
|
||||
User.SelectedBy.SelectedCharacter = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (User == Character.Controlled
|
||||
|| teleportTransition < 1f
|
||||
|| Vector2.DistanceSquared(item.WorldPosition, User.WorldPosition) > 0.1f)
|
||||
{
|
||||
var targetPosition = Vector2.Lerp(teleportStartPosition, item.WorldPosition, teleportTransition);
|
||||
User.TeleportTo(targetPosition);
|
||||
User.AnimController.Collider.ResetDynamics();
|
||||
foreach (var limb in User.AnimController.Limbs)
|
||||
{
|
||||
if (limb.Removed || limb.IsSevered) { continue; }
|
||||
limb.body?.ResetDynamics();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user.AnimController.StartUsingItem();
|
||||
User.AnimController.StartUsingItem();
|
||||
|
||||
if (userPos != Vector2.Zero)
|
||||
{
|
||||
Vector2 diff = (item.WorldPosition + userPos) - user.WorldPosition;
|
||||
Vector2 diff = (item.WorldPosition + userPos) - User.WorldPosition;
|
||||
|
||||
if (user.AnimController.InWater)
|
||||
if (User.AnimController.InWater)
|
||||
{
|
||||
if (diff.LengthSquared() > 30.0f * 30.0f)
|
||||
{
|
||||
user.AnimController.TargetMovement = Vector2.Clamp(diff * 0.01f, -Vector2.One, Vector2.One);
|
||||
user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
User.AnimController.TargetMovement = Vector2.Clamp(diff * 0.01f, -Vector2.One, Vector2.One);
|
||||
User.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.AnimController.TargetMovement = Vector2.Zero;
|
||||
User.AnimController.TargetMovement = Vector2.Zero;
|
||||
UserInCorrectPosition = true;
|
||||
}
|
||||
}
|
||||
@@ -295,10 +424,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
// Secondary items (like ladders or chairs) will control the character position over primary items
|
||||
// Only control the character position if the character doesn't have another secondary item already controlling it
|
||||
if (!user.HasSelectedAnotherSecondaryItem(Item))
|
||||
if (!User.HasSelectedAnotherSecondaryItem(Item))
|
||||
{
|
||||
diff.Y = 0.0f;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled)
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && User != Character.Controlled)
|
||||
{
|
||||
if (Math.Abs(diff.X) > 20.0f)
|
||||
{
|
||||
@@ -308,48 +437,48 @@ namespace Barotrauma.Items.Components
|
||||
else if (Math.Abs(diff.X) > 0.1f)
|
||||
{
|
||||
//aim to keep the collider at the correct position once close enough
|
||||
user.AnimController.Collider.LinearVelocity = new Vector2(
|
||||
User.AnimController.Collider.LinearVelocity = new Vector2(
|
||||
diff.X * 0.1f,
|
||||
user.AnimController.Collider.LinearVelocity.Y);
|
||||
User.AnimController.Collider.LinearVelocity.Y);
|
||||
}
|
||||
}
|
||||
else if (Math.Abs(diff.X) > 10.0f)
|
||||
{
|
||||
user.AnimController.TargetMovement = Vector2.Normalize(diff);
|
||||
user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
User.AnimController.TargetMovement = Vector2.Normalize(diff);
|
||||
User.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
return;
|
||||
}
|
||||
user.AnimController.TargetMovement = Vector2.Zero;
|
||||
User.AnimController.TargetMovement = Vector2.Zero;
|
||||
}
|
||||
UserInCorrectPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, user);
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, User);
|
||||
|
||||
if (limbPositions.Count == 0) { return; }
|
||||
|
||||
user.AnimController.StartUsingItem();
|
||||
User.AnimController.StartUsingItem();
|
||||
|
||||
if (user.SelectedItem != null)
|
||||
if (User.SelectedItem != null)
|
||||
{
|
||||
user.AnimController.ResetPullJoints(l => l.IsLowerBody);
|
||||
User.AnimController.ResetPullJoints(l => l.IsLowerBody);
|
||||
}
|
||||
else
|
||||
{
|
||||
user.AnimController.ResetPullJoints();
|
||||
User.AnimController.ResetPullJoints();
|
||||
}
|
||||
|
||||
if (dir != 0) { user.AnimController.TargetDir = dir; }
|
||||
if (dir != 0) { User.AnimController.TargetDir = dir; }
|
||||
|
||||
foreach (LimbPos lb in limbPositions)
|
||||
{
|
||||
Limb limb = user.AnimController.GetLimb(lb.LimbType);
|
||||
Limb limb = User.AnimController.GetLimb(lb.LimbType);
|
||||
if (limb == null || !limb.body.Enabled) { continue; }
|
||||
// Don't move lower body limbs if there's another selected secondary item that should control them
|
||||
if (limb.IsLowerBody && user.HasSelectedAnotherSecondaryItem(Item)) { continue; }
|
||||
if (limb.IsLowerBody && User.HasSelectedAnotherSecondaryItem(Item)) { continue; }
|
||||
// Don't move hands if there's a selected primary item that should control them
|
||||
if (limb.IsArm && Item == user.SelectedSecondaryItem && user.SelectedItem != null) { continue; }
|
||||
if (limb.IsArm && Item == User.SelectedSecondaryItem && User.SelectedItem != null) { continue; }
|
||||
if (lb.AllowUsingLimb)
|
||||
{
|
||||
switch (lb.LimbType)
|
||||
@@ -357,12 +486,12 @@ namespace Barotrauma.Items.Components
|
||||
case LimbType.RightHand:
|
||||
case LimbType.RightForearm:
|
||||
case LimbType.RightArm:
|
||||
if (user.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) != null) { continue; }
|
||||
if (User.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) != null) { continue; }
|
||||
break;
|
||||
case LimbType.LeftHand:
|
||||
case LimbType.LeftForearm:
|
||||
case LimbType.LeftArm:
|
||||
if (user.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand) != null) { continue; }
|
||||
if (User.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand) != null) { continue; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -374,15 +503,77 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSpawnContainerFull()
|
||||
{
|
||||
if (spawnItemOnSelectedPrefab == null || containerToSpawnOnSelectedItem == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (containerToSpawnOnSelectedItem.Inventory.IsFull())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckSpawnItem()
|
||||
{
|
||||
if (spawnItemOnSelectedPrefab == null || containerToSpawnOnSelectedItem == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (containerToSpawnOnSelectedItem.Inventory.AllItems.Any(x => x.Prefab == spawnItemOnSelectedPrefab))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (spawnedItemOnSelected != null && !spawnedItemOnSelected.Removed)
|
||||
{
|
||||
if (spawnedItemOnSelected.ParentInventory != containerToSpawnOnSelectedItem.Inventory)
|
||||
{
|
||||
// Item was moved or dropped, force the user in this controller out
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsSpawnContainerFull())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(spawnItemOnSelectedPrefab, containerToSpawnOnSelectedItem.Inventory, onSpawned: spawnedItem =>
|
||||
{
|
||||
spawnedItemOnSelected = spawnedItem;
|
||||
|
||||
var linkedCharacterComponent = spawnedItem.GetComponent<LinkedControllerCharacterComponent>();
|
||||
if (linkedCharacterComponent != null)
|
||||
{
|
||||
linkedCharacterComponent.UpdateLinkedCharacter(User);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CheckUserCanInteract()
|
||||
{
|
||||
//optimization: CanInteractWith is relatively heavy (can involve visibility checks for example), let's not do it every frame
|
||||
if (user != null)
|
||||
if (User != null)
|
||||
{
|
||||
if (userCanInteractCheckTimer <= 0.0f)
|
||||
{
|
||||
userCanInteractCheckTimer = UserCanInteractCheckInterval;
|
||||
return user.CanInteractWith(item);
|
||||
return User.CanInteractWith(item);
|
||||
}
|
||||
}
|
||||
//we only do the actual check every UserCanInteractCheckInterval seconds
|
||||
@@ -394,13 +585,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Use(float deltaTime, Character activator = null)
|
||||
{
|
||||
if (activator != user)
|
||||
if (activator != User)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (user == null || user.Removed || !user.IsAnySelectedItem(item) || !user.CanInteractWith(item))
|
||||
if (User == null || User.Removed || !User.IsAnySelectedItem(item) || !User.CanInteractWith(item))
|
||||
{
|
||||
user = null;
|
||||
User = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -419,7 +610,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(output))
|
||||
{
|
||||
item.SendSignal(new Signal(output, sender: user), "trigger_out");
|
||||
item.SendSignal(new Signal(output, sender: User), "trigger_out");
|
||||
}
|
||||
|
||||
lastUsed = Timing.TotalTime;
|
||||
@@ -428,13 +619,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool SecondaryUse(float deltaTime, Character character = null)
|
||||
{
|
||||
if (user != character)
|
||||
if (User != character)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (user == null || character.Removed || !user.IsAnySelectedItem(item) || !character.CanInteractWith(item))
|
||||
if (User == null || character.Removed || !User.IsAnySelectedItem(item) || !character.CanInteractWith(item))
|
||||
{
|
||||
user = null;
|
||||
User = null;
|
||||
return false;
|
||||
}
|
||||
if (character == null)
|
||||
@@ -495,7 +686,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (IsOutOfPower()) { return null; }
|
||||
|
||||
item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: user), positionOut);
|
||||
item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: User), positionOut);
|
||||
|
||||
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -547,8 +738,20 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void CancelUsing(Character character)
|
||||
{
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
if (spawnedItemOnSelected != null)
|
||||
{
|
||||
Entity.Spawner.AddEntityToRemoveQueue(spawnedItemOnSelected);
|
||||
spawnedItemOnSelected = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (character == null || character.Removed) { return; }
|
||||
|
||||
// Wake character's colliders so they don't just float in the air when taken out of the controller
|
||||
character.AnimController.BodyInRest = false;
|
||||
|
||||
foreach (LimbPos lb in limbPositions)
|
||||
{
|
||||
Limb limb = character.AnimController.GetLimb(lb.LimbType);
|
||||
@@ -588,31 +791,80 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
//someone already using the item
|
||||
if (user != null && !user.Removed)
|
||||
// Someone already using the item
|
||||
if (User != null && !User.Removed)
|
||||
{
|
||||
if (user == activator)
|
||||
// Let the server handle the logic here
|
||||
if (GameMain.NetworkMember is { IsClient: true })
|
||||
{
|
||||
IsActive = false;
|
||||
CancelUsing(user);
|
||||
user = null;
|
||||
return false;
|
||||
}
|
||||
else if (user.IsBot && !activator.IsBot)
|
||||
|
||||
// Prevent user from kicking character out if they are holding another character
|
||||
if (AllowPuttingInOtherCharacters && CanPutSelectedCharacter(activator.SelectedCharacter))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (User == activator || SelectingKicksCharacterOut)
|
||||
{
|
||||
#if SERVER
|
||||
if (User != activator)
|
||||
{
|
||||
GameServer.Log($"{GameServer.CharacterLogName(activator)} removed {GameServer.CharacterLogName(User)} from {item.Name}",
|
||||
ServerLog.MessageType.Attack);
|
||||
}
|
||||
#endif
|
||||
|
||||
IsActive = false;
|
||||
CancelUsing(User);
|
||||
User = null;
|
||||
return false;
|
||||
}
|
||||
else if (User.IsBot && !activator.IsBot)
|
||||
{
|
||||
if (AllowSelectingWhenSelectedByBot)
|
||||
{
|
||||
CancelUsing(user);
|
||||
user = activator;
|
||||
CancelUsing(User);
|
||||
User = activator;
|
||||
IsActive = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return AllowSelectingWhenSelectedByOther;
|
||||
}
|
||||
else
|
||||
else if (AllowPuttingInOtherCharacters && CanPutSelectedCharacter(activator.SelectedCharacter))
|
||||
{
|
||||
user = activator;
|
||||
// Stun pets longer so they don't immediately get out of the controller
|
||||
if (activator.SelectedCharacter.IsPet)
|
||||
{
|
||||
activator.SelectedCharacter.SetStun(MathF.Max(activator.SelectedCharacter.Stun, 4f), isNetworkMessage: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Small amount of stun since non-ragdolled characters behave weirdly when syncing the periodic teleportation in multiplayer
|
||||
activator.SelectedCharacter.SetStun(MathF.Max(activator.SelectedCharacter.Stun, 1f), isNetworkMessage: true);
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
GameServer.Log($"{GameServer.CharacterLogName(activator)} forced {GameServer.CharacterLogName(activator.SelectedCharacter)} into {item.Name}",
|
||||
ServerLog.MessageType.Attack);
|
||||
#endif
|
||||
|
||||
User = activator.SelectedCharacter;
|
||||
User.SelectedItem = this.Item;
|
||||
IsActive = true;
|
||||
if (ForceUserToStayAttached && item.Container != null)
|
||||
{
|
||||
forceSelectNextFrame = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (CanBeSelectedByCharacters)
|
||||
{
|
||||
activator.DeselectCharacter();
|
||||
|
||||
User = activator;
|
||||
IsActive = true;
|
||||
if (ForceUserToStayAttached && item.Container != null)
|
||||
{
|
||||
@@ -621,15 +873,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
//allow the selection logic above to run when out of power, but allow sending signals
|
||||
//allow the selection logic above to run when out of power, but disallow sending signals
|
||||
if (IsOutOfPower()) { return false; }
|
||||
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
|
||||
if (!string.IsNullOrEmpty(output))
|
||||
{
|
||||
item.SendSignal(new Signal(output, sender: user), "signal_out");
|
||||
item.SendSignal(new Signal(output, sender: User), "signal_out");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -639,7 +888,7 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
public bool IsAttachedUser(Character character)
|
||||
{
|
||||
return character != null && character == user && ForceUserToStayAttached;
|
||||
return character != null && character == User && ForceUserToStayAttached;
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
@@ -668,12 +917,87 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
|
||||
{
|
||||
#if CLIENT
|
||||
UpdateMsg();
|
||||
#endif
|
||||
|
||||
bool canPutCharacter = AllowPuttingInOtherCharacters && CanPutSelectedCharacter(character.SelectedCharacter, addMessage);
|
||||
bool canKickCharacter = SelectingKicksCharacterOut && User != null && !User.Removed;
|
||||
bool canUseController = CanBeSelectedByCharacters;
|
||||
|
||||
// Prevent kicking a character out when the user is holding another character to put into the controller.
|
||||
// This avoids accidentally taking out a character (e.g. from a deconstructor).
|
||||
if (canPutCharacter && canKickCharacter)
|
||||
{
|
||||
#if CLIENT
|
||||
if (addMessage)
|
||||
{
|
||||
GUI.AddMessage(TextManager.Get("ItemMsgAlreadyHasCharacterFail"), Color.Red, playSound: false);
|
||||
SoundPlayer.PlayUISound(GUISoundType.PickItemFail);
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canKickCharacter && !canPutCharacter && !canUseController)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsSpawnContainerFull())
|
||||
{
|
||||
#if CLIENT
|
||||
if (addMessage)
|
||||
{
|
||||
GUI.AddMessage(TextManager.Get("ItemMsgNotEnoughSpaceCharacterFail"), Color.Red, playSound: false);
|
||||
SoundPlayer.PlayUISound(GUISoundType.PickItemFail);
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.HasRequiredItems(character, addMessage, msg);
|
||||
}
|
||||
|
||||
public override bool HasAccess(Character character)
|
||||
{
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
return base.HasAccess(character);
|
||||
}
|
||||
|
||||
private bool CanPutSelectedCharacter(Character character, bool showMessage = false)
|
||||
{
|
||||
if (character == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!character.IsContainable)
|
||||
{
|
||||
#if CLIENT
|
||||
if (showMessage)
|
||||
{
|
||||
GUI.AddMessage(TextManager.Get("ItemMsgPutCharacterFail"), Color.Red);
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (character.IsKnockedDownOrRagdolled) { return true; }
|
||||
if (character.LockHands) { return true; }
|
||||
if (character.IsPet)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
partial void HideHUDs(bool value);
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
|
||||
+131
-28
@@ -1,11 +1,13 @@
|
||||
using Barotrauma.Abilities;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using static OneOf.Types.TrueFalseOrNull;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -114,7 +116,19 @@ namespace Barotrauma.Items.Components
|
||||
float deconstructTime = 0.0f;
|
||||
foreach (Item targetItem in inputContainer.Inventory.AllItems)
|
||||
{
|
||||
deconstructTime += targetItem.Prefab.DeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier);
|
||||
float itemDeconstructTime = item.Submarine is { Info.Type: SubmarineType.Outpost }
|
||||
? targetItem.Prefab.DeconstructTimeInOutposts : targetItem.Prefab.DeconstructTime;
|
||||
float targetDeconstructTime = itemDeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier);
|
||||
|
||||
var linkedCharacter = targetItem.GetComponent<LinkedControllerCharacterComponent>();
|
||||
if (linkedCharacter != null)
|
||||
{
|
||||
targetDeconstructTime *= linkedCharacter.DeconstructTimeMultiplier;
|
||||
}
|
||||
|
||||
deconstructTime += targetDeconstructTime;
|
||||
|
||||
ApplyDeconstructionStatusEffects(targetItem, ActionType.OnDeconstructing, deltaTime);
|
||||
}
|
||||
|
||||
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
|
||||
@@ -143,8 +157,21 @@ namespace Barotrauma.Items.Components
|
||||
var targetItem = inputContainer.Inventory.LastOrDefault();
|
||||
if (targetItem == null) { return; }
|
||||
|
||||
ApplyDeconstructionStatusEffects(targetItem, ActionType.OnDeconstructing, deltaTime);
|
||||
|
||||
var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it => it.IsValidDeconstructor(item)).ToList();
|
||||
float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier) : 1.0f;
|
||||
|
||||
float itemDeconstructTime = item.Submarine is { Info.Type: SubmarineType.Outpost }
|
||||
? targetItem.Prefab.DeconstructTimeInOutposts : targetItem.Prefab.DeconstructTime;
|
||||
|
||||
float deconstructTime = !targetItem.Prefab.DeconstructItems.Any() || validDeconstructItems.Any()
|
||||
? itemDeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier) : 1.0f;
|
||||
|
||||
var linkedCharacter = targetItem.GetComponent<LinkedControllerCharacterComponent>();
|
||||
if (linkedCharacter != null)
|
||||
{
|
||||
deconstructTime *= linkedCharacter.DeconstructTimeMultiplier;
|
||||
}
|
||||
|
||||
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
|
||||
if (progressTimer > deconstructTime)
|
||||
@@ -183,6 +210,8 @@ namespace Barotrauma.Items.Components
|
||||
amountMultiplier = (int)itemCreationMultiplier.Value;
|
||||
}
|
||||
|
||||
ApplyDeconstructionStatusEffects(targetItem, ActionType.OnDeconstructed, 1f);
|
||||
|
||||
if (targetItem.Prefab.RandomDeconstructionOutput)
|
||||
{
|
||||
int amount = targetItem.Prefab.RandomDeconstructionOutputAmount;
|
||||
@@ -297,30 +326,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
humanAi.HandleRelocation(spawnedItem);
|
||||
}
|
||||
for (int i = 0; i < outputContainer.Capacity; i++)
|
||||
{
|
||||
var containedItem = outputContainer.Inventory.GetItemAt(i);
|
||||
bool combined = false;
|
||||
if (containedItem?.OwnInventory != null)
|
||||
{
|
||||
foreach (Item subItem in containedItem.ContainedItems.ToList())
|
||||
{
|
||||
if (subItem.Combine(spawnedItem, null))
|
||||
{
|
||||
combined = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!combined)
|
||||
{
|
||||
if (containedItem?.Combine(spawnedItem, null) ?? false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
PutItemsToLinkedContainer();
|
||||
|
||||
TryMoveItemToOutputContainers(spawnedItem);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -332,8 +339,9 @@ namespace Barotrauma.Items.Components
|
||||
GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + targetItem.Prefab.Identifier);
|
||||
}
|
||||
|
||||
bool? result = GameMain.LuaCs.Hook.Call<bool?>("item.deconstructed", targetItem, this, user, allowRemove);
|
||||
if (result == true) { return; }
|
||||
bool? should = null;
|
||||
LuaCsSetup.Instance.EventService.PublishEvent<IEventItemDeconstructed>(x => should = x.OnItemDeconstructed(targetItem, this, user, allowRemove) ?? should);
|
||||
if (should == true) { return; }
|
||||
|
||||
if (targetItem.AllowDeconstruct && allowRemove)
|
||||
{
|
||||
@@ -350,6 +358,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputContainer.Inventory.RemoveItem(targetItem);
|
||||
Entity.Spawner.AddItemToRemoveQueue(targetItem);
|
||||
MoveInputQueue();
|
||||
@@ -384,6 +393,34 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private void TryMoveItemToOutputContainers(Item spawnedItem)
|
||||
{
|
||||
for (int i = 0; i < outputContainer.Capacity; i++)
|
||||
{
|
||||
var containedItem = outputContainer.Inventory.GetItemAt(i);
|
||||
bool combined = false;
|
||||
if (containedItem?.OwnInventory != null)
|
||||
{
|
||||
foreach (Item subItem in containedItem.ContainedItems.ToList())
|
||||
{
|
||||
if (subItem.Combine(spawnedItem, null))
|
||||
{
|
||||
combined = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!combined)
|
||||
{
|
||||
if (containedItem?.Combine(spawnedItem, null) ?? false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
PutItemsToLinkedContainer();
|
||||
}
|
||||
|
||||
private void PutItemsToLinkedContainer()
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
@@ -402,6 +439,72 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyDeconstructionStatusEffects(Item targetItem, ActionType type, float deltaTime)
|
||||
{
|
||||
var linkedCharacterComponent = targetItem.GetComponent<LinkedControllerCharacterComponent>();
|
||||
Character character = null;
|
||||
if (linkedCharacterComponent is { Character.Removed: false })
|
||||
{
|
||||
character = linkedCharacterComponent.Character;
|
||||
}
|
||||
|
||||
Limb limb = character?.AnimController.Limbs.GetRandomUnsynced();
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
item.GetStatusEffectsOfType(type).ForEach(statusEffect => statusEffect.SetUser(user));
|
||||
targetItem.GetStatusEffectsOfType(type).ForEach(statusEffect => statusEffect.SetUser(user));
|
||||
}
|
||||
|
||||
// Apply OnDeconstruct/OnDeconstructing to the Deconstructor/item being deconstructed
|
||||
item.ApplyStatusEffects(type, deltaTime, character, limb, useTarget: targetItem);
|
||||
targetItem.ApplyStatusEffects(type, deltaTime, character, limb);
|
||||
|
||||
if (character != null)
|
||||
{
|
||||
if (type == ActionType.OnDeconstructed)
|
||||
{
|
||||
// Move whatever was on the character inventory to free up space for status effects that spawn items
|
||||
MoveItemsFromCharacterToOutput();
|
||||
}
|
||||
|
||||
character.ApplyStatusEffects(type, deltaTime);
|
||||
|
||||
if (type == ActionType.OnDeconstructed)
|
||||
{
|
||||
// This needs to run next frame because the status effect might enqueue items to be spawned next frame
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
if (character.Removed) { return; }
|
||||
|
||||
// Move items again since the status effect could have spawned additional items in the character inventory
|
||||
MoveItemsFromCharacterToOutput();
|
||||
|
||||
Entity.Spawner?.AddEntityToRemoveQueue(character);
|
||||
}, 0.1f);
|
||||
}
|
||||
|
||||
void MoveItemsFromCharacterToOutput()
|
||||
{
|
||||
if (character.Inventory != null)
|
||||
{
|
||||
foreach (var item in character.Inventory.AllItemsMod)
|
||||
{
|
||||
if (item.Removed) { continue; }
|
||||
if (!item.IsPlayerTeamInteractable) { continue; }
|
||||
|
||||
if (!outputContainer.Inventory.TryPutItem(item, user: null))
|
||||
{
|
||||
item.Drop(dropper: null);
|
||||
}
|
||||
|
||||
TryMoveItemToOutputContainers(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move items towards the last slot in the inventory if there's free slots
|
||||
/// </summary>
|
||||
|
||||
@@ -388,6 +388,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Attack.DamageMultiplier = damageMultiplier;
|
||||
}
|
||||
foreach (var statusEffect in Item.GetStatusEffectsOfType(ActionType.OnImpact))
|
||||
{
|
||||
foreach (var explosion in statusEffect.Explosions)
|
||||
{
|
||||
explosion.Attack.DamageMultiplier = damageMultiplier;
|
||||
}
|
||||
}
|
||||
// Set user for hitscan projectiles to work properly.
|
||||
User = user;
|
||||
// Need to set null for non-characterusable items.
|
||||
@@ -460,6 +467,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
initialRotation -= MathHelper.Pi;
|
||||
}
|
||||
Submarine initialSubmarine = item.Submarine;
|
||||
for (int i = 0; i < HitScanCount; i++)
|
||||
{
|
||||
float launchAngle;
|
||||
@@ -476,6 +484,8 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle));
|
||||
Vector2 prevSimpos = item.SimPosition;
|
||||
item.body.SetTransformIgnoreContacts(item.body.SimPosition, launchAngle);
|
||||
//when launching multiple projectiles, ensure each raycast starts from the same sub
|
||||
item.Submarine = initialSubmarine;
|
||||
if (Hitscan)
|
||||
{
|
||||
DoHitscan(launchDir);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Barotrauma.Items.Components
|
||||
FiringRateMultiplier
|
||||
}
|
||||
|
||||
private readonly Dictionary<StatType, float> statValues = new Dictionary<StatType, float>();
|
||||
public readonly Dictionary<StatType, float> statValues = new Dictionary<StatType, float>();
|
||||
|
||||
private int qualityLevel;
|
||||
|
||||
|
||||
@@ -448,9 +448,10 @@ namespace Barotrauma.Items.Components
|
||||
UpdateProjSpecific(deltaTime);
|
||||
IsTinkering = false;
|
||||
|
||||
if (prevSentConditionValue != (int)item.ConditionPercentage || conditionSignal == null)
|
||||
int condition = (int)(item.Condition / (item.MaxCondition / item.MaxRepairConditionMultiplier) * 100f);
|
||||
if (prevSentConditionValue != condition || conditionSignal == null)
|
||||
{
|
||||
prevSentConditionValue = (int)item.ConditionPercentage;
|
||||
prevSentConditionValue = condition;
|
||||
conditionSignal = prevSentConditionValue.ToString();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ namespace Barotrauma.Items.Components
|
||||
private init => _displayName = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display name ignoring <see cref="DisplayNameOverride"/>
|
||||
/// </summary>
|
||||
public LocalizedString DefaultDisplayName => _displayName;
|
||||
|
||||
public LocalizedString DisplayNameOverride;
|
||||
|
||||
private readonly HashSet<Wire> wires;
|
||||
@@ -350,15 +355,11 @@ namespace Barotrauma.Items.Components
|
||||
wire.RegisterSignal(signal, source: this);
|
||||
#endif
|
||||
SendSignalIntoConnection(signal, recipient);
|
||||
GameMain.LuaCs.Hook.Call("signalReceived", signal, recipient);
|
||||
GameMain.LuaCs.Hook.Call("signalReceived." + recipient.item.Prefab.Identifier, signal, recipient);
|
||||
}
|
||||
|
||||
foreach (CircuitBoxConnection connection in CircuitBoxConnections)
|
||||
{
|
||||
connection.ReceiveSignal(signal);
|
||||
GameMain.LuaCs.Hook.Call("signalReceived", signal, connection.Connection);
|
||||
GameMain.LuaCs.Hook.Call("signalReceived." + connection.Connection.Item.Prefab.Identifier, signal, connection);
|
||||
}
|
||||
enumeratingWires = false;
|
||||
foreach (var removedWire in removedWires)
|
||||
|
||||
@@ -296,6 +296,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
#endif
|
||||
CheckIfNeedsUpdate();
|
||||
SetLightSourceTransformProjSpecific();
|
||||
}
|
||||
|
||||
public void CheckIfNeedsUpdate()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using static Barotrauma.CharacterHealth;
|
||||
using static Barotrauma.MedicalClinic;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -228,8 +231,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void TransmitSignal(Signal signal, bool sentFromChat)
|
||||
{
|
||||
var should = GameMain.LuaCs.Hook.Call<bool?>("wifiSignalTransmitted", this, signal, sentFromChat);
|
||||
|
||||
bool? should = null;
|
||||
LuaCsSetup.Instance.EventService.PublishEvent<IEventWifiSignalTransmitted>(x => should = x.OnWifiSignalTransmitted(this, signal, sentFromChat) ?? should);
|
||||
if (should != null && should.Value) { return; }
|
||||
|
||||
bool chatMsgSent = false;
|
||||
|
||||
@@ -42,6 +42,10 @@ namespace Barotrauma.Items.Components
|
||||
private float currentChargeTime;
|
||||
private bool tryingToCharge;
|
||||
|
||||
private const float LineOfSightCheckInterval = 0.5f;
|
||||
private (Body WorldTarget, Body TransformedTarget, double Time) lastLineOfSightCheck;
|
||||
private (Character Target, bool CanSee, double Time) lastCanSeeTargetCheck;
|
||||
|
||||
private enum ChargingState
|
||||
{
|
||||
Inactive,
|
||||
@@ -1088,6 +1092,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub == Item.Submarine) { continue; }
|
||||
if (sub.IsRespawnShuttle) { continue; }
|
||||
if (item.Submarine != null)
|
||||
{
|
||||
if (Character.IsOnFriendlyTeam(item.Submarine.TeamID, sub.TeamID)) { continue; }
|
||||
@@ -1164,15 +1169,28 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition);
|
||||
Vector2 end = ConvertUnits.ToSimUnits(target.WorldPosition);
|
||||
|
||||
bool doLineOfSightCheck = lastLineOfSightCheck.Time < Timing.TotalTimeUnpaused - LineOfSightCheckInterval;
|
||||
if (doLineOfSightCheck)
|
||||
{
|
||||
lastLineOfSightCheck.WorldTarget = CheckLineOfSight(start, end);
|
||||
lastLineOfSightCheck.Time = Timing.TotalTime;
|
||||
}
|
||||
|
||||
// Check that there's not other entities that shouldn't be targeted (like a friendly sub) between us and the target.
|
||||
Body worldTarget = CheckLineOfSight(start, end);
|
||||
Body worldTarget = lastLineOfSightCheck.WorldTarget;
|
||||
bool shoot;
|
||||
if (target.Submarine != null)
|
||||
{
|
||||
start -= target.Submarine.SimPosition;
|
||||
end -= target.Submarine.SimPosition;
|
||||
Body transformedTarget = CheckLineOfSight(start, end);
|
||||
shoot = CanShoot(transformedTarget, user: null, friendlyTag, TargetSubmarines) && (worldTarget == null || CanShoot(worldTarget, user: null, friendlyTag, TargetSubmarines));
|
||||
if (doLineOfSightCheck)
|
||||
{
|
||||
start -= target.Submarine.SimPosition;
|
||||
end -= target.Submarine.SimPosition;
|
||||
lastLineOfSightCheck.TransformedTarget = CheckLineOfSight(start, end);
|
||||
}
|
||||
shoot =
|
||||
(worldTarget == null || CanShoot(worldTarget, user: null, friendlyTag, TargetSubmarines)) &&
|
||||
CanShoot(lastLineOfSightCheck.TransformedTarget, user: null, friendlyTag, TargetSubmarines);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1437,8 +1455,20 @@ namespace Barotrauma.Items.Components
|
||||
// Adjust the target character position (limb or submarine)
|
||||
if (currentTarget is Character targetCharacter)
|
||||
{
|
||||
bool enemyInAnotherSub = targetCharacter.Submarine != null && targetCharacter.CurrentHull != null && targetCharacter.Submarine != item.Submarine;
|
||||
bool canSeeTarget = true;
|
||||
if (enemyInAnotherSub)
|
||||
{
|
||||
if (lastCanSeeTargetCheck.Time < Timing.TotalTime - LineOfSightCheckInterval ||
|
||||
targetCharacter != lastCanSeeTargetCheck.Target)
|
||||
{
|
||||
canSeeTarget = targetCharacter.CanSeeTarget(Item);
|
||||
lastCanSeeTargetCheck = (targetCharacter, canSeeTarget, Timing.TotalTime);
|
||||
}
|
||||
}
|
||||
|
||||
//if the enemy is inside another sub, aim at the room they're in to make it less obvious that the enemy "knows" exactly where the target is
|
||||
if (targetCharacter.Submarine != null && targetCharacter.CurrentHull != null && targetCharacter.Submarine != item.Submarine && !targetCharacter.CanSeeTarget(Item))
|
||||
if (enemyInAnotherSub && !canSeeTarget)
|
||||
{
|
||||
targetPos = targetCharacter.CurrentHull.WorldPosition;
|
||||
if (closestDistance > maxDistance * maxDistance)
|
||||
|
||||
Reference in New Issue
Block a user