<commit_before>﻿//#define DEBUG_MOVER
using System;
using Microsoft.Xna.Framework;
using System.Collections.Generic;


namespace Nez.Tiled
{
	/// <summary>
	/// WIP
	/// The TiledMapMover is a helper for moving objects around in a gravity-based Tiled map. It requires that the Entity it is on has a BoxCollider. The
	/// BoxCollider will be used in conjuntion with colliderHorizontal/VerticalInset for all collision detection.
	/// 
	/// One way platforms can be jumped down through by moving your Transform down 1 pixel and calling CollisionState.clearLastGroundTile.
	/// 
	/// If you plan to use slopes/one way platforms with the TiledMapMover some extra properties need to be added to your tiles in Tiled.
	/// They are listed below:
	/// - nez:isOneWayPlatform (bool): one way platforms will ignore all collisions except from above
	/// - nez:isSlope (bool): signifies if the tile is a slope. Requires the next two properties if it is
	/// - nez:slopeTopLeft (int): distance in pixels from the tiles top to the slope on the left side. For example, a 45 top-left to bottom-right
	/// tile |\ would have a slopeTopLeft of 0 and slopeTopRight of 15
	/// - nez:slopeTopRight (int): distance in pixels from the tiles top to the slope on the right side
	/// </summary>
	public class TiledMapMover :
	#if DEBUG_MOVER
	RenderableComponent
	#else
	Component
	#endif
	{
		/// <summary>
		/// class used to house all the collision information from a call to move
		/// </summary>
		public class CollisionState
		{
			public bool right, left, above, below;
			public bool becameGroundedThisFrame;
			public bool wasGroundedLastFrame;
			public bool isGroundedOnOneWayPlatform;
			public float slopeAngle;

			public bool hasCollision { get { return below || right || left || above; } }

			// state used by the TiledMapMover
			internal SubpixelFloat _movementRemainderX, _movementRemainderY;
			internal TiledTile _lastGroundTile;


			public void clearLastGroundTile()
			{
				_lastGroundTile = null;
			}


			public void reset()
			{
				becameGroundedThisFrame = isGroundedOnOneWayPlatform = right = left = above = below = false;
				slopeAngle = 0f;
			}


			/// <summary>
			/// resets collision state and does sub-pixel movement calculations
			/// </summary>
			/// <param name="motion">Motion.</param>
			public void reset( ref Vector2 motion )
			{
				if( motion.X == 0 )
					right = left = false;

				if( motion.Y == 0 )
					above = below = false;
				
				becameGroundedThisFrame = isGroundedOnOneWayPlatform = false;
				slopeAngle = 0f;

				// deal with subpixel movement, storing off any non-integar remainder for the next frame
				_movementRemainderX.update( ref motion.X );
				_movementRemainderY.update( ref motion.Y );

				// due to subpixel movement we might end up with 0 gravity when we really want there to be at least 1 pixel so slopes can work
				if( below && motion.Y == 0 && _movementRemainderY.remainder > 0 )
				{
					motion.Y = 1;
					_movementRemainderY.reset();
				}
			}


			public override string ToString()
			{
				return string.Format( "[CollisionState] r: {0}, l: {1}, a: {2}, b: {3}, angle: {4}, wasGroundedLastFrame: {5}, becameGroundedThisFrame: {6}",
					right, left, above, below, slopeAngle, wasGroundedLastFrame, becameGroundedThisFrame );
			}

		}

		/// <summary>
		/// the inset on the horizontal plane that the BoxCollider will be shrunk by when moving vertically
		/// </summary>
		public int colliderHorizontalInset = 2;

		/// <summary>
		/// the inset on the vertical plane that the BoxCollider will be shrunk by when moving horizontally
		/// </summary>
		public int colliderVerticalInset = 6;

		/// <summary>
		/// the TiledTileLayer used for collision checks
		/// </summary>
		public readonly TiledTileLayer collisionLayer;

		/// <summary>
		/// the TiledMap that contains collisionLayer
		/// </summary>
		public readonly TiledMap tiledMap;

		/// <summary>
		/// temporary storage for all the tiles that intersect the bounds being checked
		/// </summary>
		List<TiledTile> _collidingTiles = new List<TiledTile>();

		/// <summary>
		/// temporary storage to avoid having to pass it around
		/// </summary>
		Rectangle _boxColliderBounds;

		bool _handleSubpixelMovement;
		SubpixelVector2 _subpixelV2 = new SubpixelVector2();


		public TiledMapMover( TiledTileLayer collisionLayer, bool handleSubpixelMovement = false )
		{
			_handleSubpixelMovement = handleSubpixelMovement;

			this.collisionLayer = collisionLayer;
			tiledMap = collisionLayer.tiledMap;
			Assert.isNotNull( collisionLayer, nameof( collisionLayer ) + " is required" );
		}


		/// <summary>
		/// moves the Entity taking into account the tiled map
		/// </summary>
		/// <param name="motion">Motion.</param>
		/// <param name="boxCollider">Box collider.</param>
		public void move( Vector2 motion, BoxCollider boxCollider, CollisionState collisionState )
		{
			// test for collisions then move the Entity
			testCollisions( ref motion, boxCollider.bounds, collisionState );

			boxCollider.unregisterColliderWithPhysicsSystem();
			if( _handleSubpixelMovement )
				_subpixelV2.update( ref motion );
			boxCollider.entity.transform.position += motion;
			boxCollider.registerColliderWithPhysicsSystem();
		}


		public void testCollisions( ref Vector2 motion, Rectangle boxColliderBounds, CollisionState collisionState )
		{
			_boxColliderBounds = boxColliderBounds;

			// save off our current grounded state which we will use for wasGroundedLastFrame and becameGroundedThisFrame<commit_msg>ummm, yeah, we already had subpixel movement handled. reverting last commit<commit_after>﻿//#define DEBUG_MOVER
using System;
using Microsoft.Xna.Framework;
using System.Collections.Generic;


namespace Nez.Tiled
{
	/// <summary>
	/// WIP
	/// The TiledMapMover is a helper for moving objects around in a gravity-based Tiled map. It requires that the Entity it is on has a BoxCollider. The
	/// BoxCollider will be used in conjuntion with colliderHorizontal/VerticalInset for all collision detection.
	/// 
	/// One way platforms can be jumped down through by moving your Transform down 1 pixel and calling CollisionState.clearLastGroundTile.
	/// 
	/// If you plan to use slopes/one way platforms with the TiledMapMover some extra properties need to be added to your tiles in Tiled.
	/// They are listed below:
	/// - nez:isOneWayPlatform (bool): one way platforms will ignore all collisions except from above
	/// - nez:isSlope (bool): signifies if the tile is a slope. Requires the next two properties if it is
	/// - nez:slopeTopLeft (int): distance in pixels from the tiles top to the slope on the left side. For example, a 45 top-left to bottom-right
	/// tile |\ would have a slopeTopLeft of 0 and slopeTopRight of 15
	/// - nez:slopeTopRight (int): distance in pixels from the tiles top to the slope on the right side
	/// </summary>
	public class TiledMapMover :
	#if DEBUG_MOVER
	RenderableComponent
	#else
	Component
	#endif
	{
		/// <summary>
		/// class used to house all the collision information from a call to move
		/// </summary>
		public class CollisionState
		{
			public bool right, left, above, below;
			public bool becameGroundedThisFrame;
			public bool wasGroundedLastFrame;
			public bool isGroundedOnOneWayPlatform;
			public float slopeAngle;

			public bool hasCollision { get { return below || right || left || above; } }

			// state used by the TiledMapMover
			internal SubpixelFloat _movementRemainderX, _movementRemainderY;
			internal TiledTile _lastGroundTile;


			public void clearLastGroundTile()
			{
				_lastGroundTile = null;
			}


			public void reset()
			{
				becameGroundedThisFrame = isGroundedOnOneWayPlatform = right = left = above = below = false;
				slopeAngle = 0f;
			}


			/// <summary>
			/// resets collision state and does sub-pixel movement calculations
			/// </summary>
			/// <param name="motion">Motion.</param>
			public void reset( ref Vector2 motion )
			{
				if( motion.X == 0 )
					right = left = false;

				if( motion.Y == 0 )
					above = below = false;
				
				becameGroundedThisFrame = isGroundedOnOneWayPlatform = false;
				slopeAngle = 0f;

				// deal with subpixel movement, storing off any non-integar remainder for the next frame
				_movementRemainderX.update( ref motion.X );
				_movementRemainderY.update( ref motion.Y );

				// due to subpixel movement we might end up with 0 gravity when we really want there to be at least 1 pixel so slopes can work
				if( below && motion.Y == 0 && _movementRemainderY.remainder > 0 )
				{
					motion.Y = 1;
					_movementRemainderY.reset();
				}
			}


			public override string ToString()
			{
				return string.Format( "[CollisionState] r: {0}, l: {1}, a: {2}, b: {3}, angle: {4}, wasGroundedLastFrame: {5}, becameGroundedThisFrame: {6}",
					right, left, above, below, slopeAngle, wasGroundedLastFrame, becameGroundedThisFrame );
			}

		}

		/// <summary>
		/// the inset on the horizontal plane that the BoxCollider will be shrunk by when moving vertically
		/// </summary>
		public int colliderHorizontalInset = 2;

		/// <summary>
		/// the inset on the vertical plane that the BoxCollider will be shrunk by when moving horizontally
		/// </summary>
		public int colliderVerticalInset = 6;

		/// <summary>
		/// the TiledTileLayer used for collision checks
		/// </summary>
		public readonly TiledTileLayer collisionLayer;

		/// <summary>
		/// the TiledMap that contains collisionLayer
		/// </summary>
		public readonly TiledMap tiledMap;

		/// <summary>
		/// temporary storage for all the tiles that intersect the bounds being checked
		/// </summary>
		List<TiledTile> _collidingTiles = new List<TiledTile>();

		/// <summary>
		/// temporary storage to avoid having to pass it around
		/// </summary>
		Rectangle _boxColliderBounds;


		public TiledMapMover( TiledTileLayer collisionLayer )
		{
			this.collisionLayer = collisionLayer;
			tiledMap = collisionLayer.tiledMap;
			Assert.isNotNull( collisionLayer, nameof( collisionLayer ) + " is required" );
		}


		/// <summary>
		/// moves the Entity taking into account the tiled map
		/// </summary>
		/// <param name="motion">Motion.</param>
		/// <param name="boxCollider">Box collider.</param>
		public void move( Vector2 motion, BoxCollider boxCollider, CollisionState collisionState )
		{
			// test for collisions then move the Entity
			testCollisions( ref motion, boxCollider.bounds, collisionState );

			boxCollider.unregisterColliderWithPhysicsSystem();
			boxCollider.entity.transform.position += motion;
			boxCollider.registerColliderWithPhysicsSystem();
		}


		public void testCollisions( ref Vector2 motion, Rectangle boxColliderBounds, CollisionState collisionState )
		{
			_boxColliderBounds = boxColliderBounds;

			// save off our current grounded state which we will use for wasGroundedLastFrame and becameGroundedThisFrame
{'fraction_non_alphanumeric': 0.09254498714652956, 'fraction_numerical': 0.005325009181050312, 'mean_word_length': 3.367682437850842, 'pattern_counts': {'":': 0, '<': 59, '<?xml version=': 0, '>': 61, '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': '5895293', 'n_tokens_mistral': 3646, 'n_tokens_neox': 3118, 'n_words': 1133}