Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs
2025-09-17 13:44:21 +03:00

240 lines
9.6 KiB
C#

using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
namespace Barotrauma.Items.Components
{
class Throwable : Holdable
{
enum ThrowState
{
None,
Initiated,
Throwing
}
private const float ThrowAngleStart = -MathHelper.PiOver2, ThrowAngleEnd = MathHelper.PiOver2;
private float throwAngle = ThrowAngleStart;
private bool midAir;
private ThrowState throwState;
//continuous collision detection is used while the item is moving faster than this
const float ContinuousCollisionThreshold = 5.0f;
public Character CurrentThrower
{
get;
private set;
}
[Serialize(1.0f, IsPropertySaveable.No, description: "The impulse applied to the physics body of the item when thrown. Higher values make the item be thrown faster.")]
public float ThrowForce { get; set; }
public Throwable(Item item, ContentXElement element)
: base(item, element)
{
if (aimPos == Vector2.Zero)
{
aimPos = new Vector2(0.45f, 0.1f);
}
}
public const float WaterDragCoefficient = 0.5f;
public override bool Use(float deltaTime, Character character = null)
{
//actual throwing logic is handled in Update
return (characterUsable && !UsageDisabledByRangedWeapon(character)) || character == null;
}
public override bool SecondaryUse(float deltaTime, Character character = null)
{
//actual throwing logic is handled in Update - SecondaryUse only triggers when the item is thrown
return false;
}
public override void Drop(Character dropper, bool setTransform = true)
{
base.Drop(dropper, setTransform);
throwState = ThrowState.None;
throwAngle = ThrowAngleStart;
Item.ResetWaterDragCoefficient();
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
Update(deltaTime, cam);
}
public override void Update(float deltaTime, Camera cam)
{
if (!item.body.Enabled) { return; }
if (midAir)
{
if (item.body.FarseerBody.IsBullet)
{
if (item.body.LinearVelocity.LengthSquared() < ContinuousCollisionThreshold * ContinuousCollisionThreshold)
{
item.body.FarseerBody.IsBullet = false;
}
}
if (item.body.LinearVelocity.LengthSquared() < 0.01f)
{
CurrentThrower = null;
if (statusEffectLists?.ContainsKey(ActionType.OnImpact) ?? false)
{
foreach (var statusEffect in statusEffectLists[ActionType.OnImpact])
{
statusEffect.SetUser(null);
}
}
if (statusEffectLists?.ContainsKey(ActionType.OnBroken) ?? false)
{
foreach (var statusEffect in statusEffectLists[ActionType.OnBroken])
{
statusEffect.SetUser(null);
}
}
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform;
midAir = false;
Item.ResetWaterDragCoefficient();
}
return;
}
if (picker == null || picker.Removed || !picker.HeldItems.Contains(item))
{
IsActive = false;
return;
}
bool aim = false;
if (!UsageDisabledByRangedWeapon(picker))
{
if (throwState != ThrowState.Throwing)
{
if (picker.IsKeyDown(InputType.Aim))
{
if (picker.IsKeyDown(InputType.Shoot)) { throwState = ThrowState.Initiated; }
}
else if (throwState != ThrowState.Initiated)
{
throwAngle = ThrowAngleStart;
}
}
aim = picker.IsKeyDown(InputType.Aim) && picker.CanAim;
}
if (picker.IsDead || !picker.AllowInput)
{
throwState = ThrowState.None;
aim = false;
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
//return if the status effect got rid of the picker somehow
if (picker == null || picker.Removed || !picker.HeldItems.Contains(item))
{
IsActive = false;
return;
}
if (item.body.Dir != picker.AnimController.Dir) { item.FlipX(relativeToSub: false); }
AnimController ac = picker.AnimController;
item.Submarine = picker.Submarine;
if (throwState != ThrowState.Throwing)
{
if (aim || throwState == ThrowState.Initiated)
{
throwAngle = System.Math.Min(throwAngle + deltaTime * 8.0f, ThrowAngleEnd);
ac.HoldItem(deltaTime, item, handlePos, itemPos: aimPos, aim: false, throwAngle);
if (throwAngle >= ThrowAngleEnd && throwState == ThrowState.Initiated)
{
throwState = ThrowState.Throwing;
}
}
else
{
throwAngle = ThrowAngleStart;
ac.HoldItem(deltaTime, item, handlePos, itemPos: holdPos, aim: false, holdAngle);
}
}
else
{
throwAngle = MathUtils.WrapAnglePi(throwAngle - deltaTime * 15.0f);
ac.HoldItem(deltaTime, item, handlePos, itemPos: aimPos, aim: false, throwAngle);
if (throwAngle < 0)
{
Vector2 throwVector = Vector2.Normalize(picker.CursorWorldPosition - picker.WorldPosition);
//throw upwards if cursor is at the position of the character
if (!MathUtils.IsValid(throwVector)) { throwVector = Vector2.UnitY; }
#if SERVER
GameServer.Log(GameServer.CharacterLogName(picker) + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
#endif
CurrentThrower = picker;
if (statusEffectLists?.ContainsKey(ActionType.OnImpact) ?? false)
{
foreach (var statusEffect in statusEffectLists[ActionType.OnImpact])
{
statusEffect.SetUser(CurrentThrower);
}
}
if (statusEffectLists?.ContainsKey(ActionType.OnBroken) ?? false)
{
foreach (var statusEffect in statusEffectLists[ActionType.OnBroken])
{
statusEffect.SetUser(CurrentThrower);
}
}
item.Drop(CurrentThrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
item.WaterDragCoefficient = WaterDragCoefficient;
float throwForce = ThrowForce;
//Reduce force when aiming down
float downwardsDotProduct = Vector2.Dot(-Vector2.UnitY, throwVector); //1 when pointing directly down, 0 when sideways, -1 when up
if (downwardsDotProduct > 0)
{
throwForce *= (1.0f - downwardsDotProduct * 0.7f);
}
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
//disable platform collisions until the item comes back to rest again
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
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);
Limb rightHand = ac.GetLimb(LimbType.RightHand);
item.body.AngularVelocity = rightHand.body.AngularVelocity;
throwAngle = ThrowAngleStart;
IsActive = true;
if (GameMain.NetworkMember is { IsServer: true })
{
GameMain.NetworkMember.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnSecondaryUse, this, targetCharacter: CurrentThrower));
}
if (!(GameMain.NetworkMember is { IsClient: true }))
{
//Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, character: CurrentThrower, user: CurrentThrower);
}
throwState = ThrowState.None;
}
}
}
}
}