Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs
T
Joonas Rikkonen a0d606ef5f Working on improving the serialization system and ObjectProperty/IPropertyObject (TODO: come up with better names). The plan is to:
- Add support for some of the most common types (vectors, colors, rects) so there's no need to parse the values in the setters of the serializable properties (see Holdable.HoldPos for example).
- Make a generic version of the item editing HUD that can be used on any IPropertyObject. Should make it easier to implement things like the character editor, editing structure properties, particle editor, etc.
- Improve the interface of the editing HUD. Instead of having to type in a string value into a textbox, there should be number input fields for numeric properties, sliders for properties that only accept a range of values, a color picker, etc. And tooltips.
2017-11-03 19:41:21 +02:00

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;
[SerializableProperty(10.0f, false)]
public float LaunchImpulse
{
get { return launchImpulse; }
set { launchImpulse = value; }
}
[SerializableProperty(false, false)]
public bool CharacterUsable
{
get { return characterUsable; }
set { characterUsable = value; }
}
[SerializableProperty(false, false)]
public bool DoesStick
{
get { return doesStick; }
set { doesStick = value; }
}
[SerializableProperty(false, false)]
public bool Hitscan
{
get;
set;
}
[SerializableProperty(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;
}
}
}
}