using Barotrauma.Networking; using FarseerPhysics; using FarseerPhysics.Collision; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Xml.Linq; namespace Barotrauma.Items.Components { class RangedWeapon : ItemComponent { private float reload, reloadTimer; private Vector2 barrelPos; [Serialize("0.0,0.0", false)] public string BarrelPos { get { return XMLExtensions.Vector2ToString(ConvertUnits.ToDisplayUnits(barrelPos)); } set { barrelPos = ConvertUnits.ToSimUnits(XMLExtensions.ParseVector2(value)); } } [Serialize(1.0f, false)] public float Reload { get { return reload; } set { reload = Math.Max(value, 0.0f); } } [Serialize(0.0f, false)] public float Spread { get; set; } [Serialize(0.0f, false)] public float UnskilledSpread { get; set; } public Vector2 TransformedBarrelPos { get { Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation); Vector2 flippedPos = barrelPos; if (item.body.Dir < 0.0f) flippedPos.X = -flippedPos.X; return (Vector2.Transform(flippedPos, bodyTransform) + item.body.SimPosition); } } public RangedWeapon(Item item, XElement element) : base(item, element) { } public override void Update(float deltaTime, Camera cam) { reloadTimer -= deltaTime; if (reloadTimer < 0.0f) { reloadTimer = 0.0f; IsActive = false; } } public override bool Use(float deltaTime, Character character = null) { if (character == null || character.Removed) return false; if (!character.IsKeyDown(InputType.Aim) || reloadTimer > 0.0f) return false; IsActive = true; reloadTimer = reload; List limbBodies = new List(); foreach (Limb l in character.AnimController.Limbs) { limbBodies.Add(l.body.FarseerBody); } float degreeOfFailure = 1.0f - DegreeOfSuccess(character); degreeOfFailure *= degreeOfFailure; if (degreeOfFailure > Rand.Range(0.0f, 1.0f)) { ApplyStatusEffects(ActionType.OnFailure, 1.0f, character); } Projectile projectile = null; var containedItems = item.ContainedItems; if (containedItems == null) return true; foreach (Item item in containedItems) { projectile = item.GetComponent(); if (projectile != null) break; } //projectile not found, see if one of the contained items contains projectiles if (projectile == null) { foreach (Item item in containedItems) { projectile = item.GetComponent(); if (projectile != null) break; } //projectile not found, see if one of the contained items contains projectiles if (projectile == null) { foreach (Item item in containedItems) { var containedSubItems = item.ContainedItems; if (containedSubItems == null) { continue; } foreach (Item subItem in containedSubItems) { projectile = subItem.GetComponent(); //apply OnUse statuseffects to the container in case it has to react to it somehow //(play a sound, spawn more projectiles, reduce condition...) subItem.GetComponent()?.Item.ApplyStatusEffects(ActionType.OnUse, deltaTime); if (projectile != null) break; } } } } if (projectile == null) return true; float spread = MathHelper.ToRadians(MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure)); float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi; rotation += spread * Rand.Range(-0.5f, 0.5f); projectile.User = character; //add the limbs of the shooter to the list of bodies to be ignored //so that the player can't shoot himself projectile.IgnoredBodies = new List(limbBodies); Vector2 projectilePos = item.SimPosition; Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos; Vector2 barrelPos = TransformedBarrelPos; //make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies) == null) { //no obstacles -> we can spawn the projectile at the barrel projectilePos = barrelPos; } else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f) { //spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f); } projectile.Item.body.ResetDynamics(); projectile.Item.SetTransform(projectilePos, rotation); projectile.Use(deltaTime); projectile.User = character; projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f)); //set the rotation of the projectile again because dropping the projectile resets the rotation projectile.Item.SetTransform(projectilePos, rotation + (projectile.Item.body.Dir * projectile.LaunchRotationRadians)); //recoil item.body.ApplyLinearImpulse( new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); item.RemoveContained(projectile.Item); Rope rope = item.GetComponent(); if (rope != null) rope.Attach(projectile.Item); return true; } } }