Unstable 0.1500.5.0 (almost forgor edition 💀)
This commit is contained in:
@@ -79,6 +79,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Use the hand rotation instead of torso rotation for the item hold angle. Enable this if you want the item just to follow with the arm when not aiming instead of forcing the arm to a hold pose.")]
|
||||
public bool UseHandRotationForHoldAngle
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Can the item be attached to walls.")]
|
||||
public bool Attachable
|
||||
{
|
||||
@@ -93,6 +100,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Can the item only be attached in limited amount? Uses permanent stat values to check for legibility.")]
|
||||
public bool LimitedAttachable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Should the item be attached to a wall by default when it's placed in the submarine editor.")]
|
||||
public bool AttachedByDefault
|
||||
{
|
||||
@@ -154,7 +168,8 @@ namespace Barotrauma.Items.Components
|
||||
BodyType = BodyType.Dynamic,
|
||||
CollidesWith = Physics.CollisionCharacter,
|
||||
CollisionCategories = Physics.CollisionItemBlocking,
|
||||
Enabled = false
|
||||
Enabled = false,
|
||||
UserData = "Holdable.Pusher"
|
||||
};
|
||||
Pusher.FarseerBody.OnCollision += OnPusherCollision;
|
||||
Pusher.FarseerBody.FixedRotation = false;
|
||||
@@ -205,7 +220,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
characterUsable = element.GetAttributeBool("characterusable", true);
|
||||
}
|
||||
|
||||
@@ -247,6 +261,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void Drop(bool dropConnectedWires, Character dropper)
|
||||
{
|
||||
GetRope()?.Snap();
|
||||
if (dropConnectedWires)
|
||||
{
|
||||
DropConnectedWires(dropper);
|
||||
@@ -558,6 +573,15 @@ namespace Barotrauma.Items.Components
|
||||
PickKey = InputType.Select;
|
||||
}
|
||||
|
||||
public override void ParseMsg()
|
||||
{
|
||||
base.ParseMsg();
|
||||
if (Attachable)
|
||||
{
|
||||
prevMsg = DisplayMsg;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (!attachable || item.body == null) { return character == null || (character.IsKeyDown(InputType.Aim) && characterUsable); }
|
||||
@@ -567,6 +591,25 @@ namespace Barotrauma.Items.Components
|
||||
if (!character.IsKeyDown(InputType.Aim)) { return false; }
|
||||
if (!CanBeAttached(character)) { return false; }
|
||||
|
||||
if (LimitedAttachable)
|
||||
{
|
||||
if (character?.Info == null)
|
||||
{
|
||||
DebugConsole.AddWarning("Character without CharacterInfo attempting to attach a limited attachable item!");
|
||||
return false;
|
||||
}
|
||||
int maxAttachableCount = (int)character.Info.GetSavedStatValue(StatTypes.MaxAttachableCount, item.Prefab.Identifier);
|
||||
int currentlyAttachedCount = Item.ItemList.Count(
|
||||
i => i.Submarine == item.Submarine && i.GetComponent<Holdable>() is Holdable holdable && holdable.Attached && i.Prefab.Identifier == item.prefab.Identifier);
|
||||
if (currentlyAttachedCount >= maxAttachableCount)
|
||||
{
|
||||
#if CLIENT
|
||||
GUI.AddMessage($"{TextManager.Get("itemmsgtotalnumberlimited")} ({currentlyAttachedCount}/{maxAttachableCount})", Color.Red);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
if (character != Character.Controlled)
|
||||
@@ -666,6 +709,20 @@ namespace Barotrauma.Items.Components
|
||||
Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
public Rope GetRope()
|
||||
{
|
||||
var rangedWeapon = Item.GetComponent<RangedWeapon>();
|
||||
if (rangedWeapon != null)
|
||||
{
|
||||
var lastProjectile = rangedWeapon.LastProjectile;
|
||||
if (lastProjectile != null)
|
||||
{
|
||||
return lastProjectile.Item.GetComponent<Rope>();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (attachTargetCell != null)
|
||||
@@ -720,9 +777,18 @@ namespace Barotrauma.Items.Components
|
||||
scaledHandlePos[1] = handlePos[1] * item.Scale;
|
||||
bool aim = picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero && picker.CanAim;
|
||||
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swing, aimPos + swing, aim, holdAngle);
|
||||
if (!aim)
|
||||
{
|
||||
var rope = GetRope();
|
||||
if (rope != null && rope.SnapWhenNotAimed)
|
||||
{
|
||||
rope.Snap();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GetRope()?.Snap();
|
||||
Limb equipLimb = null;
|
||||
if (picker.Inventory.IsInLimbSlot(item, InvSlotType.Headset) || picker.Inventory.IsInLimbSlot(item, InvSlotType.Head))
|
||||
{
|
||||
@@ -792,7 +858,7 @@ namespace Barotrauma.Items.Components
|
||||
attachTargetCell = null;
|
||||
if (Pusher != null)
|
||||
{
|
||||
GameMain.World.Remove(Pusher.FarseerBody);
|
||||
Pusher.Remove();
|
||||
Pusher = null;
|
||||
}
|
||||
body = null;
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void Initialize(CharacterInfo info)
|
||||
{
|
||||
if (info == null) return;
|
||||
if (info == null) { return; }
|
||||
|
||||
if (info.Job?.Prefab != null)
|
||||
{
|
||||
@@ -42,20 +42,22 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
var head = info.Head;
|
||||
|
||||
if (info != null && head != null)
|
||||
{
|
||||
item.AddTag("gender:" + head.gender.ToString().ToLowerInvariant());
|
||||
item.AddTag("race:" + head.race.ToString());
|
||||
item.AddTag("headspriteid:" + info.HeadSpriteId.ToString());
|
||||
item.AddTag("hairindex:" + head.HairIndex);
|
||||
item.AddTag("beardindex:" + head.BeardIndex);
|
||||
item.AddTag("moustacheindex:" + head.MoustacheIndex);
|
||||
item.AddTag("faceattachmentindex:" + head.FaceAttachmentIndex);
|
||||
if (head == null) { return; }
|
||||
|
||||
if (info.HasGenders) { item.AddTag($"gender:{head.gender.ToString().ToLowerInvariant()}"); }
|
||||
if (info.HasRaces) { item.AddTag($"race:{head.race}"); }
|
||||
item.AddTag($"headspriteid:{info.HeadSpriteId}");
|
||||
item.AddTag($"hairindex:{head.HairIndex}");
|
||||
item.AddTag($"beardindex:{head.BeardIndex}");
|
||||
item.AddTag($"moustacheindex:{head.MoustacheIndex}");
|
||||
item.AddTag($"faceattachmentindex:{head.FaceAttachmentIndex}");
|
||||
item.AddTag($"haircolor:{head.HairColor.ToStringHex()}");
|
||||
item.AddTag($"facialhaircolor:{head.FacialHairColor.ToStringHex()}");
|
||||
item.AddTag($"skincolor:{head.SkinColor.ToStringHex()}");
|
||||
|
||||
if (head.SheetIndex != null)
|
||||
{
|
||||
item.AddTag("sheetindex:" + head.SheetIndex.Value.X + ";" + head.SheetIndex.Value.Y);
|
||||
}
|
||||
if (head.SheetIndex != null)
|
||||
{
|
||||
item.AddTag($"sheetindex:{head.SheetIndex.Value.X};{head.SheetIndex.Value.Y}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,16 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(true, false)]
|
||||
public bool Swing { get; set; }
|
||||
|
||||
[Editable, Serialize("2.0, 0.0", false)]
|
||||
public Vector2 SwingPos { get; set; }
|
||||
|
||||
[Editable, Serialize("3.0, -1.0", false)]
|
||||
public Vector2 SwingForce { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines items that boost the weapon functionality, like battery cell for stun batons.
|
||||
/// </summary>
|
||||
@@ -67,8 +77,6 @@ namespace Barotrauma.Items.Components
|
||||
};
|
||||
}
|
||||
item.IsShootable = true;
|
||||
// TODO: should define this in xml if we have melee weapons that don't require aim to use
|
||||
item.RequireAimToUse = true;
|
||||
PreferredContainedItems = element.GetAttributeStringArray("preferredcontaineditems", new string[0], convertToLowerInvariant: true);
|
||||
}
|
||||
|
||||
@@ -82,6 +90,9 @@ namespace Barotrauma.Items.Components
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null || reloadTimer > 0.0f) { return false; }
|
||||
#if CLIENT
|
||||
if (!Item.RequireAimToUse && character.IsPlayer && (GUI.MouseOn != null || character.Inventory.visualSlots.Any(s => s.MouseOn()) || Inventory.DraggingItems.Any())) { return false; }
|
||||
#endif
|
||||
if (Item.RequireAimToUse && !character.IsKeyDown(InputType.Aim) || hitting) { return false; }
|
||||
|
||||
//don't allow hitting if the character is already hitting with another weapon
|
||||
@@ -94,7 +105,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
SetUser(character);
|
||||
|
||||
if (hitPos < MathHelper.PiOver4) { return false; }
|
||||
if (Item.RequireAimToUse && hitPos < MathHelper.PiOver4) { return false; }
|
||||
|
||||
ActivateNearbySleepingCharacters();
|
||||
reloadTimer = reload / (1 + character.GetStatValue(StatTypes.MeleeAttackSpeed));
|
||||
@@ -105,20 +116,28 @@ namespace Barotrauma.Items.Components
|
||||
item.body.FarseerBody.IsBullet = true;
|
||||
item.body.PhysEnabled = true;
|
||||
|
||||
if (!character.AnimController.InWater)
|
||||
if (Swing && !character.AnimController.InWater)
|
||||
{
|
||||
foreach (Limb l in character.AnimController.Limbs)
|
||||
{
|
||||
if (l.IsSevered) { continue; }
|
||||
if (l.type == LimbType.LeftFoot || l.type == LimbType.LeftThigh || l.type == LimbType.LeftLeg) { continue; }
|
||||
if (l.type == LimbType.Head || l.type == LimbType.Torso)
|
||||
Vector2 force = new Vector2(character.AnimController.Dir * SwingForce.X, SwingForce.Y) * l.Mass;
|
||||
switch (l.type)
|
||||
{
|
||||
l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 7.0f, -4.0f));
|
||||
case LimbType.Torso:
|
||||
force *= 2;
|
||||
break;
|
||||
case LimbType.Legs:
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.LeftThigh:
|
||||
case LimbType.LeftLeg:
|
||||
case LimbType.RightFoot:
|
||||
case LimbType.RightThigh:
|
||||
case LimbType.RightLeg:
|
||||
force = Vector2.Zero;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 5.0f, -2.0f));
|
||||
}
|
||||
l.body.ApplyLinearImpulse(force);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,9 +173,16 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!item.body.Enabled) { impactQueue.Clear(); return; }
|
||||
if (picker == null && !picker.HeldItems.Contains(item)) { impactQueue.Clear(); IsActive = false; }
|
||||
|
||||
if (!item.body.Enabled)
|
||||
{
|
||||
impactQueue.Clear();
|
||||
return;
|
||||
}
|
||||
if (picker == null && !picker.HeldItems.Contains(item))
|
||||
{
|
||||
impactQueue.Clear();
|
||||
IsActive = false;
|
||||
}
|
||||
while (impactQueue.Count > 0)
|
||||
{
|
||||
var impact = impactQueue.Dequeue();
|
||||
@@ -164,37 +190,47 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
//in case handling the impact does something to the picker
|
||||
if (picker == null) { return; }
|
||||
|
||||
reloadTimer -= deltaTime;
|
||||
if (reloadTimer < 0) { reloadTimer = 0; }
|
||||
|
||||
if (!picker.IsKeyDown(InputType.Aim) && !hitting) { hitPos = 0.0f; }
|
||||
|
||||
if (reloadTimer < 0)
|
||||
{
|
||||
reloadTimer = 0;
|
||||
}
|
||||
if (!picker.IsKeyDown(InputType.Aim) && !hitting)
|
||||
{
|
||||
hitPos = 0.0f;
|
||||
}
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir) { item.FlipX(relativeToSub: false); }
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir)
|
||||
{
|
||||
item.FlipX(relativeToSub: false);
|
||||
}
|
||||
AnimController ac = picker.AnimController;
|
||||
|
||||
//TODO: refactor the hitting logic (get rid of the magic numbers, make it possible to use different kinds of animations for different items)
|
||||
if (!hitting)
|
||||
{
|
||||
bool aim = picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim;
|
||||
bool aim = item.RequireAimToUse && picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim;
|
||||
if (aim)
|
||||
{
|
||||
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 5f, MathHelper.PiOver4));
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, hitPos, holdAngle + hitPos, aimingMelee: true);
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
hitPos = 0;
|
||||
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, false, holdAngle);
|
||||
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: We might want to make this configurable
|
||||
hitPos = MathUtils.WrapAnglePi(hitPos - deltaTime * 15f);
|
||||
ac.HoldItem(deltaTime, item, handlePos, new Vector2(2, 0), Vector2.Zero, false, hitPos, holdAngle + hitPos); // aimPos not used -> zero (new Vector2(-0.3f, 0.2f)), holdPos new Vector2(0.6f, -0.1f)
|
||||
if (Swing)
|
||||
{
|
||||
ac.HoldItem(deltaTime, item, handlePos, SwingPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle);
|
||||
}
|
||||
if (hitPos < -MathHelper.PiOver2)
|
||||
{
|
||||
RestoreCollision();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Barotrauma.Abilities;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Collision;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
@@ -80,6 +79,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Projectile LastProjectile { get; private set; }
|
||||
|
||||
private float currentChargeTime;
|
||||
private bool tryingToCharge;
|
||||
|
||||
@@ -196,6 +198,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
|
||||
float rotation = (Item.body.Dir == 1.0f) ? Item.body.Rotation : Item.body.Rotation - MathHelper.Pi;
|
||||
float spread = GetSpread(character) * Rand.Range(-0.5f, 0.5f);
|
||||
LastProjectile?.Item.GetComponent<Rope>()?.Snap();
|
||||
float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.AttackMultiplier);
|
||||
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false, damageMultiplier);
|
||||
projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item);
|
||||
@@ -206,6 +209,7 @@ namespace Barotrauma.Items.Components
|
||||
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
|
||||
Item.RemoveContained(projectile.Item);
|
||||
}
|
||||
LastProjectile = projectile;
|
||||
}
|
||||
|
||||
LaunchProjSpecific();
|
||||
|
||||
@@ -123,18 +123,18 @@ namespace Barotrauma.Items.Components
|
||||
if (aim)
|
||||
{
|
||||
throwPos = MathUtils.WrapAnglePi(System.Math.Min(throwPos + deltaTime * 5.0f, MathHelper.PiOver2));
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, throwPos);
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
throwPos = 0;
|
||||
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, false, holdAngle);
|
||||
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throwPos = MathUtils.WrapAnglePi(throwPos - deltaTime * 15.0f);
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, throwPos);
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwPos);
|
||||
|
||||
if (throwPos < 0)
|
||||
{
|
||||
@@ -169,8 +169,8 @@ namespace Barotrauma.Items.Components
|
||||
item.body.FarseerBody.IsBullet = true;
|
||||
midAir = true;
|
||||
|
||||
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
ac.GetLimb(LimbType.Torso).body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
ac.GetLimb(LimbType.Head)?.body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
ac.GetLimb(LimbType.Torso)?.body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
|
||||
Limb rightHand = ac.GetLimb(LimbType.RightHand);
|
||||
item.body.AngularVelocity = rightHand.body.AngularVelocity;
|
||||
|
||||
@@ -767,7 +767,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null)
|
||||
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float applyOnUserFraction = 0.0f)
|
||||
{
|
||||
if (statusEffectLists == null) { return; }
|
||||
|
||||
@@ -779,7 +779,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (broken && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { continue; }
|
||||
if (user != null) { effect.SetUser(user); }
|
||||
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, false, false, worldPosition);
|
||||
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition);
|
||||
if (user != null && applyOnUserFraction > 0.0f && effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
effect.AfflictionMultiplier = applyOnUserFraction;
|
||||
item.ApplyStatusEffect(effect, type, deltaTime, user, targetLimb == null ? null : user.AnimController.GetLimb(targetLimb.type), useTarget, false, false, worldPosition);
|
||||
effect.AfflictionMultiplier = 1.0f;
|
||||
}
|
||||
reducesCondition |= effect.ReducesItemCondition();
|
||||
}
|
||||
//if any of the effects reduce the item's condition, set the user for OnBroken effects as well
|
||||
@@ -959,7 +965,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void ParseMsg()
|
||||
public virtual void ParseMsg()
|
||||
{
|
||||
string msg = TextManager.Get(Msg, true);
|
||||
if (msg != null)
|
||||
|
||||
@@ -220,6 +220,12 @@ namespace Barotrauma.Items.Components
|
||||
if (targetItem == otherItem) { continue; }
|
||||
if (deconstructProduct.RequiredOtherItem.Any(r => otherItem.HasTag(r) || r.Equals(otherItem.Prefab.Identifier, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
user.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
|
||||
foreach (Character character in Character.GetFriendlyCrew(user))
|
||||
{
|
||||
character.CheckTalents(AbilityEffectType.OnCrewGeneticMaterialCombinedOrRefined);
|
||||
}
|
||||
|
||||
var geneticMaterial1 = targetItem.GetComponent<GeneticMaterial>();
|
||||
var geneticMaterial2 = otherItem.GetComponent<GeneticMaterial>();
|
||||
if (geneticMaterial1 != null && geneticMaterial2 != null)
|
||||
|
||||
@@ -344,22 +344,27 @@ namespace Barotrauma.Items.Components
|
||||
var tempUser = user;
|
||||
for (int i = 0; i < (int)fabricationValueItem.Value; i++)
|
||||
{
|
||||
float outCondition = fabricatedItem.OutCondition;
|
||||
if (i < amountFittingContainer)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
|
||||
onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * outCondition,
|
||||
onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
onItemSpawned(spawnedItem, tempUser);
|
||||
spawnedItem.Quality = quality;
|
||||
//reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers
|
||||
spawnedItem.Condition = spawnedItem.MaxCondition * outCondition;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
|
||||
onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * outCondition,
|
||||
onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
onItemSpawned(spawnedItem, tempUser);
|
||||
spawnedItem.Quality = quality;
|
||||
//reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers
|
||||
spawnedItem.Condition = spawnedItem.MaxCondition * outCondition;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Can the item stick even to deflective targets.")]
|
||||
public bool StickToDeflective
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Hitscan projectiles cast a ray forwards and immediately hit whatever the ray hits. "+
|
||||
"It is recommended to use hitscans for very fast-moving projectiles such as bullets, because using extremely fast launch velocities may cause physics glitches.")]
|
||||
public bool Hitscan
|
||||
@@ -231,7 +238,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Item.body.ResetDynamics();
|
||||
Item.SetTransform(simPosition, rotation);
|
||||
Attack.DamageMultiplier = damageMultiplier;
|
||||
if (Attack != null)
|
||||
{
|
||||
Attack.DamageMultiplier = damageMultiplier;
|
||||
}
|
||||
// Set user for hitscan projectiles to work properly.
|
||||
User = user;
|
||||
// Need to set null for non-characterusable items.
|
||||
@@ -356,6 +366,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
float rotation = item.body.Rotation;
|
||||
Vector2 simPositon = item.SimPosition;
|
||||
Vector2 rayStartWorld = item.WorldPosition;
|
||||
item.Drop(null);
|
||||
|
||||
item.body.Enabled = true;
|
||||
@@ -367,7 +378,6 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 rayStart = simPositon;
|
||||
Vector2 rayEnd = rayStart + dir * 500.0f;
|
||||
|
||||
Vector2 rayStartWorld = item.WorldPosition;
|
||||
float worldDist = 1000.0f;
|
||||
#if CLIENT
|
||||
worldDist = Screen.Selected?.Cam?.WorldView.Width ?? GameMain.GraphicsWidth;
|
||||
@@ -579,7 +589,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!removePending)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
Entity useTarget = lastTarget?.Body.UserData is Limb limb ? limb.character : lastTarget?.Body.UserData as Entity;
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, useTarget: useTarget, user: _user);
|
||||
}
|
||||
|
||||
if (item.body != null && item.body.FarseerBody.IsBullet)
|
||||
@@ -624,7 +635,6 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private bool OnProjectileCollision(Fixture f1, Fixture target, Contact contact)
|
||||
{
|
||||
if (User != null && User.Removed) { User = null; return false; }
|
||||
@@ -695,7 +705,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
private Fixture lastTarget;
|
||||
|
||||
private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal, Vector2 velocity)
|
||||
{
|
||||
@@ -706,6 +717,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lastTarget = target;
|
||||
|
||||
float projectileNewSpeed = 0.5f;
|
||||
float projectileDeflectedNewSpeed = 0.1f;
|
||||
@@ -836,14 +848,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
if (attackResult.AppliedDamageModifiers != null &&
|
||||
attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles))
|
||||
(attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles) && !StickToDeflective))
|
||||
{
|
||||
item.body.LinearVelocity *= projectileDeflectedNewSpeed;
|
||||
}
|
||||
else if (Vector2.Dot(velocity, collisionNormal) < 0.0f && hits.Count() >= MaxTargetsToHit &&
|
||||
else if ( // When hitting characters the collision normal seems to sometimes point into wrong direction, resulting in a failed attempt to stick
|
||||
//Vector2.Dot(Vector2.Normalize(velocity), collisionNormal) < 0.0f &&
|
||||
hits.Count() >= MaxTargetsToHit &&
|
||||
target.Body.Mass > item.body.Mass * 0.5f &&
|
||||
(DoesStick ||
|
||||
(StickToCharacters && target.Body.UserData is Limb) ||
|
||||
(StickToCharacters && (target.Body.UserData is Limb || target.Body.UserData is Character)) ||
|
||||
(StickToStructures && target.Body.UserData is Structure) ||
|
||||
(StickToItems && target.Body.UserData is Item)))
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private int qualityLevel;
|
||||
|
||||
[Serialize(0, false)]
|
||||
[Serialize(0, true)]
|
||||
public int QualityLevel
|
||||
{
|
||||
get { return qualityLevel; }
|
||||
|
||||
@@ -406,7 +406,15 @@ namespace Barotrauma.Items.Components
|
||||
fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f;
|
||||
fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed);
|
||||
|
||||
item.MaxRepairConditionMultiplier = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplier);
|
||||
// kind of rough to keep this in update, but seems most robust
|
||||
if (requiredSkills.Any(s => s != null && s.Identifier.Equals("mechanical", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
item.MaxRepairConditionMultiplier = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplierMechanical);
|
||||
}
|
||||
if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
item.MaxRepairConditionMultiplier = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplierElectrical);
|
||||
}
|
||||
|
||||
if (currentFixerAction == FixActions.Repair)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@ using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -54,6 +53,27 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, false, description: "Should the rope snap when the character drops the aim?")]
|
||||
public bool SnapWhenNotAimed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(30.0f, false, description: "How much mass is required for the target to pull the source towards it. Static and kinematic targets are always treated heavy enough.")]
|
||||
public float TargetMinMass
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool LerpForces
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool snapped;
|
||||
public bool Snapped
|
||||
{
|
||||
@@ -85,6 +105,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public void Snap() => Snapped = true;
|
||||
|
||||
public void Attach(ISpatialEntity source, Item target)
|
||||
{
|
||||
@@ -118,14 +139,15 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 diff = target.WorldPosition - source.WorldPosition;
|
||||
if (diff.LengthSquared() > MaxLength * MaxLength)
|
||||
{
|
||||
Snapped = true;
|
||||
Snap();
|
||||
return;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
|
||||
var projectile = target.GetComponent<Projectile>();
|
||||
if (projectile == null) { return; }
|
||||
if (SnapOnCollision)
|
||||
{
|
||||
raycastTimer += deltaTime;
|
||||
@@ -135,28 +157,24 @@ namespace Barotrauma.Items.Components
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall,
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
var projectile = target?.GetComponent<Projectile>();
|
||||
if (projectile != null)
|
||||
foreach (Body body in projectile.Hits)
|
||||
{
|
||||
foreach (Body body in projectile.Hits)
|
||||
Submarine alreadyHitSub = null;
|
||||
if (body.UserData is Structure hitStructure)
|
||||
{
|
||||
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; }
|
||||
}
|
||||
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;
|
||||
Submarine targetSub = projectile.StickTarget?.UserData as Submarine ?? target.Submarine;
|
||||
|
||||
if (f.Body?.UserData is MapEntity mapEntity && mapEntity.Submarine != null)
|
||||
{
|
||||
@@ -175,7 +193,7 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}) != null)
|
||||
{
|
||||
Snapped = true;
|
||||
Snap();
|
||||
return;
|
||||
}
|
||||
raycastTimer = 0.0f;
|
||||
@@ -183,27 +201,107 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Vector2 forceDir = diff;
|
||||
if (forceDir.LengthSquared() > 0.01f)
|
||||
float distance = diff.Length();
|
||||
if (distance > 0.001f)
|
||||
{
|
||||
forceDir = Vector2.Normalize(forceDir);
|
||||
}
|
||||
|
||||
if (Math.Abs(ProjectilePullForce) > 0.001f)
|
||||
{
|
||||
var projectile = target.GetComponent<Projectile>();
|
||||
projectile?.Item?.body?.ApplyForce(-forceDir * ProjectilePullForce);
|
||||
projectile.Item?.body?.ApplyForce(-forceDir * ProjectilePullForce);
|
||||
}
|
||||
|
||||
if (Math.Abs(SourcePullForce) > 0.001f)
|
||||
if (projectile.StickTarget != null)
|
||||
{
|
||||
var sourceBody = GetBodyToPull(source);
|
||||
sourceBody?.ApplyForce(forceDir * SourcePullForce);
|
||||
}
|
||||
|
||||
if (Math.Abs(TargetPullForce) > 0.001f)
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
targetBody?.ApplyForce(-forceDir * TargetPullForce);
|
||||
float targetMass = float.MaxValue;
|
||||
Character targetCharacter = null;
|
||||
if (projectile.StickTarget.UserData is Limb targetLimb)
|
||||
{
|
||||
targetCharacter = targetLimb.character;
|
||||
targetMass = targetLimb.ragdoll.Mass;
|
||||
}
|
||||
else if (projectile.StickTarget.UserData is Character character)
|
||||
{
|
||||
targetCharacter = character;
|
||||
targetMass = character.Mass;
|
||||
}
|
||||
else if (projectile.StickTarget.UserData is Item item)
|
||||
{
|
||||
targetMass = projectile.StickTarget.Mass;
|
||||
}
|
||||
if (projectile.StickTarget.BodyType != BodyType.Dynamic)
|
||||
{
|
||||
targetMass = float.MaxValue;
|
||||
}
|
||||
var user = item.GetComponent<Projectile>()?.User;
|
||||
if (targetMass > TargetMinMass)
|
||||
{
|
||||
if (Math.Abs(SourcePullForce) > 0.001f)
|
||||
{
|
||||
var sourceBody = GetBodyToPull(source);
|
||||
if (sourceBody != null)
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
if (targetBody != null && !(targetBody.UserData is Character))
|
||||
{
|
||||
sourceBody.ApplyForce(targetBody.LinearVelocity * sourceBody.Mass);
|
||||
}
|
||||
float forceMultiplier = 1;
|
||||
if (user != null)
|
||||
{
|
||||
user.AnimController.Hang();
|
||||
if (user.InWater)
|
||||
{
|
||||
if (user.IsRagdolled)
|
||||
{
|
||||
forceMultiplier = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
forceMultiplier = user.IsRagdolled ? 0.1f : 0.4f;
|
||||
// Prevents too easy smashing to the walls
|
||||
forceDir.X /= 4;
|
||||
// Prevents rubberbanding up and down
|
||||
if (forceDir.Y < 0)
|
||||
{
|
||||
forceDir.Y = 0;
|
||||
}
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
var myCollider = user.AnimController.Collider;
|
||||
var targetCollider = targetCharacter.AnimController.Collider;
|
||||
if (myCollider.LinearVelocity != Vector2.Zero && targetCollider.LinearVelocity != Vector2.Zero)
|
||||
{
|
||||
if (Vector2.Dot(Vector2.Normalize(myCollider.LinearVelocity), Vector2.Normalize(targetCollider.LinearVelocity)) < 0)
|
||||
{
|
||||
myCollider.ApplyForce(targetCollider.LinearVelocity * targetCollider.Mass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
float force = LerpForces ? MathHelper.Lerp(0, SourcePullForce, MathUtils.InverseLerp(0, MaxLength / 2, distance)) * forceMultiplier : SourcePullForce * forceMultiplier;
|
||||
sourceBody.ApplyForce(forceDir * force);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Math.Abs(TargetPullForce) > 0.001f)
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
if (user != null && targetCharacter != null && !user.AnimController.InWater)
|
||||
{
|
||||
// Prevents rubberbanding horizontally when dragging a corpse.
|
||||
if ((forceDir.X < 0) != (user.AnimController.Dir < 0))
|
||||
{
|
||||
forceDir.X = Math.Clamp(forceDir.X, -0.1f, 0.1f);
|
||||
}
|
||||
}
|
||||
float force = LerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance)) : TargetPullForce;
|
||||
targetBody?.ApplyForce(-forceDir * force);
|
||||
targetCharacter?.AnimController.Collider.ApplyForce(-forceDir * force * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,19 +329,23 @@ namespace Barotrauma.Items.Components
|
||||
return ownerCharacter.AnimController.Collider;
|
||||
}
|
||||
var projectile = targetItem.GetComponent<Projectile>();
|
||||
if (projectile != null)
|
||||
if (projectile != null && projectile.StickTarget != null)
|
||||
{
|
||||
if (projectile.StickTarget?.UserData is Structure structure)
|
||||
if (projectile.StickTarget.UserData is Structure structure)
|
||||
{
|
||||
return structure.Submarine?.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget?.UserData is Submarine sub)
|
||||
else if (projectile.StickTarget.UserData is Submarine sub)
|
||||
{
|
||||
return sub?.PhysicsBody;
|
||||
return sub.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget?.UserData is Character character)
|
||||
else if (projectile.StickTarget.UserData is Item item)
|
||||
{
|
||||
return character.AnimController.Collider;
|
||||
return item.body;
|
||||
}
|
||||
else if (projectile.StickTarget.UserData is Limb limb)
|
||||
{
|
||||
return limb.body;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Scanner : ItemComponent
|
||||
{
|
||||
[Serialize(1.0f, false, description: "How long it takes for the scan to be completed.")]
|
||||
public float ScanDuration { get; set; }
|
||||
[Serialize(0.0f, false, description: "How far along the scan is. When the timer goes above ScanDuration, the scan is completed.")]
|
||||
public float ScanTimer
|
||||
{
|
||||
get
|
||||
{
|
||||
return scanTimer;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (Holdable == null) { return; }
|
||||
bool wasScanCompletedPreviously = IsScanCompleted;
|
||||
scanTimer = Math.Max(0.0f, value);
|
||||
if (!wasScanCompletedPreviously && IsScanCompleted)
|
||||
{
|
||||
OnScanCompleted?.Invoke(this);
|
||||
}
|
||||
#if SERVER
|
||||
if (wasScanCompletedPreviously != IsScanCompleted || Math.Abs(LastSentScanTimer - scanTimer) > 0.1f)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
LastSentScanTimer = scanTimer;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
[Serialize(1.0f, false, description: "How far the scanner can be from the target for the scan to be successful.")]
|
||||
public float ScanRadius { get; set; }
|
||||
[Serialize(true, false, description: "Should the progress bar always be displayed when the item has been attached.")]
|
||||
public bool AlwaysDisplayProgressBar { get; set; }
|
||||
|
||||
private Holdable Holdable { get; set; }
|
||||
/// <summary>
|
||||
/// Should the progress bar be displayed. Use when AlwaysDisplayProgressBar is set to false.
|
||||
/// </summary>
|
||||
public bool DisplayProgressBar { get; set; } = false;
|
||||
private bool IsScanCompleted => scanTimer >= ScanDuration;
|
||||
|
||||
private float scanTimer;
|
||||
|
||||
public Action<Scanner> OnScanStarted, OnScanCompleted;
|
||||
|
||||
public Scanner(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (Holdable != null && Holdable.Attachable && Holdable.Attached)
|
||||
{
|
||||
if (ScanTimer <= 0.0f)
|
||||
{
|
||||
OnScanStarted?.Invoke(this);
|
||||
}
|
||||
ScanTimer += deltaTime;
|
||||
item.AiTarget?.IncreaseSoundRange(deltaTime, speed: 2.0f);
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScanTimer = 0.0f;
|
||||
DisplayProgressBar = false;
|
||||
}
|
||||
UpdateProjSpecific();
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
Holdable = item.GetComponent<Holdable>();
|
||||
if (Holdable == null || !Holdable.Attachable)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in initializing a Scanner component: an attachable Holdable component is required on the same item and none was found");
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ButtonTerminal : ItemComponent
|
||||
{
|
||||
[Editable, Serialize(new string[0], true, description: "Signals sent when the corresponding buttons are pressed.", alwaysUseInstanceValues: true)]
|
||||
public string[] Signals { get; set; }
|
||||
[Editable, Serialize("", true, description: "Identifiers or tags of items that, when contained, allow the terminal buttons to be used. Multiple ones should be separated by commas.", alwaysUseInstanceValues: true)]
|
||||
public string ActivatingItems { get; set; }
|
||||
|
||||
private int RequiredSignalCount { get; set; }
|
||||
private ItemContainer Container { get; set; }
|
||||
private HashSet<ItemPrefab> ActivatingItemPrefabs { get; set; } = new HashSet<ItemPrefab>();
|
||||
|
||||
|
||||
private bool AllowUsingButtons => ActivatingItemPrefabs.None() || Container.Inventory.AllItems.Any(i => i != null && ActivatingItemPrefabs.Any(p => p == i.Prefab));
|
||||
|
||||
public ButtonTerminal(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
RequiredSignalCount = element.GetChildElements("TerminalButton").Count(c => c.GetAttribute("style") != null);
|
||||
if (RequiredSignalCount < 1)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": no TerminalButton elements defined for the ButtonTerminal component!");
|
||||
}
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
|
||||
if (Signals == null)
|
||||
{
|
||||
Signals = new string[RequiredSignalCount];
|
||||
for (int i = 0; i < RequiredSignalCount; i++)
|
||||
{
|
||||
Signals[i] = string.Empty;
|
||||
}
|
||||
}
|
||||
else if (Signals.Length != RequiredSignalCount)
|
||||
{
|
||||
string[] newSignals = new string[RequiredSignalCount];
|
||||
if (Signals.Length < RequiredSignalCount)
|
||||
{
|
||||
Signals.CopyTo(newSignals, 0);
|
||||
for (int i = Signals.Length; i < RequiredSignalCount; i++)
|
||||
{
|
||||
newSignals[i] = string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < RequiredSignalCount; i++)
|
||||
{
|
||||
newSignals[i] = Signals[i];
|
||||
}
|
||||
}
|
||||
Signals = newSignals;
|
||||
}
|
||||
|
||||
ActivatingItemPrefabs.Clear();
|
||||
if (!string.IsNullOrEmpty(ActivatingItems))
|
||||
{
|
||||
foreach (var activatingItem in ActivatingItems.Split(','))
|
||||
{
|
||||
if (MapEntityPrefab.Find(null, identifier: activatingItem, showErrorMessages: false) is ItemPrefab prefab)
|
||||
{
|
||||
ActivatingItemPrefabs.Add(prefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPrefab.Prefabs.Where(p => p.Tags.Any(t => t.Equals(activatingItem, StringComparison.OrdinalIgnoreCase)))
|
||||
.ForEach(p => ActivatingItemPrefabs.Add(p));
|
||||
}
|
||||
}
|
||||
if (ActivatingItemPrefabs.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": no activating item prefabs found with identifiers or tags \"{ActivatingItems}\"");
|
||||
}
|
||||
}
|
||||
|
||||
var containers = item.GetComponents<ItemContainer>().ToList();
|
||||
if (containers.Count != 1)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": the ButtonTerminal component requires exactly one ItemContainer component!");
|
||||
return;
|
||||
}
|
||||
Container = containers[0];
|
||||
|
||||
OnItemLoadedProjSpecific();
|
||||
}
|
||||
|
||||
partial void OnItemLoadedProjSpecific();
|
||||
|
||||
private bool SendSignal(int signalIndex, bool isServerMessage = false)
|
||||
{
|
||||
if (!isServerMessage && !AllowUsingButtons) { return false; }
|
||||
string signal = Signals[signalIndex];
|
||||
string connectionName = $"signal_out{signalIndex + 1}";
|
||||
item.SendSignal(signal, connectionName);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Write(IWriteMessage msg, object[] extraData)
|
||||
{
|
||||
if (extraData == null || extraData.Length < 3) { return; }
|
||||
msg.WriteRangedInteger((int)extraData[2], 0, Signals.Length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (XElement connectionElement in subElement.Elements())
|
||||
{
|
||||
string prefabConnectionName = element.GetAttributeString("name", null);
|
||||
string prefabConnectionName = connectionElement.GetAttributeString("name", null);
|
||||
if (prefabConnectionName == Name)
|
||||
{
|
||||
displayNameTag = connectionElement.GetAttributeString("displayname", "");
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class TriggerComponent : ItemComponent
|
||||
{
|
||||
[Editable, Serialize(0.0f, true, description: "The maximum amount of force applied to the triggering entitites.", alwaysUseInstanceValues: true)]
|
||||
public float Force { get; set; }
|
||||
|
||||
public PhysicsBody PhysicsBody { get; private set; }
|
||||
private float Radius { get; set; }
|
||||
private float RadiusInDisplayUnits { get; set; }
|
||||
private bool TriggeredOnce { get; set; }
|
||||
|
||||
public bool TriggerActive { get; private set; }
|
||||
|
||||
private readonly LevelTrigger.TriggererType triggeredBy;
|
||||
private readonly HashSet<Entity> triggerers = new HashSet<Entity>();
|
||||
private readonly bool triggerOnce;
|
||||
private readonly List<ISerializableEntity> statusEffectTargets = new List<ISerializableEntity>();
|
||||
/// <summary>
|
||||
/// Effects applied to entities inside the trigger
|
||||
/// </summary>
|
||||
private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
/// <summary>
|
||||
/// Attacks applied to entities inside the trigger
|
||||
/// </summary>
|
||||
private readonly List<Attack> attacks = new List<Attack>();
|
||||
|
||||
public TriggerComponent(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
string triggeredByAttribute = element.GetAttributeString("triggeredby", "Character");
|
||||
if (!Enum.TryParse(triggeredByAttribute, out triggeredBy))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in ForceComponent config: \"{triggeredByAttribute}\" is not a valid triggerer type.");
|
||||
}
|
||||
triggerOnce = element.GetAttributeBool("triggeronce", false);
|
||||
string parentDebugName = $"TriggerComponent in {item.Name}";
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "statuseffect":
|
||||
LevelTrigger.LoadStatusEffect(statusEffects, subElement, parentDebugName);
|
||||
break;
|
||||
case "attack":
|
||||
case "damage":
|
||||
LevelTrigger.LoadAttack(subElement, parentDebugName, triggerOnce, attacks);
|
||||
break;
|
||||
}
|
||||
}
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
float radiusAttribute = originalElement.GetAttributeFloat("radius", 10.0f);
|
||||
Radius = ConvertUnits.ToSimUnits(radiusAttribute * item.Scale);
|
||||
PhysicsBody = new PhysicsBody(0.0f, 0.0f, Radius, 1.5f)
|
||||
{
|
||||
BodyType = BodyType.Static,
|
||||
CollidesWith = LevelTrigger.GetCollisionCategories(triggeredBy),
|
||||
CollisionCategories = Physics.CollisionWall,
|
||||
UserData = item
|
||||
};
|
||||
PhysicsBody.FarseerBody.SetIsSensor(true);
|
||||
PhysicsBody.FarseerBody.OnCollision += OnCollision;
|
||||
PhysicsBody.FarseerBody.OnSeparation += OnSeparation;
|
||||
RadiusInDisplayUnits = ConvertUnits.ToDisplayUnits(PhysicsBody.radius);
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
base.OnMapLoaded();
|
||||
PhysicsBody.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
|
||||
PhysicsBody.Submarine = item.Submarine;
|
||||
}
|
||||
|
||||
private bool OnCollision(Fixture sender, Fixture other, Contact contact)
|
||||
{
|
||||
if (!(LevelTrigger.GetEntity(other) is Entity entity)) { return false; }
|
||||
if (!LevelTrigger.IsTriggeredByEntity(entity, triggeredBy, mustBeOnSpecificSub: (true, item.Submarine))) { return false; }
|
||||
triggerers.Add(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnSeparation(Fixture sender, Fixture other, Contact contact)
|
||||
{
|
||||
if (!(LevelTrigger.GetEntity(other) is Entity entity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (entity is Character character && (!character.Enabled || character.Removed) && triggerers.Contains(entity))
|
||||
{
|
||||
triggerers.Remove(entity);
|
||||
return;
|
||||
}
|
||||
if (LevelTrigger.CheckContactsForOtherFixtures(PhysicsBody, other, entity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
triggerers.Remove(entity);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
triggerers.RemoveWhere(t => t.Removed);
|
||||
LevelTrigger.RemoveDistantTriggerers(PhysicsBody, triggerers, item.WorldPosition);
|
||||
|
||||
if (triggerOnce)
|
||||
{
|
||||
if (TriggeredOnce) { return; }
|
||||
if (triggerers.Count > 0)
|
||||
{
|
||||
TriggeredOnce = true;
|
||||
IsActive = false;
|
||||
triggerers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
TriggerActive = triggerers.Any();
|
||||
|
||||
foreach (Entity triggerer in triggerers)
|
||||
{
|
||||
LevelTrigger.ApplyStatusEffects(statusEffects, item.WorldPosition, triggerer, deltaTime, statusEffectTargets);
|
||||
|
||||
if (triggerer is IDamageable damageable)
|
||||
{
|
||||
LevelTrigger.ApplyAttacks(attacks, damageable, item.WorldPosition, deltaTime);
|
||||
}
|
||||
else if (triggerer is Submarine submarine)
|
||||
{
|
||||
LevelTrigger.ApplyAttacks(attacks, item.WorldPosition, deltaTime);
|
||||
}
|
||||
|
||||
if (Force < 0.01f)
|
||||
{
|
||||
// Just ignore very minimal forces
|
||||
continue;
|
||||
}
|
||||
else if (triggerer is Character c)
|
||||
{
|
||||
ApplyForce(c.AnimController.Collider);
|
||||
}
|
||||
else if (triggerer is Submarine s)
|
||||
{
|
||||
ApplyForce(s.SubBody.Body);
|
||||
}
|
||||
else if (triggerer is Item i && i.body != null)
|
||||
{
|
||||
ApplyForce(i.body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyForce(PhysicsBody body)
|
||||
{
|
||||
Vector2 diff = ConvertUnits.ToDisplayUnits(PhysicsBody.SimPosition - body.SimPosition);
|
||||
if (diff.LengthSquared() < 0.0001f) { return; }
|
||||
float distanceFactor = LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits);
|
||||
if (distanceFactor <= 0.0f) { return; }
|
||||
Vector2 force = distanceFactor * Force * Vector2.Normalize(diff);
|
||||
if (force.LengthSquared() < 0.01f) { return; }
|
||||
body.ApplyForce(force);
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
base.Move(amount);
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
PhysicsBody.SetTransform(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
PhysicsBody.Submarine = item.Submarine;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,19 +413,19 @@ namespace Barotrauma.Items.Components
|
||||
return i1.WearableComponent.AllowedSlots.Contains(InvSlotType.OuterClothes).CompareTo(i2.WearableComponent.AllowedSlots.Contains(InvSlotType.OuterClothes));
|
||||
});
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
equipLimb.UpdateWearableTypesToHide();
|
||||
#endif
|
||||
}
|
||||
character.OnWearablesChanged();
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
Character previousPicker = picker;
|
||||
Unequip(picker);
|
||||
|
||||
base.Drop(dropper);
|
||||
|
||||
previousPicker?.OnWearablesChanged();
|
||||
picker = null;
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user