<commit_before>﻿using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Mirror;
using UnityEngine.Serialization;
/// <summary>
///  Allows an object to behave like a gun and fire shots. Server authoritative with client prediction.
/// </summary>
[RequireComponent(typeof(Pickupable))]
[RequireComponent(typeof(ItemStorage))]
public class Gun : NetworkBehaviour, IPredictedCheckedInteractable<AimApply>, IClientInteractable<HandActivate>,
	 IClientInteractable<InventoryApply>, IServerInventoryMove, IServerSpawn, IExaminable
{
	//constants for calculating screen shake due to recoil
	private static readonly float MAX_PROJECTILE_VELOCITY = 48f;
	private static readonly float MAX_SHAKE_INTENSITY = 1f;
	private static readonly float MIN_SHAKE_INTENSITY = 0.01f;

	/// <summary>
	///     The type of ammo this weapon will allow, this is a string and not an enum for diversity
	/// </summary>
	[FormerlySerializedAs("AmmoType")] public AmmoType ammoType;

	//server-side flag indicating if the gun is currently held by a player
	private bool serverIsHeld;

	/// <summary>
	///     The current magazine for this weapon, null means empty
	/// </summary>
	public MagazineBehaviour CurrentMagazine =>
		magSlot.Item != null ? magSlot.Item.GetComponent<MagazineBehaviour>() : null;

	/// <summary>
	///     Checks if the weapon should spawn weapon casings
	/// </summary>
	public bool SpawnsCaseing = true;

	/// <summary>
	///     If the gun should eject it's magazine automatically
	/// </summary>
	public bool SmartGun = false;

	/// <summary>
	///     The the current recoil variance this weapon has reached
	/// </summary>
	[HideInInspector]
	public float CurrentRecoilVariance;

	/// <summary>
	///     The countdown untill we can shoot again (seconds)
	/// </summary>
	[HideInInspector]
	public double FireCountDown;

	/// <summary>
	///     The the name of the sound this gun makes when shooting
	/// </summary>
	[FormerlySerializedAs("FireingSound")]
	public string FiringSound;

	/// <summary>
	///     The amount of times per second this weapon can fire
	/// </summary>
	public double FireRate;

	/// <summary>
	///     If suicide shooting should be prevented (for when user inadvertently drags over themselves during a burst)
	/// </summary>
	private bool AllowSuicide;

	//TODO connect these with the actual shooting of a projectile
	/// <summary>
	///     The max recoil angle this weapon can reach with sustained fire
	/// </summary>
	public float MaxRecoilVariance;

	/// <summary>
	///     The projectile fired from this weapon
	/// </summary>
	public GameObject Projectile;

	/// <summary>
	///     The amount of projectiles expended per shot
	/// </summary>
	public int ProjectilesFired;

	/// <summary>
	///     The traveling speed for this weapons projectile
	/// </summary>
	public int ProjectileVelocity;

	/// <summary>
	/// Describes the recoil behavior of the camera when this gun is fired
	/// </summary>
	[Tooltip("Describes the recoil behavior of the camera when this gun is fired")]
	public CameraRecoilConfig CameraRecoilConfig;

	/// <summary>
	///     Current Weapon Type
	/// </summary>
	public WeaponType WeaponType;

	private GameObject casingPrefab;

	/// <summary>
	/// Used only in server, the queued up shots that need to be performed when the weapon FireCountDown hits
	/// 0.
	/// </summary>
	private Queue<QueuedShot> queuedShots;

	/// <summary>
	/// We don't want to eject the magazine or reload as soon as the client says its time to do those things - if the client is
	/// firing a burst, they might shoot the whole magazine then eject it and reload before server is done processing the last shot.
	/// Instead, these vars are set when the client says to eject or load a new magazine, and the server will only process
	/// the actual unload / load (updating MagNetID) once the shot queue is empty.
	/// </summary>
	private bool queuedUnload = false;
	private uint queuedLoadMagNetID = NetId.Invalid;

	private RegisterTile registerTile;
	private ItemStorage itemStorage;
	private ItemSlot magSlot;


	#region Init Logic

	private void Awake()
	{
		GetComponent<ItemAttributesV2>().AddTrait(CommonTraits.Instance.Gun);
		itemStorage = GetComponent<ItemStorage>();
		magSlot = itemStorage.GetIndexedItemSlot(0);
		registerTile = GetComponent<RegisterTile>();
		//init weapon with missing settings


		queuedShots = new Queue<QueuedShot>();
	}

	public void OnSpawnServer(SpawnInfo info)
	{
		Init();
	}

	private void Init()
	{
		//populate with a full mag on spawn
		Logger.LogTraceFormat("Auto-populate magazine for {0}", Category.Inventory, name);

		//AmmoPrefabs
		GameObject ammoPrefab = AmmoPrefabs.GetAmmoPrefab(ammoType);

		Inventory.ServerAdd(Spawn.ServerPrefab(ammoPrefab).GameObject, magSlot);
	}

	public void OnInventoryMoveServer(InventoryMove info)
	{
		serverIsHeld = info.ToPlayer != null;
	}

	#endregion

	#region Interaction

	public bool WillInteract(AimApply interaction, NetworkSide side)
	{
		if (!DefaultWillInteract.Default(interaction, side)) return false;
		if (CurrentMagazine == null)
		{
			PlayEmptySFX();
			if (interaction.Performer != PlayerManager.LocalPlayer)
			{
				Logger.LogTrace("Server rejected shot - No magazine being loaded", Category.Firearms);
			}

			return false;
		}

		//note: fire count down is only checked local player side, as server processes all the shots in a queue
		//anyway so client cannot exceed that firing rate no matter what. If we validate firing rate server
		//side at the moment of interaction, it will reject client's shots because of lag between server / client
		//firing countdown
		if (Projectile != null && CurrentMagazine.ClientAmmoRemains <= 0 && (interaction.Performer != PlayerManager.LocalPlayer || FireCountDown <= 0))
		{
			if (SmartGun)
			{
				RequestUnload(CurrentMagazine);
				OutOfAmmoSFX();
			}
			else
			{
				PlayEmptySFX();
			}
			if (interaction.Performer != PlayerManager.LocalPlayer)
			{
				Logger.LogTrace("Server rejected shot - out of ammo", Category.Firearms);
			}

			return false;
		}

		if (Projectile != null && CurrentMagazine.ClientAmmoRemains > 0 && (interaction.Performer != PlayerManager.LocalPlayer || FireCountDown <= 0))
		{
			if (interaction.MouseButtonState == MouseButtonState.PRESS)
			{
				return true;
			}
			else
			{
				//being held, only can shoot if this is an automatic
				return WeaponType == WeaponType.FullyAutomatic;
			}
		}

		if (interaction.Performer != PlayerManager.LocalPlayer)
		{
			Logger.LogTraceFormat("Server rejected shot - unknown reason. MouseButtonState {0} ammo remains {1} weapon type {2}", Category.Firearms,
				interaction.MouseButtonState, CurrentMagazine.ClientAmmoRemains, WeaponType);
		}
		return false;
	}

	public void ClientPredictInteraction(AimApply interaction)
	{
		//do we need to check if this is a suicide (want to avoid the check because it involves a raycast).
		//case 1 - we are beginning a new shot, need to see if we are shooting ourselves
		//case 2 - we are firing an automatic and are currently shooting ourselves, need to see if we moused off
		//	ourselves.
		var isSuicide = false;
		if (interaction.MouseButtonState == MouseButtonState.PRESS ||
			(WeaponType == WeaponType.FullyAutomatic && AllowSuicide))
		{
			isSuicide = interaction.IsAimingAtSelf;
			AllowSuicide = isSuicide;
		}

		var dir = ApplyRecoil(interaction.TargetVector.normalized);
		DisplayShot(PlayerManager.LocalPlayer, dir, UIManager.DamageZone, isSuicide);
	}

	//nothing to rollback
	public void ServerRollbackClient(AimApply interaction) { }

	public void ServerPerformInteraction(AimApply interaction)
	{
		//do we need to check if this is a suicide (want to avoid the check because it involves a raycast).
		//case 1 - we are beginning a new shot, need to see if we are shooting ourselves
		//case 2 - we are firing an automatic and are currently shooting ourselves, need to see if we moused off
		//	ourselves.
		var isSuicide = false;
		if (interaction.MouseButtonState == MouseButtonState.PRESS ||
			(WeaponType == WeaponType.FullyAutomatic && AllowSuicide))
		{
			isSuicide = interaction.IsAimingAtSelf;
			AllowSuicide = isSuicide;
		}

		//enqueue the shot (will be processed in Update)
		ServerShoot(interaction.Performer, interaction.TargetVector.normalized, UIManager.DamageZone, isSuicide);
	}



	public bool Interact(HandActivate interaction)
	{
		//try ejecting the mag
		if (CurrentMagazine != null)
		{
			RequestUnload(CurrentMagazine);
			return true;
		}

		return false;
	}

	public bool Interact(InventoryApply interaction)
	{
		//only reload if the gun is the target and mag is in hand slot
		if (interaction.TargetObject == gameObject && interaction.IsFromHandSlot)
		{
			if (interaction.UsedObject != null)
			{
				MagazineBehaviour magazine = interaction.UsedObject.GetComponent<MagazineBehaviour>();
				if (magazine)
				{
					TryReload(magazine);
					return true;
				}
			}

		}
		return false;
	}

	public string  Examine(Vector3 pos)
	{
		return WeaponType + " - Fires " + ammoType + " ammunition (" + (CurrentMagazine != null?(CurrentMagazine.ServerAmmoRemains.ToString() + " rounds loaded in magazine"):"It's empty!") + ")";
	}

	#endregion


	#region Weapon Firing Mechanism

	private void Update()
	{
		//don't process if we are server and the gun is not held by anyone
		if (isServer && !serverIsHeld) return;

		//if we are client, make sure we've initialized
		if (!isServer && !PlayerManager.LocalPlayer) return;

		//if we are client, only process this if we are holding it
		if (!isServer)
		{
			if (UIManager.Hands == null || UIManager.Hands.CurrentSlot == null) return;
			var heldItem = UIManager.Hands.CurrentSlot.ItemObject;
			if (gameObject != heldItem) return;
		}

		//update the time until the next shot can happen
		if (FireCountDown > 0)
		{
			FireCountDown -= Time.deltaTime;
			//prevents the next projectile taking miliseconds longer than it should
			if (FireCountDown < 0)
			{
				FireCountDown = 0;
			}
		}

		//remaining logic is server side only

		//this will only be executed on the server since only the server
		//maintains the queued actions
		if (queuedShots.Count > 0 && FireCountDown <= 0)
		{
			//fire the next shot in the queue
			DequeueAndProcessServerShot();
		}

		if (queuedUnload && queuedShots.Count == 0)
		{
			// done processing shot queue,
			// perform the queued unload action, causing all clients and server to update their version of this Weapon
			//due to the syncvar hook
			Inventory.ServerDrop(magSlot);
			queuedUnload = false;

		}
		if (queuedLoadMagNetID != NetId.Invalid && queuedShots.Count == 0)
		{
			//done processing shot queue, perform the reload, causing all clients and server to update their version of this Weapon
			//due to the syncvar hook
			var magazine = NetworkIdentity.spawned[queuedLoadMagNetID];
			var fromSlot = magazine.GetComponent<Pickupable>().ItemSlot;
			Inventory.ServerTransfer(fromSlot, magSlot);
			queuedLoadMagNetID = NetId.Invalid;
		}
	}

	/// <summary>
	/// attempt to reload the weapon with the item given
	/// </summary>
	private void TryReload(MagazineBehaviour magazine)
	{
		if (CurrentMagazine == null)
		{
			//RELOAD
			// If the item used on the gun is a magazine, check type and reload
			AmmoType ammoType = magazine.ammoType;
			if (this.ammoType == ammoType)
			{
				var hand = UIManager.Hands.CurrentSlot.NamedSlot;
				RequestReload(magazine.gameObject, hand, true);
			}
			if (this.ammoType != ammoType)
			{
				Chat.AddExamineMsgToClient("You try to load the wrong ammo into your weapon");
			}
		}
		else if (ammoType == magazine.ammoType)
		{
			Chat.AddExamineMsgToClient("You weapon is already loaded, you can't fit more Magazines in it, silly!");
		}
	}

	/// <summary>
	/// Perform an actual shot on the server, putting the requesst to shoot into our queue
	/// and informing all clients of the shot once the shot is processed
	/// </summary>
	/// <param name="shotBy">gameobject of the player performing the shot</param>
	/// <param name="target">normalized target vector(actual trajectory will differ due to accuracy)</param>
	/// <param name="damageZone">targeted body part</param>
	/// <param name="isSuicideShot">if this is a suicide shot</param>
	[Server]
	public void ServerShoot(GameObject shotBy, Vector2 target,
		BodyPartType damageZone, bool isSuicideShot)
	{
		var finalDirection = ApplyRecoil(target);
		//don't enqueue the shot if the player is no longer able to shoot
		PlayerScript shooter = shotBy.GetComponent<PlayerScript>();
		if (!Validations.CanInteract(shooter, NetworkSide.Server))
		{
			Logger.LogTrace("Server rejected shot: shooter cannot interact", Category.Firearms);
			return;
		}
		//simply enqueue the shot
		//but only enqueue the shot if we have not yet queued up all the shots in the magazine
		if (CurrentMagazine != null && queuedShots.Count < CurrentMagazine.ServerAmmoRemains)
		{
			queuedShots.Enqueue(new QueuedShot(shotBy, finalDirection, damageZone, isSuicideShot));
		}
	}

	/// <summary>
	/// Gets the next shot from the queue (if there is one) and performs the server-side shot (which will
	/// perform damage calculation when the bullet hits stuff), informing all
	/// clients to display the shot.
	/// </summary>
	[Server]
	private void DequeueAndProcessServerShot()
	{
		if (queuedShots.Count > 0)
		{
			QueuedShot nextShot = queuedShots.Dequeue();

			// check if we can still shoot
			PlayerMove shooter = nextShot.shooter.GetComponent<PlayerMove>();
			PlayerScript shooterScript = nextShot.shooter.GetComponent<PlayerScript>();
			if (!shooter.allowInput || shooterScript.IsGhost)
			{
				Logger.Log("A player tried to shoot when not allowed or when they were a ghost.", Category.Exploits);
				Logger.LogWarning("A shot was attempted when shooter is a ghost or is not allowed to shoot.", Category.Firearms);
				return;
			}


			if (CurrentMagazine == null || CurrentMagazine.ServerAmmoRemains <= 0 || Projectile == null)
			{
				Logger.LogTrace("Player tried to shoot when there was no ammo.", Category.Exploits);
				Logger.LogWarning("A shot was attempted when there is no ammo.", Category.Firearms);
				return;
			}

			if (FireCountDown > 0)
			{
				Logger.LogTrace("Player tried to shoot too fast.", Category.Exploits);
				Logger.LogWarning("Shot was attempted to be dequeued when the fire count down is not yet at 0.", Category.Exploits);
				return;
			}

			//perform the actual server side shooting, creating the bullet that does actual damage
			DisplayShot(nextShot.shooter, nextShot.finalDirection, nextShot.damageZone, nextShot.isSuicide);

			//trigger a hotspot caused by gun firing
			registerTile.Matrix.ReactionManager.ExposeHotspotWorldPosition(nextShot.shooter.TileWorldPosition(), 3200, 0.005f);

			//tell all the clients to display the shot
			ShootMessage.SendToAll(nextShot.finalDirection, nextShot.damageZone, nextShot.shooter, this.gameObject, nextShot.isSuicide);

			//kickback
			shooterScript.pushPull.Pushable.NewtonianMove((-nextShot.finalDirection).NormalizeToInt());

			if (SpawnsCaseing)
			{
				if (casingPrefab == null)
				{
					casingPrefab = Resources.Load("BulletCasing") as GameObject;
				}

				Spawn.ServerPrefab(casingPrefab, nextShot.shooter.transform.position, nextShot.shooter.transform.parent);
			}
		}
	}

	/// <summary>
	/// Perform and display the shot locally (i.e. only on this instance of the game). Does not
	/// communicate anything to other players (unless this is the server, in which case the server
	/// will determine the effects of the bullet). Does not do any validation. This should only be invoked
	/// when displaying the results of a shot (i.e. after receiving a ShootMessage or after this client performs a shot)
	/// or when server is determining the outcome of the shot.
	/// </summary>
	/// <param name="shooter">gameobject of the shooter</param>
	/// <param name="finalDirection">direction the shot should travel (accuracy deviation should already be factored into this)</param>
	/// <param name="damageZone">targeted damage zone</param>
	/// <param name="isSuicideShot">if this is a suicide shot (aimed at shooter)</param>
	public void DisplayShot(GameObject shooter, Vector2 finalDirection,
		BodyPartType damageZone, bool isSuicideShot)
	{
		//if this is our gun (or server), last check to ensure we really can shoot
		if ((isServer || PlayerManager.LocalPlayer == shooter) &&
			CurrentMagazine.ClientAmmoRemains <= 0)
		{
			if (isServer)
			{
				Logger.LogTrace("Server rejected shot - out of ammo", Category.Firearms);
			}

			return;
		}
		//TODO: If this is not our gun, simply display the shot, don't run any other logic
		if (shooter == PlayerManager.LocalPlayer)
		{
			//this is our gun so we need to update our predictions
			FireCountDown += 1.0 / FireRate;
			//add additional recoil after shooting for the next round
			AppendRecoil();

			//Default camera recoil params until each gun is configured separately
			if (CameraRecoilConfig == null || CameraRecoilConfig.Distance == 0f)
			{
				CameraRecoilConfig = new CameraRecoilConfig
				{
					Distance = 0.2f,
					RecoilDuration = 0.05f,
					RecoveryDuration = 0.6f
				};
			}
			Camera2DFollow.followControl.Recoil(-finalDirection, CameraRecoilConfig);
		}

		//call ExpendAmmo outside of previous check, or it won't run serverside and state will desync.
		CurrentMagazine.ExpendAmmo();
<commit_msg>Fixes shooting bug<commit_after>﻿using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Mirror;
using UnityEngine.Serialization;
/// <summary>
///  Allows an object to behave like a gun and fire shots. Server authoritative with client prediction.
/// </summary>
[RequireComponent(typeof(Pickupable))]
[RequireComponent(typeof(ItemStorage))]
public class Gun : NetworkBehaviour, IPredictedCheckedInteractable<AimApply>, IClientInteractable<HandActivate>,
	 IClientInteractable<InventoryApply>, IServerInventoryMove, IServerSpawn, IExaminable
{
	//constants for calculating screen shake due to recoil
	private static readonly float MAX_PROJECTILE_VELOCITY = 48f;
	private static readonly float MAX_SHAKE_INTENSITY = 1f;
	private static readonly float MIN_SHAKE_INTENSITY = 0.01f;

	/// <summary>
	///     The type of ammo this weapon will allow, this is a string and not an enum for diversity
	/// </summary>
	[FormerlySerializedAs("AmmoType")] public AmmoType ammoType;

	//server-side flag indicating if the gun is currently held by a player
	private bool serverIsHeld;

	/// <summary>
	///     The current magazine for this weapon, null means empty
	/// </summary>
	public MagazineBehaviour CurrentMagazine =>
		magSlot.Item != null ? magSlot.Item.GetComponent<MagazineBehaviour>() : null;

	/// <summary>
	///     Checks if the weapon should spawn weapon casings
	/// </summary>
	public bool SpawnsCaseing = true;

	/// <summary>
	///     If the gun should eject it's magazine automatically
	/// </summary>
	public bool SmartGun = false;

	/// <summary>
	///     The the current recoil variance this weapon has reached
	/// </summary>
	[HideInInspector]
	public float CurrentRecoilVariance;

	/// <summary>
	///     The countdown untill we can shoot again (seconds)
	/// </summary>
	[HideInInspector]
	public double FireCountDown;

	/// <summary>
	///     The the name of the sound this gun makes when shooting
	/// </summary>
	[FormerlySerializedAs("FireingSound")]
	public string FiringSound;

	/// <summary>
	///     The amount of times per second this weapon can fire
	/// </summary>
	public double FireRate;

	/// <summary>
	///     If suicide shooting should be prevented (for when user inadvertently drags over themselves during a burst)
	/// </summary>
	private bool AllowSuicide;

	//TODO connect these with the actual shooting of a projectile
	/// <summary>
	///     The max recoil angle this weapon can reach with sustained fire
	/// </summary>
	public float MaxRecoilVariance;

	/// <summary>
	///     The projectile fired from this weapon
	/// </summary>
	public GameObject Projectile;

	/// <summary>
	///     The amount of projectiles expended per shot
	/// </summary>
	public int ProjectilesFired;

	/// <summary>
	///     The traveling speed for this weapons projectile
	/// </summary>
	public int ProjectileVelocity;

	/// <summary>
	/// Describes the recoil behavior of the camera when this gun is fired
	/// </summary>
	[Tooltip("Describes the recoil behavior of the camera when this gun is fired")]
	public CameraRecoilConfig CameraRecoilConfig;

	/// <summary>
	///     Current Weapon Type
	/// </summary>
	public WeaponType WeaponType;

	private GameObject casingPrefab;

	/// <summary>
	/// Used only in server, the queued up shots that need to be performed when the weapon FireCountDown hits
	/// 0.
	/// </summary>
	private Queue<QueuedShot> queuedShots;

	/// <summary>
	/// We don't want to eject the magazine or reload as soon as the client says its time to do those things - if the client is
	/// firing a burst, they might shoot the whole magazine then eject it and reload before server is done processing the last shot.
	/// Instead, these vars are set when the client says to eject or load a new magazine, and the server will only process
	/// the actual unload / load (updating MagNetID) once the shot queue is empty.
	/// </summary>
	private bool queuedUnload = false;
	private uint queuedLoadMagNetID = NetId.Invalid;

	private RegisterTile registerTile;
	private ItemStorage itemStorage;
	private ItemSlot magSlot;


	#region Init Logic

	private void Awake()
	{
		GetComponent<ItemAttributesV2>().AddTrait(CommonTraits.Instance.Gun);
		itemStorage = GetComponent<ItemStorage>();
		magSlot = itemStorage.GetIndexedItemSlot(0);
		registerTile = GetComponent<RegisterTile>();
		//init weapon with missing settings


		queuedShots = new Queue<QueuedShot>();
	}

	public void OnSpawnServer(SpawnInfo info)
	{
		Init();
	}

	private void Init()
	{
		//populate with a full mag on spawn
		Logger.LogTraceFormat("Auto-populate magazine for {0}", Category.Inventory, name);

		//AmmoPrefabs
		GameObject ammoPrefab = AmmoPrefabs.GetAmmoPrefab(ammoType);

		Inventory.ServerAdd(Spawn.ServerPrefab(ammoPrefab).GameObject, magSlot);
	}

	public void OnInventoryMoveServer(InventoryMove info)
	{
		serverIsHeld = info.ToPlayer != null;
	}

	#endregion

	#region Interaction

	public bool WillInteract(AimApply interaction, NetworkSide side)
	{
		if (!DefaultWillInteract.Default(interaction, side)) return false;
		if (CurrentMagazine == null)
		{
			PlayEmptySFX();
			if (interaction.Performer != PlayerManager.LocalPlayer)
			{
				Logger.LogTrace("Server rejected shot - No magazine being loaded", Category.Firearms);
			}

			return false;
		}

		//note: fire count down is only checked local player side, as server processes all the shots in a queue
		//anyway so client cannot exceed that firing rate no matter what. If we validate firing rate server
		//side at the moment of interaction, it will reject client's shots because of lag between server / client
		//firing countdown
		if (Projectile != null && CurrentMagazine.ClientAmmoRemains <= 0 && (interaction.Performer != PlayerManager.LocalPlayer || FireCountDown <= 0))
		{
			if (SmartGun)
			{
				RequestUnload(CurrentMagazine);
				OutOfAmmoSFX();
			}
			else
			{
				PlayEmptySFX();
			}
			if (interaction.Performer != PlayerManager.LocalPlayer)
			{
				Logger.LogTrace("Server rejected shot - out of ammo", Category.Firearms);
			}

			return false;
		}

		if (Projectile != null && CurrentMagazine.ClientAmmoRemains > 0 && (interaction.Performer != PlayerManager.LocalPlayer || FireCountDown <= 0))
		{
			if (interaction.MouseButtonState == MouseButtonState.PRESS)
			{
				return true;
			}
			else
			{
				//being held, only can shoot if this is an automatic
				return WeaponType == WeaponType.FullyAutomatic;
			}
		}

		if (interaction.Performer != PlayerManager.LocalPlayer)
		{
			Logger.LogTraceFormat("Server rejected shot - unknown reason. MouseButtonState {0} ammo remains {1} weapon type {2}", Category.Firearms,
				interaction.MouseButtonState, CurrentMagazine.ClientAmmoRemains, WeaponType);
		}
		return false;
	}

	public void ClientPredictInteraction(AimApply interaction)
	{
		//do we need to check if this is a suicide (want to avoid the check because it involves a raycast).
		//case 1 - we are beginning a new shot, need to see if we are shooting ourselves
		//case 2 - we are firing an automatic and are currently shooting ourselves, need to see if we moused off
		//	ourselves.
		var isSuicide = false;
		if (interaction.MouseButtonState == MouseButtonState.PRESS ||
			(WeaponType == WeaponType.FullyAutomatic && AllowSuicide))
		{
			isSuicide = interaction.IsAimingAtSelf;
			AllowSuicide = isSuicide;
		}

		var dir = ApplyRecoil(interaction.TargetVector.normalized);
		DisplayShot(PlayerManager.LocalPlayer, dir, UIManager.DamageZone, isSuicide);
	}

	//nothing to rollback
	public void ServerRollbackClient(AimApply interaction) { }

	public void ServerPerformInteraction(AimApply interaction)
	{
		//do we need to check if this is a suicide (want to avoid the check because it involves a raycast).
		//case 1 - we are beginning a new shot, need to see if we are shooting ourselves
		//case 2 - we are firing an automatic and are currently shooting ourselves, need to see if we moused off
		//	ourselves.
		var isSuicide = false;
		if (interaction.MouseButtonState == MouseButtonState.PRESS ||
			(WeaponType == WeaponType.FullyAutomatic && AllowSuicide))
		{
			isSuicide = interaction.IsAimingAtSelf;
			AllowSuicide = isSuicide;
		}

		//enqueue the shot (will be processed in Update)
		ServerShoot(interaction.Performer, interaction.TargetVector.normalized, UIManager.DamageZone, isSuicide);
	}



	public bool Interact(HandActivate interaction)
	{
		//try ejecting the mag
		if (CurrentMagazine != null)
		{
			RequestUnload(CurrentMagazine);
			return true;
		}

		return false;
	}

	public bool Interact(InventoryApply interaction)
	{
		//only reload if the gun is the target and mag is in hand slot
		if (interaction.TargetObject == gameObject && interaction.IsFromHandSlot)
		{
			if (interaction.UsedObject != null)
			{
				MagazineBehaviour magazine = interaction.UsedObject.GetComponent<MagazineBehaviour>();
				if (magazine)
				{
					TryReload(magazine);
					return true;
				}
			}

		}
		return false;
	}

	public string  Examine(Vector3 pos)
	{
		return WeaponType + " - Fires " + ammoType + " ammunition (" + (CurrentMagazine != null?(CurrentMagazine.ServerAmmoRemains.ToString() + " rounds loaded in magazine"):"It's empty!") + ")";
	}

	#endregion


	#region Weapon Firing Mechanism

	private void Update()
	{
		//don't process if we are server and the gun is not held by anyone
		if (isServer && !serverIsHeld) return;

		//if we are client, make sure we've initialized
		if (!isServer && !PlayerManager.LocalPlayer) return;

		//if we are client, only process this if we are holding it
		if (!isServer)
		{
			if (UIManager.Hands == null || UIManager.Hands.CurrentSlot == null) return;
			var heldItem = UIManager.Hands.CurrentSlot.ItemObject;
			if (gameObject != heldItem) return;
		}

		//update the time until the next shot can happen
		if (FireCountDown > 0)
		{
			FireCountDown -= Time.deltaTime;
			//prevents the next projectile taking miliseconds longer than it should
			if (FireCountDown < 0)
			{
				FireCountDown = 0;
			}
		}

		//remaining logic is server side only

		//this will only be executed on the server since only the server
		//maintains the queued actions
		if (queuedShots.Count > 0 && FireCountDown <= 0)
		{
			//fire the next shot in the queue
			DequeueAndProcessServerShot();
		}

		if (queuedUnload && queuedShots.Count == 0)
		{
			// done processing shot queue,
			// perform the queued unload action, causing all clients and server to update their version of this Weapon
			//due to the syncvar hook
			Inventory.ServerDrop(magSlot);
			queuedUnload = false;

		}
		if (queuedLoadMagNetID != NetId.Invalid && queuedShots.Count == 0)
		{
			//done processing shot queue, perform the reload, causing all clients and server to update their version of this Weapon
			//due to the syncvar hook
			var magazine = NetworkIdentity.spawned[queuedLoadMagNetID];
			var fromSlot = magazine.GetComponent<Pickupable>().ItemSlot;
			Inventory.ServerTransfer(fromSlot, magSlot);
			queuedLoadMagNetID = NetId.Invalid;
		}
	}

	/// <summary>
	/// attempt to reload the weapon with the item given
	/// </summary>
	private void TryReload(MagazineBehaviour magazine)
	{
		if (CurrentMagazine == null)
		{
			//RELOAD
			// If the item used on the gun is a magazine, check type and reload
			AmmoType ammoType = magazine.ammoType;
			if (this.ammoType == ammoType)
			{
				var hand = UIManager.Hands.CurrentSlot.NamedSlot;
				RequestReload(magazine.gameObject, hand, true);
			}
			if (this.ammoType != ammoType)
			{
				Chat.AddExamineMsgToClient("You try to load the wrong ammo into your weapon");
			}
		}
		else if (ammoType == magazine.ammoType)
		{
			Chat.AddExamineMsgToClient("You weapon is already loaded, you can't fit more Magazines in it, silly!");
		}
	}

	/// <summary>
	/// Perform an actual shot on the server, putting the requesst to shoot into our queue
	/// and informing all clients of the shot once the shot is processed
	/// </summary>
	/// <param name="shotBy">gameobject of the player performing the shot</param>
	/// <param name="target">normalized target vector(actual trajectory will differ due to accuracy)</param>
	/// <param name="damageZone">targeted body part</param>
	/// <param name="isSuicideShot">if this is a suicide shot</param>
	[Server]
	public void ServerShoot(GameObject shotBy, Vector2 target,
		BodyPartType damageZone, bool isSuicideShot)
	{
		var finalDirection = ApplyRecoil(target);
		//don't enqueue the shot if the player is no longer able to shoot
		PlayerScript shooter = shotBy.GetComponent<PlayerScript>();
		if (!Validations.CanInteract(shooter, NetworkSide.Server))
		{
			Logger.LogTrace("Server rejected shot: shooter cannot interact", Category.Firearms);
			return;
		}
		//simply enqueue the shot
		//but only enqueue the shot if we have not yet queued up all the shots in the magazine
		if (CurrentMagazine != null && queuedShots.Count < CurrentMagazine.ServerAmmoRemains)
		{
			queuedShots.Enqueue(new QueuedShot(shotBy, finalDirection, damageZone, isSuicideShot));
		}
	}

	/// <summary>
	/// Gets the next shot from the queue (if there is one) and performs the server-side shot (which will
	/// perform damage calculation when the bullet hits stuff), informing all
	/// clients to display the shot.
	/// </summary>
	[Server]
	private void DequeueAndProcessServerShot()
	{
		if (queuedShots.Count > 0)
		{
			QueuedShot nextShot = queuedShots.Dequeue();

			// check if we can still shoot
			PlayerMove shooter = nextShot.shooter.GetComponent<PlayerMove>();
			PlayerScript shooterScript = nextShot.shooter.GetComponent<PlayerScript>();
			if (!shooter.allowInput || shooterScript.IsGhost)
			{
				Logger.Log("A player tried to shoot when not allowed or when they were a ghost.", Category.Exploits);
				Logger.LogWarning("A shot was attempted when shooter is a ghost or is not allowed to shoot.", Category.Firearms);
				return;
			}


			if (CurrentMagazine == null || CurrentMagazine.ServerAmmoRemains <= 0 || Projectile == null)
			{
				Logger.LogTrace("Player tried to shoot when there was no ammo.", Category.Exploits);
				Logger.LogWarning("A shot was attempted when there is no ammo.", Category.Firearms);
				return;
			}

			if (FireCountDown > 0)
			{
				Logger.LogTrace("Player tried to shoot too fast.", Category.Exploits);
				Logger.LogWarning("Shot was attempted to be dequeued when the fire count down is not yet at 0.", Category.Exploits);
				return;
			}

			//perform the actual server side shooting, creating the bullet that does actual damage
			DisplayShot(nextShot.shooter, nextShot.finalDirection, nextShot.damageZone, nextShot.isSuicide);

			//trigger a hotspot caused by gun firing
			registerTile.Matrix.ReactionManager.ExposeHotspotWorldPosition(nextShot.shooter.TileWorldPosition(), 3200, 0.005f);

			//tell all the clients to display the shot
			ShootMessage.SendToAll(nextShot.finalDirection, nextShot.damageZone, nextShot.shooter, this.gameObject, nextShot.isSuicide);

			//kickback
			shooterScript.pushPull.Pushable.NewtonianMove((-nextShot.finalDirection).NormalizeToInt());

			if (SpawnsCaseing)
			{
				if (casingPrefab == null)
				{
					casingPrefab = Resources.Load("BulletCasing") as GameObject;
				}

				Spawn.ServerPrefab(casingPrefab, nextShot.shooter.transform.position, nextShot.shooter.transform.parent);
			}
		}
	}

	/// <summary>
	/// Perform and display the shot locally (i.e. only on this instance of the game). Does not
	/// communicate anything to other players (unless this is the server, in which case the server
	/// will determine the effects of the bullet). Does not do any validation. This should only be invoked
	/// when displaying the results of a shot (i.e. after receiving a ShootMessage or after this client performs a shot)
	/// or when server is determining the outcome of the shot.
	/// </summary>
	/// <param name="shooter">gameobject of the shooter</param>
	/// <param name="finalDirection">direction the shot should travel (accuracy deviation should already be factored into this)</param>
	/// <param name="damageZone">targeted damage zone</param>
	/// <param name="isSuicideShot">if this is a suicide shot (aimed at shooter)</param>
	public void DisplayShot(GameObject shooter, Vector2 finalDirection,
		BodyPartType damageZone, bool isSuicideShot)
	{
		//if this is our gun (or server), last check to ensure we really can shoot
		if ((isServer || PlayerManager.LocalPlayer == shooter) &&
			CurrentMagazine.ClientAmmoRemains <= 0)
		{
			if (isServer)
			{
				Logger.LogTrace("Server rejected shot - out of ammo", Category.Firearms);
			}

			return;
		}
		//TODO: If this is not our gun, simply display the shot, don't run any other logic
		if (shooter == PlayerManager.LocalPlayer)
		{
			//this is our gun so we need to update our predictions
			FireCountDown += 1.0 / FireRate;
			//add additional recoil after shooting for the next round
			AppendRecoil();

			//Default camera recoil params until each gun is configured separately
			if (CameraRecoilConfig == null || CameraRecoilConfig.Distance == 0f)
			{
				CameraRecoilConfig = new CameraRecoilConfig
				{
					Distance = 0.2f,
					RecoilDuration = 0.05f,
					RecoveryDuration = 0.6f
				};
			}
			Camera2DFollow.followControl.Recoil(-finalDirection, CameraRecoilConfig);
		}

		if (CurrentMagazine == null)
		{
			Logger.Log("Why is CurrentMagazine null on this client?");
		}
		else
		{
			//call ExpendAmmo outside of previous check, or it won't run serverside and state will desync.
			CurrentMagazine.ExpendAmmo();
		}

{'fraction_non_alphanumeric': 0.09728448645098656, 'fraction_numerical': 0.00314097255932155, 'mean_word_length': 3.6602794411177646, 'pattern_counts': {'":': 0, '<': 167, '<?xml version=': 0, '>': 163, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7546341', 'n_tokens_mistral': 11648, 'n_tokens_neox': 10313, 'n_words': 3545}