- Option to adjust ranged weapon spread (separate values for "normal spread" and when being used by an unskilled character). - Option to disable explosion flashes.
341 lines
10 KiB
C#
341 lines
10 KiB
C#
using FarseerPhysics;
|
|
using FarseerPhysics.Dynamics;
|
|
using FarseerPhysics.Dynamics.Contacts;
|
|
using FarseerPhysics.Dynamics.Joints;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma.Items.Components
|
|
{
|
|
class Projectile : ItemComponent
|
|
{
|
|
private float launchImpulse;
|
|
|
|
private bool doesStick;
|
|
private PrismaticJoint stickJoint;
|
|
private Body stickTarget;
|
|
|
|
private Attack attack;
|
|
|
|
public List<Body> IgnoredBodies;
|
|
|
|
public Character User;
|
|
|
|
[HasDefaultValue(10.0f, false)]
|
|
public float LaunchImpulse
|
|
{
|
|
get { return launchImpulse; }
|
|
set { launchImpulse = value; }
|
|
}
|
|
|
|
[HasDefaultValue(false, false)]
|
|
public bool CharacterUsable
|
|
{
|
|
get { return characterUsable; }
|
|
set { characterUsable = value; }
|
|
}
|
|
|
|
[HasDefaultValue(false, false)]
|
|
public bool DoesStick
|
|
{
|
|
get { return doesStick; }
|
|
set { doesStick = value; }
|
|
}
|
|
|
|
[HasDefaultValue(false, false)]
|
|
public bool Hitscan
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[HasDefaultValue(false, false)]
|
|
public bool RemoveOnHit
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public Projectile(Item item, XElement element)
|
|
: base (item, element)
|
|
{
|
|
IgnoredBodies = new List<Body>();
|
|
|
|
foreach (XElement subElement in element.Elements())
|
|
{
|
|
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
|
|
attack = new Attack(subElement);
|
|
}
|
|
}
|
|
|
|
public override bool Use(float deltaTime, Character character = null)
|
|
{
|
|
if (character != null && !characterUsable) return false;
|
|
|
|
Vector2 launchDir = new Vector2((float)Math.Cos(item.body.Rotation), (float)Math.Sin(item.body.Rotation));
|
|
|
|
if (Hitscan)
|
|
{
|
|
DoHitscan(launchDir);
|
|
}
|
|
else
|
|
{
|
|
Launch(launchDir * launchImpulse * item.body.Mass);
|
|
}
|
|
|
|
User = character;
|
|
|
|
return true;
|
|
}
|
|
|
|
private void Launch(Vector2 impulse)
|
|
{
|
|
item.Drop();
|
|
|
|
item.body.Enabled = true;
|
|
item.body.ApplyLinearImpulse(impulse);
|
|
|
|
item.body.FarseerBody.OnCollision += OnProjectileCollision;
|
|
item.body.FarseerBody.IsBullet = true;
|
|
|
|
item.body.CollisionCategories = Physics.CollisionProjectile;
|
|
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
|
|
|
|
IsActive = true;
|
|
|
|
if (stickJoint == null || !doesStick) return;
|
|
|
|
if (stickTarget != null)
|
|
{
|
|
try
|
|
{
|
|
item.body.FarseerBody.RestoreCollisionWith(stickTarget);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
#if DEBUG
|
|
DebugConsole.ThrowError("Failed to restore collision with stickTarget", e);
|
|
#endif
|
|
}
|
|
|
|
stickTarget = null;
|
|
}
|
|
GameMain.World.RemoveJoint(stickJoint);
|
|
stickJoint = null;
|
|
}
|
|
|
|
private void DoHitscan(Vector2 dir)
|
|
{
|
|
float rotation = item.body.Rotation;
|
|
item.Drop();
|
|
|
|
item.body.Enabled = true;
|
|
//set the velocity of the body because the OnProjectileCollision method
|
|
//uses it to determine the direction from which the projectile hit
|
|
item.body.LinearVelocity = dir;
|
|
IsActive = true;
|
|
|
|
Vector2 rayStart = item.SimPosition;
|
|
Vector2 rayEnd = item.SimPosition + dir * 1000.0f;
|
|
|
|
bool hitSomething = false;
|
|
GameMain.World.RayCast((fixture, point, normal, fraction) =>
|
|
{
|
|
if (fixture == null || fixture.IsSensor) return -1;
|
|
|
|
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
|
|
!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
|
|
!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) return -1;
|
|
|
|
item.body.SetTransform(point, rotation);
|
|
if (OnProjectileCollision(fixture, normal))
|
|
{
|
|
hitSomething = true;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}, rayStart, rayEnd);
|
|
|
|
//the raycast didn't hit anything -> the projectile flew somewhere outside the level and is permanently lost
|
|
if (!hitSomething)
|
|
{
|
|
Item.Spawner.AddToRemoveQueue(item);
|
|
}
|
|
}
|
|
|
|
public override void Update(float deltaTime, Camera cam)
|
|
{
|
|
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
|
|
|
if (stickJoint != null &&
|
|
(stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f))
|
|
{
|
|
if (stickTarget != null)
|
|
{
|
|
try
|
|
{
|
|
item.body.FarseerBody.RestoreCollisionWith(stickTarget);
|
|
}
|
|
catch
|
|
{
|
|
//the body that the projectile was stuck to has been removed
|
|
}
|
|
|
|
stickTarget = null;
|
|
}
|
|
|
|
try
|
|
{
|
|
GameMain.World.RemoveJoint(stickJoint);
|
|
}
|
|
catch
|
|
{
|
|
//the body that the projectile was stuck to has been removed
|
|
}
|
|
|
|
stickJoint = null;
|
|
|
|
IsActive = false;
|
|
}
|
|
}
|
|
|
|
private bool OnProjectileCollision(Fixture f1, Fixture f2, Contact contact)
|
|
{
|
|
return OnProjectileCollision(f2, contact.Manifold.LocalNormal);
|
|
}
|
|
|
|
private bool OnProjectileCollision(Fixture target, Vector2 collisionNormal)
|
|
{
|
|
if (IgnoredBodies.Contains(target.Body)) return false;
|
|
|
|
if (target.CollisionCategories == Physics.CollisionCharacter && !(target.Body.UserData is Limb))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
AttackResult attackResult = new AttackResult();
|
|
if (attack != null)
|
|
{
|
|
var submarine = target.Body.UserData as Submarine;
|
|
if (submarine != null)
|
|
{
|
|
item.Move(-submarine.Position);
|
|
item.Submarine = submarine;
|
|
item.body.Submarine = submarine;
|
|
//item.FindHull();
|
|
return true;
|
|
}
|
|
|
|
Limb limb;
|
|
Structure structure;
|
|
if ((limb = (target.Body.UserData as Limb)) != null)
|
|
{
|
|
attackResult = attack.DoDamage(User, limb.character, item.WorldPosition, 1.0f);
|
|
}
|
|
else if ((structure = (target.Body.UserData as Structure)) != null)
|
|
{
|
|
attackResult = attack.DoDamage(User, structure, item.WorldPosition, 1.0f);
|
|
}
|
|
}
|
|
|
|
ApplyStatusEffects(ActionType.OnUse, 1.0f);
|
|
ApplyStatusEffects(ActionType.OnImpact, 1.0f);
|
|
|
|
IsActive = false;
|
|
|
|
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
|
|
|
item.body.FarseerBody.IsBullet = false;
|
|
item.body.CollisionCategories = Physics.CollisionItem;
|
|
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
|
|
|
IgnoredBodies.Clear();
|
|
|
|
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
|
|
|
|
if (attackResult.HitArmor)
|
|
{
|
|
item.body.LinearVelocity *= 0.1f;
|
|
}
|
|
else if (doesStick)
|
|
{
|
|
Vector2 dir = new Vector2(
|
|
(float)Math.Cos(item.body.Rotation),
|
|
(float)Math.Sin(item.body.Rotation));
|
|
|
|
if (Vector2.Dot(item.body.LinearVelocity, collisionNormal) < 0.0f)
|
|
{
|
|
StickToTarget(target.Body, dir);
|
|
return Hitscan;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
item.body.LinearVelocity *= 0.5f;
|
|
}
|
|
|
|
var containedItems = item.ContainedItems;
|
|
if (containedItems != null)
|
|
{
|
|
foreach (Item contained in containedItems)
|
|
{
|
|
if (contained.body != null)
|
|
{
|
|
contained.SetTransform(item.SimPosition, contained.body.Rotation);
|
|
}
|
|
contained.Condition = 0.0f;
|
|
}
|
|
}
|
|
|
|
if (RemoveOnHit)
|
|
{
|
|
Item.Spawner.AddToRemoveQueue(item);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void StickToTarget(Body targetBody, Vector2 axis)
|
|
{
|
|
if (stickJoint != null) return;
|
|
|
|
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, true);
|
|
stickJoint.MotorEnabled = true;
|
|
stickJoint.MaxMotorForce = 30.0f;
|
|
|
|
stickJoint.LimitEnabled = true;
|
|
if (item.Sprite != null)
|
|
{
|
|
stickJoint.LowerLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * -0.3f);
|
|
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * 0.3f);
|
|
}
|
|
|
|
item.body.FarseerBody.IgnoreCollisionWith(targetBody);
|
|
stickTarget = targetBody;
|
|
GameMain.World.AddJoint(stickJoint);
|
|
|
|
IsActive = true;
|
|
}
|
|
|
|
protected override void RemoveComponentSpecific()
|
|
{
|
|
if (stickJoint != null)
|
|
{
|
|
try
|
|
{
|
|
GameMain.World.RemoveJoint(stickJoint);
|
|
}
|
|
catch
|
|
{
|
|
//the body that the projectile was stuck to has been removed
|
|
}
|
|
|
|
stickJoint = null;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|