context: using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Linq;
  using UnityEditor;
  using UnityEngine;
  using UnityEngine.Events;
  using Mirror;
  
  public partial class PlayerSync
  {
  	//Server-only fields, don't concern clients in any way
  	private DualVector3IntEvent onStartMove = new DualVector3IntEvent();
  	public DualVector3IntEvent OnStartMove() => onStartMove;
  	private Vector3IntEvent onTileReached = new Vector3IntEvent();
  	public Vector3IntEvent OnTileReached() => onTileReached;
  	private Vector3IntEvent onUpdateReceived = new Vector3IntEvent();
  	public Vector3IntEvent OnUpdateRecieved() => onUpdateReceived;
  	private UnityEvent onPullInterrupt = new UnityEvent();
  	public UnityEvent OnPullInterrupt() => onPullInterrupt;
  	public CollisionEvent onHighSpeedCollision = new CollisionEvent();
  	public CollisionEvent OnHighSpeedCollision() => onHighSpeedCollision;
  
  	public Vector3Int ServerPosition => serverState.WorldPosition.RoundToInt();
  	public Vector3Int ServerLocalPosition => serverState.Position.RoundToInt();
  
  	public Vector3Int LastNonHiddenPosition => serverState.LastNonHiddenPosition.RoundToInt();
  
  	/// Current server state. Integer positions.
  	private PlayerState serverState;
  
  	/// Serverside lerping state that simulates where players should be on clients at the moment.
  	/// Tracks in-between integer positions unlike serverState. Basically when position changes, it starts with
  	/// setting serverState to the new position, then serverLerpState lerps from the current position to
  	/// serverState until it reaches it.
  	private PlayerState serverLerpState;
  
  	private Queue<PlayerAction> serverPendingActions;
  
  	/// Max size of serverside queue, client will be rolled back if it overflows
  	private readonly int maxServerQueue = 10;
  
  	private HashSet<PushPull> questionablePushables = new HashSet<PushPull>();
  
  	/// Last direction that player moved in. Currently works more like a true impulse, therefore is zero-able
  	private Vector2 lastDirectionServer;
  
  	private RegisterPlayer registerPlayer;
  
  	public float SpeedServer
  	{
  		get => masterSpeedServer;
  		set
  		{ // Future move speed (applied on the next step)
  			if ( Math.Abs( masterSpeedServer - value ) > 0.01f )
  			{
  				Logger.LogTraceFormat( "{0}: setting SERVER speed {1}->{2}", Category.Movement, gameObject.name, SpeedServer, value );
  			}
  			masterSpeedServer = value < 0 ? 0 : value;
  		}
  	}
  	/// <summary>
  	/// Player's serverside move speed, applied on tile change
  	/// </summary>
  	private float masterSpeedServer;
  
  	///
  	public bool IsWeightlessServer {
  		get {
  			if (playerScript.IsGhost)
  			{
  				return false;
  			}
  			GameObject[] context = pushPull.IsPullingSomethingServer ? new[]{gameObject, pushPull.PulledObjectServer.gameObject} : new[]{gameObject};
  			return MatrixManager.IsFloatingAt( context, Vector3Int.RoundToInt( serverState.WorldPosition ), isServer: true );
  		}
  	}
  
  	/// <summary>
  	/// If the position of this player is "non-sticky", i.e. meaning they would slide / float in a given direction
  	/// </summary>
  	public bool IsNonStickyServer
  	{
  		get
  		{
  			if (registerPlayer.IsSlippingServer)
  			{
  				return MatrixManager.IsNoGravityAt(serverState.WorldPosition.RoundToInt(), true)
  				    || MatrixManager.IsSlipperyAt(serverState.WorldPosition.RoundToInt());
  			}
  			return !playerScript.IsGhost && MatrixManager.IsNonStickyAt(serverState.WorldPosition.RoundToInt(), true);
  		}
  	}
  
  	public bool CanNotSpaceMoveServer => IsWeightlessServer && !IsAroundPushables( serverState, true );
  
  
  	public bool IsMovingServer => consideredFloatingServer || !ServerPositionsMatch;
  	public Vector2 ServerImpulse => serverState.WorldImpulse;
  
  	/// Whether player is considered to be floating on server
  	private bool consideredFloatingServer => serverState.WorldImpulse != Vector2.zero /*&& !IsBeingPulledServer*/;
  
  	/// Do current and target server positions match?
  	private bool ServerPositionsMatch => serverState.WorldPosition == serverLerpState.WorldPosition;
  
  
  	///
  		[Server]
  		private void InitServerState()
  		{
  			Vector3Int worldPos = Vector3Int.RoundToInt((Vector2) transform.position); //cutting off Z-axis & rounding
  			MatrixInfo matrixAtPoint = MatrixManager.AtPoint(worldPos, true);
  			masterSpeedServer = playerMove.RunSpeed;
  			PlayerState state = new PlayerState
  			{
  				MoveNumber = 0,
  				MatrixId = matrixAtPoint.Id,
  				WorldPosition = worldPos,
  				Speed = masterSpeedServer
  			};
  			Logger.LogTraceFormat( "{0}: InitServerState for {1} found matrix {2} resulting in\n{3}", Category.Movement,
  				PlayerList.Instance.Get( gameObject ).Name, worldPos, matrixAtPoint, state );
  			serverLerpState = state;
  			ServerState = state;
  	}
  
  	private PlayerAction lastAddedAction = PlayerAction.None;
  	private Coroutine floatingSyncHandle;
  
  	[Command]
  	private void CmdProcessAction(PlayerAction action)
  	{
  		if ( serverPendingActions.Count > 0 && !lastAddedAction.Equals(PlayerAction.None)
  		     && lastAddedAction.isNonPredictive && action.isNonPredictive )
  		{
  			Logger.Log( $"Ignored {action}: two non-predictive actions in a row!", Category.Movement );
  			return;
  		}
  
  		//add action to server simulation queue
  		serverPendingActions.Enqueue(action);
  
  		lastAddedAction = action;
  
  
  		//Rollback pos and punish player if server queue size is more than max size
  		if (serverPendingActions.Count > maxServerQueue)
  		{
  			RollbackPosition();
  			Logger.LogWarning( $"{gameObject.name}: Server pending actions overflow! (More than {maxServerQueue})." +
  			                   "\nEither server lagged or player is attempting speedhack", Category.Movement );
  		}
  	}
  
  	public void SetVisibleServer(bool visible)
  	{
  		if ( visible )
  		{
  			AppearAtPositionServer( pushPull.AssumedWorldPositionServer() );
  		}
  		else
  		{
  			DisappearFromWorldServer();
  		}
  	}
  	public void NewtonianMove(Vector2Int direction, float speed = Single.NaN)
  	{
  		//if we are buckled, transfer the impulse to our buckled object.
  		if (playerMove.IsBuckled)
  		{
  			var buckledCNT = playerMove.BuckledObject.GetComponent<CustomNetTransform>();
  			buckledCNT.NewtonianMove(direction, speed);
  		}
  		else
  		{
  			PushInternal(direction, true, speed);
  		}
  	}
  
  	/// <summary>
  	/// Push player in direction.
  	/// Impulse should be consumed after one tile if indoors,
  	/// and last indefinitely (until hit by obstacle) if you pushed someone into deep space
  	/// </summary>
  	/// <param name="direction"></param>
  	/// <param name="speed"></param>
  	/// <param name="followMode">used when object is following its puller
  	/// (turns on tile snapping and removes player collision check)</param>
  	/// <returns>true if push was successful</returns>
  	[Server]
  	public bool Push(Vector2Int direction, float speed = Single.NaN, bool followMode = false)
  	{
  		//if we are buckled, transfer the impulse to our buckled object.
  		if (playerMove.IsBuckled)
  		{
  			var buckledCNT = playerMove.BuckledObject.GetComponent<CustomNetTransform>();
  			return buckledCNT.Push(direction, speed, followMode);
  		}
  		else
  		{
  			return PushInternal(direction, false, speed, followMode);
  		}
  	}
  
  	private bool PushInternal(Vector2Int direction, bool isNewtonian = false, float speed = Single.NaN, bool followMode = false )
  	{
  		if (!float.IsNaN(speed) && speed <= 0)
  		{
  			return false;
  		}
  
  		speed = float.IsNaN( speed ) ? PushPull.DEFAULT_PUSH_SPEED : speed;
  
  		direction = direction.Normalize();
  
  		if (direction == Vector2Int.zero)
  		{
  			return false;
  		}
  
  		Vector3Int origin = ServerPosition;
  		Vector3Int pushGoal = origin + direction.To3Int();
  
  		if ( !MatrixManager.IsPassableAt( origin, pushGoal, isServer: true, includingPlayers: !followMode ) ) {
  			return false;
  		}
  
  		float uncorrectedSpeed = speed;
  
  		if (isNewtonian)
  		{
  			if (!MatrixManager.IsSlipperyOrNoGravityAt(pushGoal))
  			{
  				return false;
  			}
  
  			if (consideredFloatingServer)
  			{
  				var currentFlyingDirection = serverState.WorldImpulse;
  				var currentFlyingSpeed = serverState.speed;
  
  				float correctedSpeed = speed;
  
  				bool isOppositeDirection = direction == -currentFlyingDirection;
  				bool isSameDirection = direction == currentFlyingDirection;
  
  				if (isOppositeDirection)
  				{
  					Logger.LogTrace("got counter impulse, stopping", Category.PushPull);
  					Stop();
  					return true;
  				}
  
  				if (isSameDirection)
  				{
  					correctedSpeed = Mathf.Clamp(speed + currentFlyingSpeed, currentFlyingSpeed, PushPull.MAX_NEWTONIAN_SPEED);
  				}
  
  				Logger.LogTraceFormat("proposed: {0}@{1}, current: {2}@{3}, result: {4}@{5}", Category.PushPull,
  					direction, speed, currentFlyingDirection, currentFlyingSpeed, direction, correctedSpeed
  					);
  				speed = correctedSpeed;
  			}
  		}
  
  
gt: 		if ( followMode ) {