Use Entiy\scene setter and fixes

Use Entiy\scene setter and fixes

* Two typos and one logic error fixed

require "LunoPunk.Tweener"

export ^

class Scene extends Tweener
	new: =>
		super!
		@camera = nil
		@visible = true
		-- private
		@__typeFirst = {}
		@__typeCount = {}
		@__count = 0
		@__classCount = {}
		@__entityNames = {}
		@__farthest = nil
		@__first = nil
		@__layerFarthest = nil
		@__layerNearest = nil
		@__layers = nil
		@__layerList = {}
		@__layerCount = {}
		@__add = {}
		@__remove = {}
		@__recycle = {}
		@__recycled = {}
		@__nearest = nil
		@__sprite = nil
		@__uniqueTypes = nil
		@__updateFirst = nil
		@__renderFirst = {}
		@__renderLast = {}

	-- Override this; called when Scene is switch to, and set to the currently active scene.
	begin: =>

	-- Override this; called when Scene is changed, and the active scene is no longer this.
	-- Changed from end because of issues with the name "end" in Lua
	endScene: =>

	-- Override this, called when game gains focus
	focusGained: =>

	-- Override this, called when game loses focus
	focusLost: =>

	-- Performed by the game loop, updates all contained Entities.
	-- If you override this to give your Scene update code, remember
	-- to call super.update() or your Entities will not be updated.
	update: =>
		e = @__updateFirst
		while e != nil
			if e.active
				e\updateTweens! if e\hasTween!
				e\update!
			e\graphic!\update! if e\graphic! != nil and e\graphic!.active
			e = e.__updateNext

	-- Performed by the game loop, renders all contained Entities.
	-- If you override this to give your Scene render code, remember
	-- to call super.render() or your Entities will not be rendered.
	render: =>
		if @__layerSort
			@__layerList\sort @__layerSort if #@__layerList > 1

		-- if LP.renderMode == RenderMode.Hardware
			-- clearSprites!
			-- AtlasData.setScene(@)

		for layer in *@__layerList do
			e = @__renderLast[layer]
			while e != nil
				e\render! if e.visible
				e = e.__renderPrev

		-- AtlasData\render! if LP.renderMode == RenderMode.Hardware

	-- X position of the mouse in the Scene.
	mouseX: => LP.screen.mouseX + @camera.x

	-- Y position of the mouse in the Scene.
	mouseY: => LP.screen.mouseY + @camera.y

	-- Sprite used to store layer sprites when RenderMode.HARDWARE is set.
	sprite: => @__sprite

	-- Adds the Entity to the Scene at the end of the frame.
	-- @param	e		Entity object you want to add.
	-- @return	The added Entity object.
	add: (e) =>
		@__add[#@__add + 1] = e
		e

	-- Removes the Entity from the Scene at the end of the frame.
	-- @param	e		Entity object you want to remove.
	-- @return	The removed Entity object.
	remove: (e) =>
		@__remove[#@__remove + 1] = e
		e

	-- Removes all Entities from the Scene at the end of the frame.
	removeAll: =>
		e = @__updateFirst
		while e != nil
			@__remove[#@__remove] = e
			e = e.__updateNext

	-- Adds multiple Entities to the scene.
	-- @param	...list		Several Entities (as arguments) or an Array/Vector of Entities.
	addList: (list) =>
		for e in *list do @add e

	-- Removes multiple Entities from the scene.
	-- @param	...list		Several Entities (as arguments) or an Array/Vector of Entities.
	removeList: (list) =>
		for e in *list do @remove e

	-- Adds an Entity to the Scene with the Graphic object.
	-- @param	graphic		Graphic to assign the Entity.
	-- @param	x			X position of the Entity.
	-- @param	y			Y position of the Entity.
	-- @param	layer		Layer of the Entity.
	-- @return	The Entity that was added.
	addGraphic: (graphic, layer = LP.BASELAYER!, x = 0, y = 0) =>
		e = Entity x, y, graphic

		e.layer = layer if layer != LP.BASELAYER!
		e.active = false

		@add e

	-- Adds an Entity to the Scene with the Mask object.
	-- @param	mask	Mask to assign the Entity.
	-- @param	type	Collision type of the Entity.
	-- @param	x		X position of the Entity.
	-- @param	y		Y position of the Entity.
	-- @return	The Entity that was added.
	addMask: (mask, type, x = 0, y = 0) =>
		e = Entity x, y, nil, mask

		e.type = type if type != ""
		e.active, e.visable = false, false

		@add e

	-- Returns a new Entity, or a stored recycled Entity if one exists.
	-- @param	classType			The Class of the Entity you want to add.
	-- @param	addToScene			Add it to the Scene immediately.
	-- @param	constructorsArgs	List of the entity constructor arguments (optional).
	-- @return	The new Entity object.
	create: (classType, addToScene = true, ...) =>
		className = classType.__class.__name
		e = @__recycle[className]
		if e != nil
			@__recycle[className] = e.__recycleNext
			e.__recycleNext = nil
		else
			e = classType ...

		return @add e if addToScene

		e

	-- Removes the Entity from the Scene at the end of the frame and recycles it.
	-- The recycled Entity can then be fetched again by calling the create() function.
	-- @param	e		The Entity to recycle.
	-- @return	The recycled Entity.
	recycle: (e) =>
		@__recycle[#@__recycle + 1] = e
		@remove e

	-- Clears stored reycled Entities of the Class type.
	-- @param	classType		The Class type to clear.
	clearRecycled: (classType) =>
		idx = 0
		for i = 1, #@__recycle do
			if @__recycle[i].__class == classType
				idx = i
				break

		e = @__recycle[idx]
		n = nil
		while e != nil
			n = e.__recycleNext
			e.__recycleNext = nil
			e = n
		table.remove @__recycle, idx
		return

	-- Clears stored recycled Entities of all Class types.
	clearRecycledAll: =>
		for e in *@__recycle do
			@clearRecycled e.__class

	-- Brings the Entity to the front of its contained layer.
	-- @param	e		The Entity to shift.
	-- @return	If the Entity changed position.
	bringToFront: (e) =>

	-- Sends the Entity to the back of its contained layer.
	-- @param	e		The Entity to shift.
	-- @return	If the Entity changed position.
	sendToBack: (e) =>

	-- Shifts the Entity one place towards the front of its contained layer.
	-- @param	e		The Entity to shift.
	-- @return	If the Entity changed position.
	bringForward: (e) =>

	-- Shifts the Entity one place towards the back of its contained layer.
	-- @param	e		The Entity to shift.
	-- @return	If the Entity changed position.
	sendBackward: (e) =>

	-- If the Entity as at the front of its layer.
	-- @param	e		The Entity to check.
	-- @return	True or false.
	isAtFront: (e) =>

	-- If the Entity as at the back of its layer.
	-- @param	e		The Entity to check.
	-- @return	True or false.
	isAtBack: (e) =>

	-- Returns the first Entity that collides with the rectangular area.
	-- @param	type		The Entity type to check for.
	-- @param	rX			X position of the rectangle.
	-- @param	rY			Y position of the rectangle.
	-- @param	rWidth		Width of the rectangle.
	-- @param	rHeight		Height of the rectangle.
	-- @return	The first Entity to collide, or null if none collide.
	collideRect: (type, rX, rY, rWidth, rHeight) =>
		e = @__typeFirst[type]
		while e != nil
			return e if e.collidable and e\collideRect e.x, e.y, rX, rY, rWidth, rHeight
			e = e.__typeNext

		return nil

	-- Returns the first Entity found that collides with the position.
	-- @param	type		The Entity type to check for.
	-- @param	pX			X position.
	-- @param	pY			Y position.
	-- @return	The collided Entity, or null if none collide.
	collidePoint: (type, pX, pY) =>
		e = @__typeFirst[type]
		result = nil
		while e != nil
			-- only look for entities that collide
			if e.collidable and e\collidePoint e.x, e.y, pX, pY
				-- the first one must be the front one
				if result == nil
					result = e
				-- compare if the new collided entity is above the former one (lower valuer is toward, higher value is backward)
				else if e.layer < result.layer
					result = e

			e = e.__typeNext

		return result

	-- Returns the first Entity found that collides with the line.
	-- @param	type		The Entity type to check for.
	-- @param	fromX		Start x of the line.
	-- @param	fromY		Start y of the line.
	-- @param	toX			End x of the line.
	-- @param	toY			End y of the line.
	-- @param	precision   Distance between consecutive tests. Higher values are faster but increase the chance of missing collisions.
	-- @param	p           If non-null, will have its x and y values set to the point of collision.
	-- @return	The first Entity to collide, or null if none collide.
	collideLine: (type, fromX, fromY, toX, toY, precision, p) =>

	-- Populates an array with all Entities that collide with the rectangle. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	type		The Entity type to check for.
	-- @param	rX			X position of the rectangle.
	-- @param	rY			Y position of the rectangle.
	-- @param	rWidth		Width of the rectangle.
	-- @param	rHeight		Height of the rectangle.
	-- @param	into		The Array or Vector to populate with collided Entities.
	collideRectInto: (type, rX, rY, rWidth, rHeight, into = {}) =>
		e = @__typeFirst[type]
		n = #into
		while e != nil
			if e.collidable and e\collideRect e.x, e.y, rX, rY, rWidth, rHeight
				into[n] = e
				n += 1

			e = e.__typeNext

		into

	-- Populates an array with all Entities that collide with the circle. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	type 		The Entity type to check for.
	-- @param	circleX		X position of the circle.
	-- @param	circleY		Y position of the circle.
	-- @param	radius		The radius of the circle.
	-- @param	into		The Array or Vector to populate with collided Entities.
	collideCircleInto: (type, circleX, circleY, radius, into = {}) =>
		e = @__typeFirst[type]
		n = #into
		radius *= radius -- square it to avoid the square root
		while e != nil
			if LP.distanceSquared circleX, circleY, e.x, e.y < radius
				into[n] = e
				n += 1

			e = e.__typeNext

		into

	-- Populates an array with all Entities that collide with the position. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	type		The Entity type to check for.
	-- @param	pX			X position.
	-- @param	pY			Y position.
	-- @param	into		The Array or Vector to populate with collided Entities.
	collidePointInto: (type, pX, pY, into) =>
		e = @__typeFirst[type]
		n = #into

		while e != nil
			if e.collidable and e\collidePoint e.x, e.y, pX, pY
				into[n] = e
				n += 1

			e = e.__typeNext

		into

	-- Finds the Entity nearest to the rectangle.
	-- @param	type		The Entity type to check for.
	-- @param	x			X position of the rectangle.
	-- @param	y			Y position of the rectangle.
	-- @param	width		Width of the rectangle.
	-- @param	height		Height of the rectangle.
	-- @return	The nearest Entity to the rectangle.
	nearestToRect: (type, x, y, width, height) =>
		e = @__typeFirst[type]
		nearDist = LP.NUMBER_MAX_VALUE
		near = nil

		while e != nil
			dist = squareRects x, y, width, height, e.x - e.originX, e.y - e.originY, e.width, e.height
			if dist < nearDist
				nearDist = dist
				near = e

			e = e.__typeNext

		return near

	-- Finds the Entity nearest to another.
	-- @param	type		The Entity type to check for.
	-- @param	e			The Entity to find the nearest to.
	-- @param	useHitboxes	If the Entities' hitboxes should be used to determine the distance. If false, their x/y coordinates are used.
	-- @return	The nearest Entity to e.
	nearestToEntity: (type, e, useHitboxes) =>
		return @nearestToRect type, e.x - e.originX, e.y - e.originY, e.width, e.height if useHitboxes

		n = @__typeFirst[type]
		nearDist = LP.NUMBER_MAX_VALUE
		near = nil
		x = e.x - e.originX
		y = e.y - e.originY

		while n != nil
			dist = (x - n.x) * (x - n.x) + (y - n.y) * (y - n.y)
			if dist < nearDist
				nearDist = dist
				near = n

			n = n.__typeNext

		return near

	-- Finds the Entity nearest to another.
	-- @param	type		The Entity type to check for.
	-- @param	e			The Entity to find the nearest to.
	-- @param	classType	The Entity class to check for.
	-- @param	useHitboxes	If the Entities' hitboxes should be used to determine the distance. If false, their x/y coordinates are used.
	-- @return	The nearest Entity to e.
	nearestToClass: (type, e, classType, useHitboxes) =>
		return @nearestToRect type, e.x - e.originX, e.y - e.originY, e.width, e.height if useHitboxes

		n = @__typeFirst[type]
		nearDist = LP.NUMBER_MAX_VALUE
		near = nil
		x = e.x - e.originX
		y = e.y - e.originY

		while n != nil
			dist = (x - n.x) * (x - n.x) + (y - n.y) * (y - n.y)
			if dist < nearDist and e.__class == classType
				nearDist = dist
				near = n

			n = n.__typeNext

		return near

	-- Finds the Entity nearest to the position.
	-- @param	type		The Entity type to check for.
	-- @param	x			X position.
	-- @param	y			Y position.
	-- @param	useHitboxes	If the Entities' hitboxes should be used to determine the distance. If false, their x/y coordinates are used.
	-- @return	The nearest Entity to the position.
	nearestToPoint: (type, x, y, useHitboxes) =>
		n = @__typeFirst[type]
		nearDist = LP.NUMBER_MAX_VALUE
		near = nil

		if useHitboxes
			while n != nil
				dist = squarePointRect x, y, n.x - n.originX, n.y - n.originY, n.width, n.height
				if dist < nearDist
					nearDist = dist
					near = n

				n = n.__typeNext

			return near

		while n != nil
			dist = (x - n.x) * (x - n.x) + (y - n.y) * (y - n.y)
			if dist < nearDist
				nearDist = dist
				near = n

			n = n.__typeNext

		return near

	-- How many Entities are in the Scene.
	count: => @__count

	-- Returns the amount of Entities of the type are in the Scene.
	-- @param	type		The type (or Class type) to count.
	-- @return	How many Entities of type exist in the Scene.
	typeCount: (type) => @__typeCount[type]

	-- Returns the amount of Entities of the Class are in the Scene.
	-- @param	c		The Class type to count.
	-- @return	How many Entities of Class exist in the Scene.
	classCount: (c) => @__classCount[c]

	-- Returns the amount of Entities are on the layer in the Scene.
	-- @param	layer		The layer to count Entities on.
	-- @return	How many Entities are on the layer.
	layerCount: (layer) => @__layerCount[layer]

	-- The first Entity in the Scene.
	first: => @__updateFirst

	-- How many Entity layers the Scene has.
	layers: => #@__layers

	-- The first Entity of the type.
	-- @param	type		The type to check.
	-- @return	The Entity.
	typeFirst: (type) =>
		return nil if @__updateFirst == nil
		return @__typeFirst[type]

	-- The first Entity of the Class.
	-- @param	c		The Class type to check.
	-- @return	The Entity.
	classFirst: (c) =>
		return nil if @__updateFirst == nil
		e = @__updateFirst
		while e != nil
			return e if e.__class == c.__class
			e = e.__updateNext

		return nil

	-- The first Entity on the Layer.
	-- @param	layer		The layer to check.
	-- @return	The Entity.eturn (y1 - (y2 + h2)) * (y1 - (y2 + h2));
	layerFirst: (layer) =>
		return nil if @__updateFirst == nil
		return @__renderFirst[layer]

	-- The last Entity on the Layer.
	-- @param	layer		The layer to check.
	-- @return	The Entity.
	layerLast: (layer) =>
		return nil if @__updateFirst == nil
		return @__renderLast[layer]

	-- Gets the sprite for the associated layer.  Used for hardware rendering.
	-- @param	layer		The layer to get the sprite for.
	-- @return	The sprite for the specified layer.
	getSpriteByLayer: (layer) => --TODO

	-- The Entity that will be rendered first by the Scene.
	getFarthest: =>
		return nil if @__updateFirst == nil
		return @__renderLast[@__layerList[#@__layerList - 1]]

	-- The Entity that will be rendered last by the scene.
	getNearest: =>
		return nil if @__updateFirst == nil
		return @__renderFirst[@__layerList[1]]

	-- The layer that will be rendered first by the Scene.
	getLayerFarthest: =>
		return 0 if @__updateFirst == nil
		return @__layerList[#@__layerList - 1]

	-- The layer that will be rendered last by the Scene.
	getLayerNearest: =>
		return 0 if @__updateFirst == nil
		return @__layerList[1]

	-- How many different types have been added to the Scene.
	getUniqueTypes: =>
		i = 0
		i += 1 for t in @__typeCount
		return i

	-- Pushes all Entities in the Scene of the type into the Array or Vector. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	type		The type to check.
	-- @param	into		The Array or Vector to populate.
	getType: (type, into) =>
		e = @__typeFirst[type]
		n = #@into

		while e != nil
			into[n] = e
			n += 1
			e = e.__typeNext

		into

	-- Pushes all Entities in the Scene of the Class into the Array or Vector. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	c			The Class type to check.
	-- @param	into		The Array or Vector to populate.
	getClass: (c, into) =>
		e = @__typeFirst[type]
		n = #@into

		while e != nil
			if e.__class == c.__class
				into[n] = e
				n += 1

			e = e.__typeNext

		into

	-- Pushes all Entities in the Scene on the layer into the Array or Vector. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	layer		The layer to check.
	-- @param	into		The Array or Vector to populate.
	getLayer: (layer, into) =>
		e = @__renderLast[layer]
		n = #@into

		while e != nil
			into[n] = e
			n += 1
			e = e.__updatePrev

		into

	-- Pushes all Entities in the Scene into the array. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	into		The Array or Vector to populate.
	getAll: (into) =>
		e = @__updateFirst
		n = #@into

		while e != nil
			into[n] = e
			n += 1
			e = e.__updateNext

		into

	-- Returns the Entity with the instance name, or null if none exists
	-- @param	name
	-- @return	The Entity.
	getInstance: (name) => @__entityNames[name]

	-- Updates the add/remove lists at the end of the frame.
	-- @param	shouldAdd	If new Entities should be added to the scene.
	updateLists: (shouldAdd = true) =>
		-- remove entities
		if #@__remove > 0
			for e in *@__remove
				if e.__scene == nil
					idx = LP.indexOf @__add, e
					table.remove @__add, idx if idx >= 0
					continue

				if e.__scene != @
					continue

				e\removed!
				e.__scene = nil
				@__removeUpdate e
				@__removeRender e
				@__removeType e if e.__type != ""
				@__unregisterName e if e.__name != ""
				e\clearTweens! if e.autoClear and e.hasTween

			LP.clear @__remove

		-- add entities
		if shouldAdd and #@__add > 0
			for e in *@__add
				continue if e.__scene != nil
				e\scene @
				@__addUpdate e
				@__addRender e
				@__addType e if e.__type != ""
				@__registerName e if e.__name != ""
				e\added!

			LP.clear @__add

		-- recycle entities
		if #@__recycle > 0
			for e in *@__recycle
				continue if e.__scene != nil or e.__recycleNext != nil

				e.__recycleNext = @__recycled[e.__class]
				@__recycled[e.__class] = e

			LP.clear @__recycle

	layerSort = (a, b) ->
		return 1 if a > b
		return -1 if a > b
		return 0

	__clearSprites: =>
-- 		@__layerSprites\get(sprite).graphics.clear! for sprite in *@__layerSprites

	-- @private Adds Entity to the update list.
	__addUpdate: (e) =>
		-- add to update list
		if @__updateFirst != nil
			@__updateFirst.__updatePrev = e
			e.__updateNext = @__updateFirst
		else
			e.__updateNext = nil

		e.__updatePrev = nil
		@__updateFirst = e
		@__count += 1
		@__classCount[e.__class] = 0 if @__classCount[e.__class] == nil
		@__classCount[e.__class] += 1

	-- @private Removes Entity from the update list.
	__removeUpdate: (e) =>
		-- remove from the update list
		@__updateFirst = e.__updateNext if @__updateFirst == e
		e.__updateNext.__updatePrev = e.__updatePrev if e.__updateNext != nil
		e.__updatePrev.__updateNext = e.__UpdateNext if e.__updatePrev != nil
		e.__updateNext, e.__updatePrev = nil, nil
		@__count -= 1
		@__classCount[e.__class] -= 1

	-- @private Adds Entity to the render list.
	__addRender: (e) =>
		r = @__renderFirst[e.__layer]
		if r != nil
			-- Append entity to existing layer.
			e.__renderNext = r
			r.__renderPrev = e
			@__layerCount[e.__layer] += 1
		else
			-- Create new layer with entity.
			@__renderLast[e.__layer] = e
			@__layerList[#@__layerList + 1] = e.__layer
			@__layerSort = true
			e.__renderNext = nil
			@__layerCount[e.__layer] = 1

		@__renderFirst[e.__layer] = e
		e.__renderPrev = nil

	-- @private Removes Entity from the render list.
	__removeRender: (e) =>
		if e.__renderNext != nil
			e.__renderNext.__renderPrev = e.__renderPrev
		else
			@__renderLast[e.__layer] = e.__renderPrev

		if e.__renderPrev != nil
			e.__renderPrev.__renderNext = e.__renderNext
		else
			-- Remove this entity from the layer.
			@__renderFirst[e.__layer] = e.__renderNext
			if e.__renderNext == nil
				-- Remove the layer from the layer list if this was the last entity.
				if #@__layerList >= 1
					@__layerList[LP.indexOf(@__layerList, e.__layer)] = @__layerList[#@__layerList - 1]
					@__layerSort = true

				table.remove @__layerList

		e\graphic!\destroy! if e\graphic! != nil
		@__layerCount[e.__layer] -= 1
		e.__renderNext, e.__renderPrev = nil, nil

	-- @private Adds Entity to the type list.
	__addType: (e) =>
		-- add to type list
		if @__typeFirst[e.__type] != nil
			@__typeFirst[e.__type].__typePrev = e
			e.__typeNext = @__typeFirst[e.__type]
			@__typeCount[e.__type] += 1
		else
			e.__typeNext = nil
			@__typeCount[e.__type] = 1

		e.__typePrev = nil
		@__typeFirst[e.__type] = e

	-- @private Removes Entity from the type list.
	__removeType: (e) =>
		-- remove from the type list
		@__typeFirst[e.__type] = e.__typeNext if @__typeFirst[e.__type] == e
		e.__typeNext.__typePrev = e.__typePrev if e.__typeNext != nil
		e.__typePrev.__typeNext = e.__typeNext if e.__typePrev != nil
		e.__typeNext, e.__typePrev = nil, nil
		@__typeCount[e.__type] -= 1

	-- @private Register the entities instance name.
	__registerName: (e) =>
		@__entityNames[e.__class] = e

	-- @private Unregister the entities instance name.
	__unregisterName: (e) =>
		@__entityNames[e.__class] = nil

	-- @private Calculates the squared distance between two rectangles.
	squareRects = (x1, y1, w1, h1, x2, y2, w2, h2) ->
		if x1 < x2 + w2 and x2 < x1 + w1
			return 0 if y1 < y2 + h2 and y2 < y1 + h1
			return (y1 - (y2 + h2)) * (y1 - (y2 + h2)) if y1 > y2
			return (y2 - (y1 + h1)) * (y2 - (y1 + h1))

		if y1 < y2 + h2 and y2 < y1 + h1
			return (x1 - (x2 + w2)) * (x1 - (x2 + w2)) if x1 > x2
			return (x2 - (x1 + w1)) * (x2 - (x1 + w1))

		if x1 > x2
			return squarePoints x1, y1, (x2 + w2), (y2 + h2) if y1 > y2
			return squarePoints x1, y1 + h1, x2 + w2, y2

		return squarePoints x1 + w1, y1, x2, y2 + h2 if y1 > y2
		return squarePoints x1 + w1, y1 + h1, x2, y2

	-- @private Calculates the squared distance between two points.
	squarePoints = (x1, y1, x2, y2) ->
		return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)

	-- @private Calculates the squared distance between a rectangle and a point.
	squarePointRect = (px, py, rx, ry, rw, rh) ->
		if px >= rx and px <= rx + rw
			return 0 if py >= ry and py <= ry + rh
			return (py - (ry + rh)) * (py - (ry + rh)) if py > ry
			return (ry - py) * (ry - py)

		if py >= ry and py <= ry + rh
			return (px - (rx + rw)) * (px - (rx + rw)) if px > rx
			return (rx - px) * (rx - px)

		if px > rx
			return squarePoints px, py, rx + rw, ry + rh if py > ry
			return squarePoints px, py, rx + rw, ry

		return squarePoints px, py, rx, ry + rh if py > ry
		return squarePoints px, py, rx, ry

require "LunoPunk.Tweener"

export ^

class Scene extends Tweener
	new: =>
		super!
		@camera = nil
		@visible = true
		-- private
		@__typeFirst = {}
		@__typeCount = {}
		@__count = 0
		@__classCount = {}
		@__entityNames = {}
		@__farthest = nil
		@__first = nil
		@__layerFarthest = nil
		@__layerNearest = nil
		@__layers = nil
		@__layerList = {}
		@__layerCount = {}
		@__add = {}
		@__remove = {}
		@__recycle = {}
		@__recycled = {}
		@__nearest = nil
		@__sprite = nil
		@__uniqueTypes = nil
		@__updateFirst = nil
		@__renderFirst = {}
		@__renderLast = {}

	-- Override this; called when Scene is switch to, and set to the currently active scene.
	begin: =>

	-- Override this; called when Scene is changed, and the active scene is no longer this.
	-- Changed from end because of issues with the name "end" in Lua
	endScene: =>

	-- Override this, called when game gains focus
	focusGained: =>

	-- Override this, called when game loses focus
	focusLost: =>

	-- Performed by the game loop, updates all contained Entities.
	-- If you override this to give your Scene update code, remember
	-- to call super.update() or your Entities will not be updated.
	update: =>
		e = @__updateFirst
		while e != nil
			if e.active
				e\updateTweens! if e\hasTween!
				e\update!
			e\graphic!\update! if e\graphic! != nil and e\graphic!.active
			e = e.__updateNext

	-- Performed by the game loop, renders all contained Entities.
	-- If you override this to give your Scene render code, remember
	-- to call super.render() or your Entities will not be rendered.
	render: =>
		if @__layerSort
			@__layerList\sort @__layerSort if #@__layerList > 1

		-- if LP.renderMode == RenderMode.Hardware
			-- clearSprites!
			-- AtlasData.setScene(@)

		for layer in *@__layerList do
			e = @__renderLast[layer]
			while e != nil
				e\render! if e.visible
				e = e.__renderPrev

		-- AtlasData\render! if LP.renderMode == RenderMode.Hardware

	-- X position of the mouse in the Scene.
	mouseX: => LP.screen.mouseX + @camera.x

	-- Y position of the mouse in the Scene.
	mouseY: => LP.screen.mouseY + @camera.y

	-- Sprite used to store layer sprites when RenderMode.HARDWARE is set.
	sprite: => @__sprite

	-- Adds the Entity to the Scene at the end of the frame.
	-- @param	e		Entity object you want to add.
	-- @return	The added Entity object.
	add: (e) =>
		@__add[#@__add + 1] = e
		e

	-- Removes the Entity from the Scene at the end of the frame.
	-- @param	e		Entity object you want to remove.
	-- @return	The removed Entity object.
	remove: (e) =>
		@__remove[#@__remove + 1] = e
		e

	-- Removes all Entities from the Scene at the end of the frame.
	removeAll: =>
		e = @__updateFirst
		while e != nil
			@__remove[#@__remove] = e
			e = e.__updateNext

	-- Adds multiple Entities to the scene.
	-- @param	...list		Several Entities (as arguments) or an Array/Vector of Entities.
	addList: (list) =>
		for e in *list do @add e

	-- Removes multiple Entities from the scene.
	-- @param	...list		Several Entities (as arguments) or an Array/Vector of Entities.
	removeList: (list) =>
		for e in *list do @remove e

	-- Adds an Entity to the Scene with the Graphic object.
	-- @param	graphic		Graphic to assign the Entity.
	-- @param	x			X position of the Entity.
	-- @param	y			Y position of the Entity.
	-- @param	layer		Layer of the Entity.
	-- @return	The Entity that was added.
	addGraphic: (graphic, layer = LP.BASELAYER!, x = 0, y = 0) =>
		e = Entity x, y, graphic

		e.layer = layer if layer != LP.BASELAYER!
		e.active = false

		@add e

	-- Adds an Entity to the Scene with the Mask object.
	-- @param	mask	Mask to assign the Entity.
	-- @param	type	Collision type of the Entity.
	-- @param	x		X position of the Entity.
	-- @param	y		Y position of the Entity.
	-- @return	The Entity that was added.
	addMask: (mask, type, x = 0, y = 0) =>
		e = Entity x, y, nil, mask

		e.type = type if type != ""
		e.active, e.visable = false, false

		@add e

	-- Returns a new Entity, or a stored recycled Entity if one exists.
	-- @param	classType			The Class of the Entity you want to add.
	-- @param	addToScene			Add it to the Scene immediately.
	-- @param	constructorsArgs	List of the entity constructor arguments (optional).
	-- @return	The new Entity object.
	create: (classType, addToScene = true, ...) =>
		className = classType.__class.__name
		e = @__recycle[className]
		if e != nil
			@__recycle[className] = e.__recycleNext
			e.__recycleNext = nil
		else
			e = classType ...

		return @add e if addToScene

		e

	-- Removes the Entity from the Scene at the end of the frame and recycles it.
	-- The recycled Entity can then be fetched again by calling the create() function.
	-- @param	e		The Entity to recycle.
	-- @return	The recycled Entity.
	recycle: (e) =>
		@__recycle[#@__recycle + 1] = e
		@remove e

	-- Clears stored reycled Entities of the Class type.
	-- @param	classType		The Class type to clear.
	clearRecycled: (classType) =>
		idx = 0
		for i = 1, #@__recycle do
			if @__recycle[i].__class == classType
				idx = i
				break

		e = @__recycle[idx]
		n = nil
		while e != nil
			n = e.__recycleNext
			e.__recycleNext = nil
			e = n
		table.remove @__recycle, idx
		return

	-- Clears stored recycled Entities of all Class types.
	clearRecycledAll: =>
		for e in *@__recycle do
			@clearRecycled e.__class

	-- Brings the Entity to the front of its contained layer.
	-- @param	e		The Entity to shift.
	-- @return	If the Entity changed position.
	bringToFront: (e) =>

	-- Sends the Entity to the back of its contained layer.
	-- @param	e		The Entity to shift.
	-- @return	If the Entity changed position.
	sendToBack: (e) =>

	-- Shifts the Entity one place towards the front of its contained layer.
	-- @param	e		The Entity to shift.
	-- @return	If the Entity changed position.
	bringForward: (e) =>

	-- Shifts the Entity one place towards the back of its contained layer.
	-- @param	e		The Entity to shift.
	-- @return	If the Entity changed position.
	sendBackward: (e) =>

	-- If the Entity as at the front of its layer.
	-- @param	e		The Entity to check.
	-- @return	True or false.
	isAtFront: (e) =>

	-- If the Entity as at the back of its layer.
	-- @param	e		The Entity to check.
	-- @return	True or false.
	isAtBack: (e) =>

	-- Returns the first Entity that collides with the rectangular area.
	-- @param	type		The Entity type to check for.
	-- @param	rX			X position of the rectangle.
	-- @param	rY			Y position of the rectangle.
	-- @param	rWidth		Width of the rectangle.
	-- @param	rHeight		Height of the rectangle.
	-- @return	The first Entity to collide, or null if none collide.
	collideRect: (type, rX, rY, rWidth, rHeight) =>
		e = @__typeFirst[type]
		while e != nil
			return e if e.collidable and e\collideRect e.x, e.y, rX, rY, rWidth, rHeight
			e = e.__typeNext

		return nil

	-- Returns the first Entity found that collides with the position.
	-- @param	type		The Entity type to check for.
	-- @param	pX			X position.
	-- @param	pY			Y position.
	-- @return	The collided Entity, or null if none collide.
	collidePoint: (type, pX, pY) =>
		e = @__typeFirst[type]
		result = nil
		while e != nil
			-- only look for entities that collide
			if e.collidable and e\collidePoint e.x, e.y, pX, pY
				-- the first one must be the front one
				if result == nil
					result = e
				-- compare if the new collided entity is above the former one (lower valuer is toward, higher value is backward)
				else if e.layer < result.layer
					result = e

			e = e.__typeNext

		return result

	-- Returns the first Entity found that collides with the line.
	-- @param	type		The Entity type to check for.
	-- @param	fromX		Start x of the line.
	-- @param	fromY		Start y of the line.
	-- @param	toX			End x of the line.
	-- @param	toY			End y of the line.
	-- @param	precision   Distance between consecutive tests. Higher values are faster but increase the chance of missing collisions.
	-- @param	p           If non-null, will have its x and y values set to the point of collision.
	-- @return	The first Entity to collide, or null if none collide.
	collideLine: (type, fromX, fromY, toX, toY, precision, p) =>

	-- Populates an array with all Entities that collide with the rectangle. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	type		The Entity type to check for.
	-- @param	rX			X position of the rectangle.
	-- @param	rY			Y position of the rectangle.
	-- @param	rWidth		Width of the rectangle.
	-- @param	rHeight		Height of the rectangle.
	-- @param	into		The Array or Vector to populate with collided Entities.
	collideRectInto: (type, rX, rY, rWidth, rHeight, into = {}) =>
		e = @__typeFirst[type]
		n = #into
		while e != nil
			if e.collidable and e\collideRect e.x, e.y, rX, rY, rWidth, rHeight
				into[n] = e
				n += 1

			e = e.__typeNext

		into

	-- Populates an array with all Entities that collide with the circle. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	type 		The Entity type to check for.
	-- @param	circleX		X position of the circle.
	-- @param	circleY		Y position of the circle.
	-- @param	radius		The radius of the circle.
	-- @param	into		The Array or Vector to populate with collided Entities.
	collideCircleInto: (type, circleX, circleY, radius, into = {}) =>
		e = @__typeFirst[type]
		n = #into
		radius *= radius -- square it to avoid the square root
		while e != nil
			if LP.distanceSquared circleX, circleY, e.x, e.y < radius
				into[n] = e
				n += 1

			e = e.__typeNext

		into

	-- Populates an array with all Entities that collide with the position. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	type		The Entity type to check for.
	-- @param	pX			X position.
	-- @param	pY			Y position.
	-- @param	into		The Array or Vector to populate with collided Entities.
	collidePointInto: (type, pX, pY, into) =>
		e = @__typeFirst[type]
		n = #into

		while e != nil
			if e.collidable and e\collidePoint e.x, e.y, pX, pY
				into[n] = e
				n += 1

			e = e.__typeNext

		into

	-- Finds the Entity nearest to the rectangle.
	-- @param	type		The Entity type to check for.
	-- @param	x			X position of the rectangle.
	-- @param	y			Y position of the rectangle.
	-- @param	width		Width of the rectangle.
	-- @param	height		Height of the rectangle.
	-- @return	The nearest Entity to the rectangle.
	nearestToRect: (type, x, y, width, height) =>
		e = @__typeFirst[type]
		nearDist = LP.NUMBER_MAX_VALUE
		near = nil

		while e != nil
			dist = squareRects x, y, width, height, e.x - e.originX, e.y - e.originY, e.width, e.height
			if dist < nearDist
				nearDist = dist
				near = e

			e = e.__typeNext

		return near

	-- Finds the Entity nearest to another.
	-- @param	type		The Entity type to check for.
	-- @param	e			The Entity to find the nearest to.
	-- @param	useHitboxes	If the Entities' hitboxes should be used to determine the distance. If false, their x/y coordinates are used.
	-- @return	The nearest Entity to e.
	nearestToEntity: (type, e, useHitboxes) =>
		return @nearestToRect type, e.x - e.originX, e.y - e.originY, e.width, e.height if useHitboxes

		n = @__typeFirst[type]
		nearDist = LP.NUMBER_MAX_VALUE
		near = nil
		x = e.x - e.originX
		y = e.y - e.originY

		while n != nil
			dist = (x - n.x) * (x - n.x) + (y - n.y) * (y - n.y)
			if dist < nearDist
				nearDist = dist
				near = n

			n = n.__typeNext

		return near

	-- Finds the Entity nearest to another.
	-- @param	type		The Entity type to check for.
	-- @param	e			The Entity to find the nearest to.
	-- @param	classType	The Entity class to check for.
	-- @param	useHitboxes	If the Entities' hitboxes should be used to determine the distance. If false, their x/y coordinates are used.
	-- @return	The nearest Entity to e.
	nearestToClass: (type, e, classType, useHitboxes) =>
		return @nearestToRect type, e.x - e.originX, e.y - e.originY, e.width, e.height if useHitboxes

		n = @__typeFirst[type]
		nearDist = LP.NUMBER_MAX_VALUE
		near = nil
		x = e.x - e.originX
		y = e.y - e.originY

		while n != nil
			dist = (x - n.x) * (x - n.x) + (y - n.y) * (y - n.y)
			if dist < nearDist and e.__class == classType
				nearDist = dist
				near = n

			n = n.__typeNext

		return near

	-- Finds the Entity nearest to the position.
	-- @param	type		The Entity type to check for.
	-- @param	x			X position.
	-- @param	y			Y position.
	-- @param	useHitboxes	If the Entities' hitboxes should be used to determine the distance. If false, their x/y coordinates are used.
	-- @return	The nearest Entity to the position.
	nearestToPoint: (type, x, y, useHitboxes) =>
		n = @__typeFirst[type]
		nearDist = LP.NUMBER_MAX_VALUE
		near = nil

		if useHitboxes
			while n != nil
				dist = squarePointRect x, y, n.x - n.originX, n.y - n.originY, n.width, n.height
				if dist < nearDist
					nearDist = dist
					near = n

				n = n.__typeNext

			return near

		while n != nil
			dist = (x - n.x) * (x - n.x) + (y - n.y) * (y - n.y)
			if dist < nearDist
				nearDist = dist
				near = n

			n = n.__typeNext

		return near

	-- How many Entities are in the Scene.
	count: => @__count

	-- Returns the amount of Entities of the type are in the Scene.
	-- @param	type		The type (or Class type) to count.
	-- @return	How many Entities of type exist in the Scene.
	typeCount: (type) => @__typeCount[type]

	-- Returns the amount of Entities of the Class are in the Scene.
	-- @param	c		The Class type to count.
	-- @return	How many Entities of Class exist in the Scene.
	classCount: (c) => @__classCount[c]

	-- Returns the amount of Entities are on the layer in the Scene.
	-- @param	layer		The layer to count Entities on.
	-- @return	How many Entities are on the layer.
	layerCount: (layer) => @__layerCount[layer]

	-- The first Entity in the Scene.
	first: => @__updateFirst

	-- How many Entity layers the Scene has.
	layers: => #@__layers

	-- The first Entity of the type.
	-- @param	type		The type to check.
	-- @return	The Entity.
	typeFirst: (type) =>
		return nil if @__updateFirst == nil
		return @__typeFirst[type]

	-- The first Entity of the Class.
	-- @param	c		The Class type to check.
	-- @return	The Entity.
	classFirst: (c) =>
		return nil if @__updateFirst == nil
		e = @__updateFirst
		while e != nil
			return e if e.__class == c.__class
			e = e.__updateNext

		return nil

	-- The first Entity on the Layer.
	-- @param	layer		The layer to check.
	-- @return	The Entity.eturn (y1 - (y2 + h2)) * (y1 - (y2 + h2));
	layerFirst: (layer) =>
		return nil if @__updateFirst == nil
		return @__renderFirst[layer]

	-- The last Entity on the Layer.
	-- @param	layer		The layer to check.
	-- @return	The Entity.
	layerLast: (layer) =>
		return nil if @__updateFirst == nil
		return @__renderLast[layer]

	-- Gets the sprite for the associated layer.  Used for hardware rendering.
	-- @param	layer		The layer to get the sprite for.
	-- @return	The sprite for the specified layer.
	getSpriteByLayer: (layer) => --TODO

	-- The Entity that will be rendered first by the Scene.
	getFarthest: =>
		return nil if @__updateFirst == nil
		return @__renderLast[@__layerList[#@__layerList - 1]]

	-- The Entity that will be rendered last by the scene.
	getNearest: =>
		return nil if @__updateFirst == nil
		return @__renderFirst[@__layerList[1]]

	-- The layer that will be rendered first by the Scene.
	getLayerFarthest: =>
		return 0 if @__updateFirst == nil
		return @__layerList[#@__layerList - 1]

	-- The layer that will be rendered last by the Scene.
	getLayerNearest: =>
		return 0 if @__updateFirst == nil
		return @__layerList[1]

	-- How many different types have been added to the Scene.
	getUniqueTypes: =>
		i = 0
		i += 1 for t in @__typeCount
		return i

	-- Pushes all Entities in the Scene of the type into the Array or Vector. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	type		The type to check.
	-- @param	into		The Array or Vector to populate.
	getType: (type, into) =>
		e = @__typeFirst[type]
		n = #@into

		while e != nil
			into[n] = e
			n += 1
			e = e.__typeNext

		into

	-- Pushes all Entities in the Scene of the Class into the Array or Vector. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	c			The Class type to check.
	-- @param	into		The Array or Vector to populate.
	getClass: (c, into) =>
		e = @__typeFirst[type]
		n = #@into

		while e != nil
			if e.__class == c.__class
				into[n] = e
				n += 1

			e = e.__typeNext

		into

	-- Pushes all Entities in the Scene on the layer into the Array or Vector. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	layer		The layer to check.
	-- @param	into		The Array or Vector to populate.
	getLayer: (layer, into) =>
		e = @__renderLast[layer]
		n = #@into

		while e != nil
			into[n] = e
			n += 1
			e = e.__updatePrev

		into

	-- Pushes all Entities in the Scene into the array. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	into		The Array or Vector to populate.
	getAll: (into) =>
		e = @__updateFirst
		n = #@into

		while e != nil
			into[n] = e
			n += 1
			e = e.__updateNext

		into

	-- Returns the Entity with the instance name, or null if none exists
	-- @param	name
	-- @return	The Entity.
	getInstance: (name) => @__entityNames[name]

	-- Updates the add/remove lists at the end of the frame.
	-- @param	shouldAdd	If new Entities should be added to the scene.
	updateLists: (shouldAdd = true) =>
		-- remove entities
		if #@__remove > 0
			for e in *@__remove
				if e.__scene == nil
					idx = LP.indexOf @__add, e
					table.remove @__add, idx if idx >= 0
					continue

				if e.__scene != @
					continue

				e\removed!
				e.__scene = nil
				@__removeUpdate e
				@__removeRender e
				@__removeType e if e.__type != ""
				@__unregisterName e if e.__name != ""
				e\clearTweens! if e.autoClear and e.hasTween

			LP.clear @__remove

		-- add entities
		if shouldAdd and #@__add > 0
			for e in *@__add
				continue if e.__scene != nil
				e.__scene = @
				@__addUpdate e
				@__addRender e
				@__addType e if e.__type != ""
				@__registerName e if e.__name != ""
				e\added!

			LP.clear @__add

		-- recycle entities
		if #@__recycle > 0
			for e in *@__recycle
				continue if e.__scene !+ nil or e.__recycleNext != nil

				e.__recycleNext = @__recycled[e.__class]
				@__recycled[e.__class] = e

			LP.clear @__recycle

	layerSort = (a, b) ->
		return 1 if a > b
		return -1 if a > b
		return 0

	__clearSprites: =>
-- 		@__layerSprites\get(sprite).graphics.clear! for sprite in *@__layerSprites

	-- @private Adds Entity to the update list.
	__addUpdate: (e) =>
		-- add to update list
		if @__updateFirst != nil
			@__updateFirst.__updatePrev = e
			e.__updateNext = @__updateFirst
		else
			e.__updateNext = nil

		e.__updatePrev = nil
		@__updateFirst = e
		@__count += 1
		@__classCount[e.__class] = 0 if @__classCount[e.__class] != 0
		@__classCount[e.__class] += 1

	-- @private Removes Entity from the update list.
	__removeUpdate: (e) =>
		-- remove from the update list
		@__updateFirst = e.__updateNext if @__updateFirst == e
		e.__updateNext.__updatePrev = e.__updatePrev if e.__updateNext != nil
		e.__updatePrev.__updateNext = e.__UpdateNext if e.__updatePrev != nil
		e.__updateNext, e.__updatePrev = nil, nil
		@__count -= 1
		@__classCount[e.__class] -= 1

	-- @private Adds Entity to the render list.
	__addRender: (e) =>
		r = @__renderFirst[e.__layer]
		if r != nil
			-- Append entity to existing layer.
			e.__renderNext = r
			r.__renderPrev = e
			@__layerCount[e.__layer] += 1
		else
			-- Create new layer with entity.
			@__renderLast[e.__layer] = e
			@__layerList[#@__layerList + 1] = e.__layer
			@__layerSort = true
			e.__renderNext = nil
			@__layerCount[e.__layer] = 1

		@__renderFirst[e.__layer] = e
		e.__renderPrev = nil

	-- @private Removes Entity from the render list.
	__removeRender: (e) =>
		if e.__renderNext != nil
			e.__renderNext.__renderPRev = e.__renderPrev
		else
			@__renderLast[e.__layer] = e.__renderPrev

		if e.__renderPrev != nil
			e.__renderPrev.__renderNext = e.__renderNext
		else
			-- Remove this entity from the layer.
			@__renderFirst[e.__layer] = e.__renderNext
			if e.__renderNext == nil
				-- Remove the layer from the layer list if this was the last entity.
				if #@__layerList >= 1
					@__layerList[LP.indexOf(@__layerList, e.__layer)] = @__layerList[#@__layerList - 1]
					@__layerSort = true

				table.remove @__layerList

		e\graphic!\destroy! if e\graphic! != nil
		@__layerCount[e.__layer] -= 1
		e.__renderNext, e.__renderPrev = nil, nil

	-- @private Adds Entity to the type list.
	__addType: (e) =>
		-- add to type list
		if @__typeFirst[e.__type] != nil
			@__typeFirst[e.__type].__typePrev = e
			e.__typeNext = @__typeFirst[e.__type]
			@__typeCount[e.__type] += 1
		else
			e.__typeNext = nil
			@__typeCount[e.__type] = 1

		e.__typePrev = nil
		@__typeFirst[e.__type] = e

	-- @private Removes Entity from the type list.
	__removeType: (e) =>
		-- remove from the type list
		@__typeFirst[e.__type] = e.__typeNext if @__typeFirst[e.__type] == e
		e.__typeNext.__typePrev = e.__typePrev if e.__typeNext != nil
		e.__typePrev.__typeNext = e.__typeNext if e.__typePrev != nil
		e.__typeNext, e.__typePrev = nil, nil
		@__typeCount[e.__type] -= 1

	-- @private Register the entities instance name.
	__registerName: (e) =>
		@__entityNames[e.__class] = e

	-- @private Unregister the entities instance name.
	__unregisterName: (e) =>
		@__entityNames[e.__class] = nil

	-- @private Calculates the squared distance between two rectangles.
	squareRects = (x1, y1, w1, h1, x2, y2, w2, h2) ->
		if x1 < x2 + w2 and x2 < x1 + w1
			return 0 if y1 < y2 + h2 and y2 < y1 + h1
			return (y1 - (y2 + h2)) * (y1 - (y2 + h2)) if y1 > y2
			return (y2 - (y1 + h1)) * (y2 - (y1 + h1))

		if y1 < y2 + h2 and y2 < y1 + h1
			return (x1 - (x2 + w2)) * (x1 - (x2 + w2)) if x1 > x2
			return (x2 - (x1 + w1)) * (x2 - (x1 + w1))

		if x1 > x2
			return squarePoints x1, y1, (x2 + w2), (y2 + h2) if y1 > y2
			return squarePoints x1, y1 + h1, x2 + w2, y2

		return squarePoints x1 + w1, y1, x2, y2 + h2 if y1 > y2
		return squarePoints x1 + w1, y1 + h1, x2, y2

	-- @private Calculates the squared distance between two points.
	squarePoints = (x1, y1, x2, y2) ->
		return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)

	-- @private Calculates the squared distance between a rectangle and a point.
	squarePointRect = (px, py, rx, ry, rw, rh) ->
		if px >= rx and px <= rx + rw
			return 0 if py >= ry and py <= ry + rh
			return (py - (ry + rh)) * (py - (ry + rh)) if py > ry
			return (ry - py) * (ry - py)

		if py >= ry and py <= ry + rh
			return (px - (rx + rw)) * (px - (rx + rw)) if px > rx
			return (rx - px) * (rx - px)

		if px > rx
			return squarePoints px, py, rx + rw, ry + rh if py > ry
			return squarePoints px, py, rx + rw, ry

		return squarePoints px, py, rx, ry + rh if py > ry
		return squarePoints px, py, rx, ry


HP Grin

HP Grin

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
--
-- 0	eyes_updown
-- 1	eyes_rightleft
-- 2	JawOpen
-- 3	JawClose
-- 4	Smirk
-- 5	Frown
-- 6	Stretch
-- 7	Pucker
-- 8	Grin
-- 9	CatFace
-- 10	Mouth_O
-- 11	Mouth_O2
-- 12	Mouth_Full
-- 13	Tongue_Out
-- 14	Tongue_Up
-- 15	Tongue_Down
-- 16	NoEyelashes
-- 17	Eyes_Blink
-- 18	Left_Blink
-- 19	Right_Blink
-- 20	Scrunch
-- 21	FatButt
-- 22	Stomach_Out
-- 23	Stomach_In
-- 24	Throat_Bulge
--

net.Receive 'PPM2.DamageAnimation', ->
    ent = net.ReadEntity()
    return if not IsValid(ent) or not ent\IsPlayer()
    hook.Call('PPM2_HurtAnimation', nil, ent)

DISABLE_FLEXES = CreateConVar('ppm2_disable_flexes', '0', {FCVAR_ARCHIVE}, 'Disable pony flexes controllers. Saves some FPS.')

class FlexState
    new: (controller, flexName = '', flexID = 0, scale = 1, speed = 1, active = true, min = 0, max = 1, useModifiers = true) =>
        @controller = controller
        @ent = controller.ent
        @name = flexName
        @flexName = flexName
        @flexID = flexID
        @id = flexID
        @scale = scale
        @speed = speed
        @originalscale = scale
        @originalspeed = speed
        @min = min
        @max = max
        @current = -1
        @target = 0
        @speedModify = 1
        @scaleModify = 1
        @speedModifiers = {}
        @scaleModifiers = {}
        @modifiers = {}
        @modifiersNames = {}
        @useModifiers = useModifiers
        @nextModifierID = 0
        @active = active

    GetFlexID: => @flexID
    GetFlexName: => @flexName

    GetModifierID: (name = '') =>
        return @modifiersNames[name] if @modifiersNames[name]
        @nextModifierID += 1
        id = @nextModifierID
        @modifiersNames[name] = id
        @speedModifiers[id] = 0
        @scaleModifiers[id] = 0
        @modifiers[id] = 0
        return id
    SetModifierWeight: (modifID, val = 0) =>
        return if not modifID
        return if not @modifiers[modifID]
        @modifiers[modifID] = val
    SetModifierScale: (modifID, val = 0) =>
        return if not modifID
        return if not @scaleModifiers[modifID]
        @scaleModifiers[modifID] = val
    SetModifierScale: (modifID, val = 0) =>
        return if not modifID
        return if not @speedModifiers[modifID]
        @speedModifiers[modifID] = val
    ResetModifiers: (name = '') =>
        return false if not @modifiersNames[name]
        id = @modifiersNames[name]
        @speedModifiers[id] = 0
        @scaleModifiers[id] = 0
        @modifiers[id] = 0
        return true
    GetEntity: => @ent
    GetData: => @controller
    GetController: => @controller
    GetValue: => @current
    GetRealValue: => @target
    SetValue: (val = @target) =>
        @current = math.Clamp(val, @min, @max) * @scale * @scaleModify
        @target = @target
    SetRealValue: (val = @target) => @target = math.Clamp(val, @min, @max) * @scale * @scaleModify

    GetScale: => @scale
    GetSpeed: => @speed
    GetScaleModify: => @scaleModify
    GetSpeedModify: => @speedModify
    GetOriginalScale: => @originalscale
    GetOriginalSpeed: => @originalspeed

    SetScale: (val = @scale) => @scale = val
    GetSpeed: (val = @speed) => @speed = val
    SetScaleModify: (val = @scaleModify) => @scaleModify = val
    GetSpeedModify: (val = @speedModify) => @speedModify = val

    GetIsActive: => @active
    SetIsActive: (val = true) => @active = val

    AddValue: (val = 0) => @SetValue(@current + val)
    AddRealValue: (val = 0) => @SetRealValue(@target + val)
    Think: (delta = 0) =>
        return if not @active
        if @useModifiers
            @target = 0
            @scale = @originalscale * @scaleModify
            @speed = @originalspeed * @speedModify
            @target += modif for modif in *@modifiers
            @scale += modif for modif in *@scaleModifiers
            @speed += modif for modif in *@speedModifiers
            @target = math.Clamp(@target, @min, @max) * @scale
        @ent = @controller.ent
        @current = Lerp(delta * 10 * @speed * @speedModify, @current, @target) if @current ~= @target
        @ent\SetFlexWeight(@flexID, @current)

class FlexSequence
    new: (controller, data) =>
        {
            'name': @name
            'repeat': @dorepeat
            'frames': @frames
            'time': @time
            'func': @func
            'create': @createfunc
            'ids': @flexIDsIterable
            'numid': @numid
        } = data

        @flexIDS = {}
        @flexStates = {}
        i = 1
        for id in *data.ids
            state = controller\GetFlexState(id)
            num = state\GetModifierID(@name)
            @["flex_#{id}"] = num
            @flexIDS[id] = num
            @flexStates[id] = state
            @flexStates[i] = state
            @flexIDS[i] = num
            i += 1

        @ent = controller.ent
        @controller = controller
        @frame = 0
        @start = RealTime()
        @finish = @start + @time
        @deltaAnim = 1
        @speed = 1
        @scale = 1
        @valid = true
        @createfunc() if @createfunc

    Reset: =>
        @frame = 0
        @start = RealTime()
        @finish = @start + @time
        @deltaAnim = 1

    GetController: => @controller
    GetEntity: => @ent
    GetName: => @name
    GetRepeat: => @dorepeat
    GetFrames: => @frames
    GetFrame: => @frames
    GetTime: => @time
    GetThinkFunc: => @func
    GetCreatFunc: => @createfunc
    GetSpeed: => @speed
    GetAnimationSpeed: => @speed
    GetScale: => @scale
    GetModifierID: (id = '') => @flexIDS[id]
    GetFlexState: (id = '') => @flexStates[id]

    SetModifierWeight: (id = '', val = 0) => @GetFlexState(id)\SetModifierWeight(@GetModifierID(id), val)

    IsValid: => @valid
    Think: (delta = 0) =>
        @ent = @controller.ent
        return false if not IsValid(@ent)
        return false if not @func
        if @HasFinished()
            @Stop()
            return false

        @deltaAnim = (@finish - RealTime()) / @time
        if @deltaAnim < 0
            @deltaAnim = 1
            @frame = 0
            @start = RealTime()
            @finish = @start + @time
        @frame += 1

        status = @func(delta, 1 - @deltaAnim)
        if status == false
            @Stop()
            return false

        return true
    Stop: =>
        for id in *@flexIDsIterable
            @GetController()\GetFlexState(id)\ResetModifiers(@name)
        @valid = false
    Remove: => @Stop()
    HasFinished: =>
        return false if @dorepeat
        return RealTime() > @finish

class PonyFlexController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base_new.mdl'}

    @FLEX_LIST = {
        {flex: 'eyes_updown',       scale: 1, speed: 1, active: true}
        {flex: 'eyes_rightleft',    scale: 1, speed: 1, active: true}
        {flex: 'JawOpen',           scale: 1, speed: 1, active: true}
        {flex: 'JawClose',          scale: 1, speed: 1, active: true}
        {flex: 'Smirk',             scale: 1, speed: 1, active: true}
        {flex: 'Frown',             scale: 1, speed: 1, active: true}
        {flex: 'Stretch',           scale: 1, speed: 1, active: true}
        {flex: 'Pucker',            scale: 1, speed: 1, active: true}
        {flex: 'Grin',              scale: 1, speed: 1, active: true}
        {flex: 'CatFace',           scale: 1, speed: 1, active: true}
        {flex: 'Mouth_O',           scale: 1, speed: 1, active: true}
        {flex: 'Mouth_O2',          scale: 1, speed: 1, active: true}
        {flex: 'Mouth_Full',        scale: 1, speed: 1, active: true}
        {flex: 'Tongue_Out',        scale: 1, speed: 1, active: true}
        {flex: 'Tongue_Up',         scale: 1, speed: 1, active: true}
        {flex: 'Tongue_Down',       scale: 1, speed: 1, active: true}
        {flex: 'NoEyelashes',       scale: 1, speed: 1, active: true}
        {flex: 'Eyes_Blink',        scale: 1, speed: 1, active: true}
        {flex: 'Left_Blink',        scale: 1, speed: 1, active: true}
        {flex: 'Right_Blink',       scale: 1, speed: 1, active: true}
        {flex: 'Scrunch',           scale: 1, speed: 1, active: true}
        {flex: 'FatButt',           scale: 1, speed: 1, active: true}
        {flex: 'Stomach_Out',       scale: 1, speed: 1, active: true}
        {flex: 'Stomach_In',        scale: 1, speed: 1, active: true}
        {flex: 'Throat_Bulge',      scale: 1, speed: 1, active: true}
    }

    @FLEX_SEQUENCES = {
        {
            'name': 'eyes_idle'
            'autostart': true
            'repeat': true
            'time': 5
            'ids': {'Left_Blink', 'Right_Blink'}
            'func': (delta, timeOfAnim) =>
                left, right = @GetModifierID(1), @GetModifierID(2)
                leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                value = math.abs(math.sin(RealTime() * .5) * .15)
                leftState\SetModifierWeight(left, value)
                rightState\SetModifierWeight(right, value)
        }

        {
            'name': 'body_idle'
            'autostart': true
            'repeat': true
            'time': 2
            'ids': {'Stomach_Out', 'Stomach_In'}
            'func': (delta, timeOfAnim) =>
                In, Out = @GetModifierID(1), @GetModifierID(2)
                InState, OutState = @GetFlexState(1), @GetFlexState(2)
                abs = math.abs(0.5 - timeOfAnim)
                InState\SetModifierWeight(In, abs)
                OutState\SetModifierWeight(Out, abs)
        }

        {
            'name': 'health_idle'
            'autostart': true
            'repeat': true
            'time': 5
            'ids': {'Frown', 'Left_Blink', 'Right_Blink', 'Scrunch', 'Mouth_O', 'JawOpen', 'Grin'}
            'func': (delta, timeOfAnim) =>
                frown = @GetModifierID(1)
                frownState = @GetFlexState(1)
                left, right = @GetModifierID(2), @GetModifierID(3)
                leftState, rightState = @GetFlexState(2), @GetFlexState(3)
                Mouth_O, Mouth_OState = @GetModifierID(4), @GetFlexState(4)
                Scrunch = @GetModifierID(4)
                ScrunchState = @GetFlexState(4)

                hp, mhp = @ent\Health(), @ent\GetMaxHealth()
                mhp = 1 if mhp == 0
                div = hp / mhp
                strength = math.Clamp(1.5 - div * 1.5, 0, 1)
                frownState\SetModifierWeight(frown, strength)
                ScrunchState\SetModifierWeight(Scrunch, strength * .5)
                leftState\SetModifierWeight(left, strength * .1)
                rightState\SetModifierWeight(right, strength * .1)
                Mouth_OState\SetModifierWeight(Mouth_O, strength * .8)

                JawOpen = @GetModifierID(6)
                JawOpenState = @GetFlexState(6)

                if strength > .75
                    JawOpenState\SetModifierWeight(JawOpen, strength * .2 + math.sin(RealTime() * strength * 3) * .1)
                else
                    JawOpenState\SetModifierWeight(JawOpen, 0)

                if div >= 2
                    @SetModifierWeight(7, .5)
                else
                    @SetModifierWeight(7, 0)
        }

        {
            'name': 'greeny'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'Grin'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                strength = .5 + math.sin(RealTime() * 2) * .25
                GrinState\SetModifierWeight(Grin, strength)
        }

        {
            'name': 'big_grin'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'Grin'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, 1)
        }

        {
            'name': 'xd'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'Grin', 'Left_Blink', 'Right_Blink', 'JawOpen'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, .6)

                Left_Blink = @GetModifierID(2)
                Left_BlinkState = @GetFlexState(2)
                Left_BlinkState\SetModifierWeight(Left_Blink, .9)

                Right_Blink = @GetModifierID(3)
                Right_BlinkState = @GetFlexState(3)
                Right_BlinkState\SetModifierWeight(Right_Blink, .9)

                JawOpen = @GetModifierID(4)
                JawOpenState = @GetFlexState(4)
                JawOpenState\SetModifierScale(JawOpen, 2)
                JawOpenState\SetModifierWeight(JawOpen, (timeOfAnim % .1) * 2)
        }

        {
            'name': 'tongue'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'JawOpen', 'Tongue_Out'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, .1)
                @SetModifierWeight(2, 1)
        }

        {
            'name': 'sad'
            'autostart': false
            'repeat': false
            'time': 4
            'ids': {'Frown'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, 1)
        }

        {
            'name': 'cat'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'CatFace'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, 1)
        }

        {
            'name': 'ooo'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'Mouth_O2', 'Mouth_O'}
            'func': (delta, timeOfAnim) =>
                timeOfAnim *= 2
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, timeOfAnim)
                Grin = @GetModifierID(2)
                GrinState = @GetFlexState(2)
                GrinState\SetModifierWeight(Grin, timeOfAnim)
        }

        {
            'name': 'talk'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
            'create': =>
                @talkAnim = for i = 0, 1, 0.05
                    rand = math.random(1, 100) / 100
                    if rand <= .25
                        {1 * rand, 0.4 * rand, 2 * rand, 0}
                    elseif rand >= .25 and rand < .4
                        rand *= .8
                        {2 * rand, .6 * rand, 0, 1 * rand}
                    elseif rand >= .4 and rand < .75
                        rand *= .6
                        {1 * rand, 0, 1 * rand, 2 * rand}
                    elseif rand >= .75
                        rand *= .4
                        {1.5 * rand, 0, 1 * rand, 0}
            'func': (delta, timeOfAnim) =>
                JawOpen = @GetModifierID(1)
                JawOpenState = @GetFlexState(1)
                Tongue_OutOpen = @GetModifierID(2)
                Tongue_OutOpenState = @GetFlexState(2)
                Tongue_UpOpen = @GetModifierID(3)
                Tongue_UpOpenState = @GetFlexState(3)
                Tongue_DownOpen = @GetModifierID(4)
                Tongue_DownOpenState = @GetFlexState(4)
                cPos = math.floor(timeOfAnim * 20) + 1
                data = @talkAnim[cPos]
                return if not data
                {jaw, out, up, down} = data
                JawOpenState\SetModifierWeight(JawOpen, jaw)
                Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
                Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
                Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
        }

        {
            'name': 'talk_endless'
            'autostart': false
            'repeat': true
            'time': 4
            'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
            'create': =>
                @talkAnim = for i = 0, 1, 0.05
                    rand = math.random(1, 100) / 100
                    if rand <= .25
                        {1 * rand, 0.4 * rand, 2 * rand, 0}
                    elseif rand >= .25 and rand < .4
                        rand *= .8
                        {2 * rand, .6 * rand, 0, 1 * rand}
                    elseif rand >= .4 and rand < .75
                        rand *= .6
                        {1 * rand, 0, 1 * rand, 2 * rand}
                    elseif rand >= .75
                        rand *= .4
                        {1.5 * rand, 0, 1 * rand, 0}
            'func': (delta, timeOfAnim) =>
                JawOpen = @GetModifierID(1)
                JawOpenState = @GetFlexState(1)
                Tongue_OutOpen = @GetModifierID(2)
                Tongue_OutOpenState = @GetFlexState(2)
                Tongue_UpOpen = @GetModifierID(3)
                Tongue_UpOpenState = @GetFlexState(3)
                Tongue_DownOpen = @GetModifierID(4)
                Tongue_DownOpenState = @GetFlexState(4)
                cPos = math.floor(timeOfAnim * 20) + 1
                data = @talkAnim[cPos]
                return if not data
                {jaw, out, up, down} = data
                volume = @ent\VoiceVolume() * 6
                jaw *= volume
                out *= volume
                up *= volume
                down *= volume
                JawOpenState\SetModifierWeight(JawOpen, jaw)
                Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
                Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
                Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
        }

        {
            'name': 'eyes_blink'
            'autostart': true
            'repeat': true
            'time': 7
            'ids': {'Left_Blink', 'Right_Blink'}
            'create': =>
                @nextBlink = math.random(300, 600) / 1000
                @nextBlinkLength = math.random(50, 75) / 1000
                @min, @max = @nextBlink, @nextBlink + @nextBlinkLength
            'func': (delta, timeOfAnim) =>
                if @min > timeOfAnim or @max < timeOfAnim
                    if @blinkHit
                        @blinkHit = false
                        left, right = @GetModifierID(1), @GetModifierID(2)
                        leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                        leftState\SetModifierWeight(left, 0)
                        rightState\SetModifierWeight(right, 0)
                    return
                left, right = @GetModifierID(1), @GetModifierID(2)
                leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                leftState\SetModifierWeight(left, .9)
                rightState\SetModifierWeight(right, .9)
                @blinkHit = true
        }

        {
            'name': 'hurt'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'JawOpen', 'Frown', 'Grin', 'Scrunch'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, .08)
                @SetModifierWeight(2, .69)
                @SetModifierWeight(3, .36)
                @SetModifierWeight(4, .81)
        }
    }

    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
        for i, flex in pairs child.FLEX_LIST
            flex.id = i - 1
            flex.targetName = "target#{flex.flex}"
        child.FLEX_IDS = {flex.id, flex for flex in *child.FLEX_LIST}
        child.FLEX_TABLE = {flex.flex, flex for flex in *child.FLEX_LIST}
        seq.numid = i for i, seq in pairs child.FLEX_SEQUENCES
        child.FLEX_SEQUENCES_TABLE = {seq.name, seq for seq in *child.FLEX_SEQUENCES}
        child.FLEX_SEQUENCES_TABLE[seq.numid] = seq for seq in *child.FLEX_SEQUENCES
    @__inherited(@)

    @NEXT_HOOK_ID = 0

    new: (data) =>
        @controller = data
        @ent = data.ent
        @states = [FlexState(@, flex, id, scale, speed, active) for {:flex, :id, :scale, :speed, :active} in *@@FLEX_LIST]
        @statesTable = {state\GetFlexName(), state for state in *@states}
        @statesTable[state\GetFlexName()\lower()] = state for state in *@states
        @statesTable[state\GetFlexID()] = state for state in *@states
        @statesIterable = for state in *@states
            continue if not state\GetIsActive()
            state
        @hooks = {}
        @@NEXT_HOOK_ID += 1
        @fid = @@NEXT_HOOK_ID
        @hookID = "PPM2.FlexController.#{@@NEXT_HOOK_ID}"
        @lastThink = RealTime()
        @currentSequences = {}
        @currentSequencesIterable = {}
        @ResetSequences()
        @Hook('OnPlayerChat', @OnPlayerChat)
        @Hook('PlayerStartVoice', @PlayerStartVoice)
        @Hook('PlayerEndVoice', @PlayerEndVoice)
        @Hook('PPM2_HurtAnimation', @PPM2_HurtAnimation)

    StartSequence: (seqID = '') =>
        return @currentSequences[seqID] if @currentSequences[seqID]
        return if not @@FLEX_SEQUENCES_TABLE[seqID]
        @currentSequences[seqID] = FlexSequence(@, @@FLEX_SEQUENCES_TABLE[seqID])
        @currentSequencesIterable = [seq for i, seq in pairs @currentSequences]
        return @currentSequences[seqID]

    RestartSequence: (seqID = '') =>
        if @currentSequences[seqID]
            @currentSequences[seqID]\Reset()
            return @currentSequences[seqID]
        return @StartSequence(seqID)

    EndSequence: (seqID = '', callStop = true) =>
        return false if not @currentSequences[seqID]
        @currentSequences[seqID]\Stop() if callStop
        @currentSequences[seqID] = nil
        @currentSequencesIterable = [seq for i, seq in pairs @currentSequences]
        return true

    ResetSequences: =>
        for seq in *@currentSequencesIterable
            seq\Stop()

        @currentSequences = {}
        @currentSequencesIterable = {}
        state\Think(1000) for state in *@statesIterable

        for seq in *@@FLEX_SEQUENCES
            continue if not seq.autostart
            @StartSequence(seq.name)

    PlayerRespawn: =>
        @ResetSequences()

    HasSequence: (seqID = '') => @currentSequences[seqID] and true or false

    GetFlexState: (name = '') => @statesTable[name]
    DataChanges: (state) =>
    GetEntity: => @ent
    GetData: => @controller
    GetController: => @controller

    Hook: (id, func) =>
        newFunc = (...) ->
            if not IsValid(@ent)
                @ent = @GetData().ent
            if not IsValid(@ent) or @GetData()\GetData() ~= @ent\GetPonyData()
                @RemoveHooks()
                return
            func(@, ...)
            return nil
        hook.Add id, @hookID, newFunc
        table.insert(@hooks, id)

    OnPlayerChat: (ply = NULL, text = '', teamOnly = false, isDead = false) =>
        return if ply ~= @ent or teamOnly or isDead
        switch text\lower()
            when 'o'
                @StartSequence('ooo')
            when ':o'
                @StartSequence('ooo')
            when 'о'
                @StartSequence('ooo')
            when 'О'
                @StartSequence('ooo')
            when ':о'
                @StartSequence('ooo')
            when ':О'
                @StartSequence('ooo')
            when ':3'
                @StartSequence('cat')
            when ':з'
                @StartSequence('cat')
            when ':d'
                @StartSequence('big_grin')
            when 'xd'
                @StartSequence('xd')
            when 'exdi'
                @StartSequence('xd')
            when ':p'
                @StartSequence('tongue')
            when ':р'
                @StartSequence('tongue')
            when ':Р'
                @StartSequence('tongue')
            when ':c'
                @StartSequence('sad')
            when ':('
                @StartSequence('sad')
            when '('
                @StartSequence('sad')
            when ':с'
                @StartSequence('sad')
            when ':С'
                @StartSequence('sad')
            else
                if string.find(text, 'hehehe') or string.find(text, 'hahaha')
                    @StartSequence('greeny')
                else
                    @StartSequence('talk')
    PlayerStartVoice: (ply = NULL) =>
        return if ply ~= @ent
        @StartSequence('talk_endless')
    PlayerEndVoice: (ply = NULL) =>
        return if ply ~= @ent
        @EndSequence('talk_endless')
    PPM2_HurtAnimation: (ply = NULL) =>
        return if ply ~= @ent
        @RestartSequence('hurt')

    RemoveHooks: =>
        for iHook in *@hooks
            hook.Remove iHook, @hookID

    Think: =>
        return if DISABLE_FLEXES\GetBool()
        if not IsValid(@ent)
            @ent = @GetData().ent
        return if not IsValid(@ent) or @ent\IsDormant() or not @ent\Alive()
        delta = RealTime() - @lastThink
        @lastThink = RealTime()
        state\Think(delta) for state in *@statesIterable
        for seq in *@currentSequencesIterable
            if not seq\IsValid()
                @EndSequence(seq\GetName(), false)
                break
            seq\Think(delta)

do
    ppm2_disable_flexes = (cvar, oldval, newval) ->
        for ply in *player.GetAll()
            data = ply\GetPonyData()
            continue if not data
            flex = data\GetFlexController()
            continue if not flex
            flex\ResetSequences()
    cvars.AddChangeCallback 'ppm2_disable_flexes', ppm2_disable_flexes, 'ppm2_disable_flexes'

PPM2.PonyFlexController = PonyFlexController
PPM2.GetFlexController = (model = 'models/ppm/player_default_base_new.mdl') -> PonyFlexController.AVALIABLE_CONTROLLERS[model]

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
--
-- 0	eyes_updown
-- 1	eyes_rightleft
-- 2	JawOpen
-- 3	JawClose
-- 4	Smirk
-- 5	Frown
-- 6	Stretch
-- 7	Pucker
-- 8	Grin
-- 9	CatFace
-- 10	Mouth_O
-- 11	Mouth_O2
-- 12	Mouth_Full
-- 13	Tongue_Out
-- 14	Tongue_Up
-- 15	Tongue_Down
-- 16	NoEyelashes
-- 17	Eyes_Blink
-- 18	Left_Blink
-- 19	Right_Blink
-- 20	Scrunch
-- 21	FatButt
-- 22	Stomach_Out
-- 23	Stomach_In
-- 24	Throat_Bulge
--

net.Receive 'PPM2.DamageAnimation', ->
    ent = net.ReadEntity()
    return if not IsValid(ent) or not ent\IsPlayer()
    hook.Call('PPM2_HurtAnimation', nil, ent)

DISABLE_FLEXES = CreateConVar('ppm2_disable_flexes', '0', {FCVAR_ARCHIVE}, 'Disable pony flexes controllers. Saves some FPS.')

class FlexState
    new: (controller, flexName = '', flexID = 0, scale = 1, speed = 1, active = true, min = 0, max = 1, useModifiers = true) =>
        @controller = controller
        @ent = controller.ent
        @name = flexName
        @flexName = flexName
        @flexID = flexID
        @id = flexID
        @scale = scale
        @speed = speed
        @originalscale = scale
        @originalspeed = speed
        @min = min
        @max = max
        @current = -1
        @target = 0
        @speedModify = 1
        @scaleModify = 1
        @speedModifiers = {}
        @scaleModifiers = {}
        @modifiers = {}
        @modifiersNames = {}
        @useModifiers = useModifiers
        @nextModifierID = 0
        @active = active

    GetFlexID: => @flexID
    GetFlexName: => @flexName

    GetModifierID: (name = '') =>
        return @modifiersNames[name] if @modifiersNames[name]
        @nextModifierID += 1
        id = @nextModifierID
        @modifiersNames[name] = id
        @speedModifiers[id] = 0
        @scaleModifiers[id] = 0
        @modifiers[id] = 0
        return id
    SetModifierWeight: (modifID, val = 0) =>
        return if not modifID
        return if not @modifiers[modifID]
        @modifiers[modifID] = val
    SetModifierScale: (modifID, val = 0) =>
        return if not modifID
        return if not @scaleModifiers[modifID]
        @scaleModifiers[modifID] = val
    SetModifierScale: (modifID, val = 0) =>
        return if not modifID
        return if not @speedModifiers[modifID]
        @speedModifiers[modifID] = val
    ResetModifiers: (name = '') =>
        return false if not @modifiersNames[name]
        id = @modifiersNames[name]
        @speedModifiers[id] = 0
        @scaleModifiers[id] = 0
        @modifiers[id] = 0
        return true
    GetEntity: => @ent
    GetData: => @controller
    GetController: => @controller
    GetValue: => @current
    GetRealValue: => @target
    SetValue: (val = @target) =>
        @current = math.Clamp(val, @min, @max) * @scale * @scaleModify
        @target = @target
    SetRealValue: (val = @target) => @target = math.Clamp(val, @min, @max) * @scale * @scaleModify

    GetScale: => @scale
    GetSpeed: => @speed
    GetScaleModify: => @scaleModify
    GetSpeedModify: => @speedModify
    GetOriginalScale: => @originalscale
    GetOriginalSpeed: => @originalspeed

    SetScale: (val = @scale) => @scale = val
    GetSpeed: (val = @speed) => @speed = val
    SetScaleModify: (val = @scaleModify) => @scaleModify = val
    GetSpeedModify: (val = @speedModify) => @speedModify = val

    GetIsActive: => @active
    SetIsActive: (val = true) => @active = val

    AddValue: (val = 0) => @SetValue(@current + val)
    AddRealValue: (val = 0) => @SetRealValue(@target + val)
    Think: (delta = 0) =>
        return if not @active
        if @useModifiers
            @target = 0
            @scale = @originalscale * @scaleModify
            @speed = @originalspeed * @speedModify
            @target += modif for modif in *@modifiers
            @scale += modif for modif in *@scaleModifiers
            @speed += modif for modif in *@speedModifiers
            @target = math.Clamp(@target, @min, @max) * @scale
        @ent = @controller.ent
        @current = Lerp(delta * 10 * @speed * @speedModify, @current, @target) if @current ~= @target
        @ent\SetFlexWeight(@flexID, @current)

class FlexSequence
    new: (controller, data) =>
        {
            'name': @name
            'repeat': @dorepeat
            'frames': @frames
            'time': @time
            'func': @func
            'create': @createfunc
            'ids': @flexIDsIterable
            'numid': @numid
        } = data

        @flexIDS = {}
        @flexStates = {}
        i = 1
        for id in *data.ids
            state = controller\GetFlexState(id)
            num = state\GetModifierID(@name)
            @["flex_#{id}"] = num
            @flexIDS[id] = num
            @flexStates[id] = state
            @flexStates[i] = state
            @flexIDS[i] = num
            i += 1

        @ent = controller.ent
        @controller = controller
        @frame = 0
        @start = RealTime()
        @finish = @start + @time
        @deltaAnim = 1
        @speed = 1
        @scale = 1
        @valid = true
        @createfunc() if @createfunc

    Reset: =>
        @frame = 0
        @start = RealTime()
        @finish = @start + @time
        @deltaAnim = 1

    GetController: => @controller
    GetEntity: => @ent
    GetName: => @name
    GetRepeat: => @dorepeat
    GetFrames: => @frames
    GetFrame: => @frames
    GetTime: => @time
    GetThinkFunc: => @func
    GetCreatFunc: => @createfunc
    GetSpeed: => @speed
    GetAnimationSpeed: => @speed
    GetScale: => @scale
    GetModifierID: (id = '') => @flexIDS[id]
    GetFlexState: (id = '') => @flexStates[id]

    SetModifierWeight: (id = '', val = 0) => @GetFlexState(id)\SetModifierWeight(@GetModifierID(id), val)

    IsValid: => @valid
    Think: (delta = 0) =>
        @ent = @controller.ent
        return false if not IsValid(@ent)
        return false if not @func
        if @HasFinished()
            @Stop()
            return false

        @deltaAnim = (@finish - RealTime()) / @time
        if @deltaAnim < 0
            @deltaAnim = 1
            @frame = 0
            @start = RealTime()
            @finish = @start + @time
        @frame += 1

        status = @func(delta, 1 - @deltaAnim)
        if status == false
            @Stop()
            return false

        return true
    Stop: =>
        for id in *@flexIDsIterable
            @GetController()\GetFlexState(id)\ResetModifiers(@name)
        @valid = false
    Remove: => @Stop()
    HasFinished: =>
        return false if @dorepeat
        return RealTime() > @finish

class PonyFlexController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base_new.mdl'}

    @FLEX_LIST = {
        {flex: 'eyes_updown',       scale: 1, speed: 1, active: true}
        {flex: 'eyes_rightleft',    scale: 1, speed: 1, active: true}
        {flex: 'JawOpen',           scale: 1, speed: 1, active: true}
        {flex: 'JawClose',          scale: 1, speed: 1, active: true}
        {flex: 'Smirk',             scale: 1, speed: 1, active: true}
        {flex: 'Frown',             scale: 1, speed: 1, active: true}
        {flex: 'Stretch',           scale: 1, speed: 1, active: true}
        {flex: 'Pucker',            scale: 1, speed: 1, active: true}
        {flex: 'Grin',              scale: 1, speed: 1, active: true}
        {flex: 'CatFace',           scale: 1, speed: 1, active: true}
        {flex: 'Mouth_O',           scale: 1, speed: 1, active: true}
        {flex: 'Mouth_O2',          scale: 1, speed: 1, active: true}
        {flex: 'Mouth_Full',        scale: 1, speed: 1, active: true}
        {flex: 'Tongue_Out',        scale: 1, speed: 1, active: true}
        {flex: 'Tongue_Up',         scale: 1, speed: 1, active: true}
        {flex: 'Tongue_Down',       scale: 1, speed: 1, active: true}
        {flex: 'NoEyelashes',       scale: 1, speed: 1, active: true}
        {flex: 'Eyes_Blink',        scale: 1, speed: 1, active: true}
        {flex: 'Left_Blink',        scale: 1, speed: 1, active: true}
        {flex: 'Right_Blink',       scale: 1, speed: 1, active: true}
        {flex: 'Scrunch',           scale: 1, speed: 1, active: true}
        {flex: 'FatButt',           scale: 1, speed: 1, active: true}
        {flex: 'Stomach_Out',       scale: 1, speed: 1, active: true}
        {flex: 'Stomach_In',        scale: 1, speed: 1, active: true}
        {flex: 'Throat_Bulge',      scale: 1, speed: 1, active: true}
    }

    @FLEX_SEQUENCES = {
        {
            'name': 'eyes_idle'
            'autostart': true
            'repeat': true
            'time': 5
            'ids': {'Left_Blink', 'Right_Blink'}
            'func': (delta, timeOfAnim) =>
                left, right = @GetModifierID(1), @GetModifierID(2)
                leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                value = math.abs(math.sin(RealTime() * .5) * .15)
                leftState\SetModifierWeight(left, value)
                rightState\SetModifierWeight(right, value)
        }

        {
            'name': 'body_idle'
            'autostart': true
            'repeat': true
            'time': 2
            'ids': {'Stomach_Out', 'Stomach_In'}
            'func': (delta, timeOfAnim) =>
                In, Out = @GetModifierID(1), @GetModifierID(2)
                InState, OutState = @GetFlexState(1), @GetFlexState(2)
                abs = math.abs(0.5 - timeOfAnim)
                InState\SetModifierWeight(In, abs)
                OutState\SetModifierWeight(Out, abs)
        }

        {
            'name': 'health_idle'
            'autostart': true
            'repeat': true
            'time': 5
            'ids': {'Frown', 'Left_Blink', 'Right_Blink', 'Scrunch', 'Mouth_O', 'JawOpen'}
            'func': (delta, timeOfAnim) =>
                frown = @GetModifierID(1)
                frownState = @GetFlexState(1)
                left, right = @GetModifierID(2), @GetModifierID(3)
                leftState, rightState = @GetFlexState(2), @GetFlexState(3)
                Mouth_O, Mouth_OState = @GetModifierID(4), @GetFlexState(4)
                Scrunch = @GetModifierID(4)
                ScrunchState = @GetFlexState(4)

                hp, mhp = @ent\Health(), @ent\GetMaxHealth()
                mhp = 1 if mhp == 0
                strength = math.Clamp(1.5 - (hp / mhp) * 1.5, 0, 1)
                frownState\SetModifierWeight(frown, strength)
                ScrunchState\SetModifierWeight(Scrunch, strength * .5)
                leftState\SetModifierWeight(left, strength * .1)
                rightState\SetModifierWeight(right, strength * .1)
                Mouth_OState\SetModifierWeight(Mouth_O, strength * .8)

                JawOpen = @GetModifierID(6)
                JawOpenState = @GetFlexState(6)

                if strength > .75
                    JawOpenState\SetModifierWeight(JawOpen, strength * .2 + math.sin(RealTime() * strength * 3) * .1)
                else
                    JawOpenState\SetModifierWeight(JawOpen, 0)
        }

        {
            'name': 'greeny'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'Grin'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                strength = .5 + math.sin(RealTime() * 2) * .25
                GrinState\SetModifierWeight(Grin, strength)
        }

        {
            'name': 'big_grin'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'Grin'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, 1)
        }

        {
            'name': 'xd'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'Grin', 'Left_Blink', 'Right_Blink', 'JawOpen'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, .6)

                Left_Blink = @GetModifierID(2)
                Left_BlinkState = @GetFlexState(2)
                Left_BlinkState\SetModifierWeight(Left_Blink, .9)

                Right_Blink = @GetModifierID(3)
                Right_BlinkState = @GetFlexState(3)
                Right_BlinkState\SetModifierWeight(Right_Blink, .9)

                JawOpen = @GetModifierID(4)
                JawOpenState = @GetFlexState(4)
                JawOpenState\SetModifierScale(JawOpen, 2)
                JawOpenState\SetModifierWeight(JawOpen, (timeOfAnim % .1) * 2)
        }

        {
            'name': 'tongue'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'JawOpen', 'Tongue_Out'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, .1)
                @SetModifierWeight(2, 1)
        }

        {
            'name': 'sad'
            'autostart': false
            'repeat': false
            'time': 4
            'ids': {'Frown'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, 1)
        }

        {
            'name': 'cat'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'CatFace'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, 1)
        }

        {
            'name': 'ooo'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'Mouth_O2', 'Mouth_O'}
            'func': (delta, timeOfAnim) =>
                timeOfAnim *= 2
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, timeOfAnim)
                Grin = @GetModifierID(2)
                GrinState = @GetFlexState(2)
                GrinState\SetModifierWeight(Grin, timeOfAnim)
        }

        {
            'name': 'talk'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
            'create': =>
                @talkAnim = for i = 0, 1, 0.05
                    rand = math.random(1, 100) / 100
                    if rand <= .25
                        {1 * rand, 0.4 * rand, 2 * rand, 0}
                    elseif rand >= .25 and rand < .4
                        rand *= .8
                        {2 * rand, .6 * rand, 0, 1 * rand}
                    elseif rand >= .4 and rand < .75
                        rand *= .6
                        {1 * rand, 0, 1 * rand, 2 * rand}
                    elseif rand >= .75
                        rand *= .4
                        {1.5 * rand, 0, 1 * rand, 0}
            'func': (delta, timeOfAnim) =>
                JawOpen = @GetModifierID(1)
                JawOpenState = @GetFlexState(1)
                Tongue_OutOpen = @GetModifierID(2)
                Tongue_OutOpenState = @GetFlexState(2)
                Tongue_UpOpen = @GetModifierID(3)
                Tongue_UpOpenState = @GetFlexState(3)
                Tongue_DownOpen = @GetModifierID(4)
                Tongue_DownOpenState = @GetFlexState(4)
                cPos = math.floor(timeOfAnim * 20) + 1
                data = @talkAnim[cPos]
                return if not data
                {jaw, out, up, down} = data
                JawOpenState\SetModifierWeight(JawOpen, jaw)
                Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
                Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
                Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
        }

        {
            'name': 'talk_endless'
            'autostart': false
            'repeat': true
            'time': 4
            'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
            'create': =>
                @talkAnim = for i = 0, 1, 0.05
                    rand = math.random(1, 100) / 100
                    if rand <= .25
                        {1 * rand, 0.4 * rand, 2 * rand, 0}
                    elseif rand >= .25 and rand < .4
                        rand *= .8
                        {2 * rand, .6 * rand, 0, 1 * rand}
                    elseif rand >= .4 and rand < .75
                        rand *= .6
                        {1 * rand, 0, 1 * rand, 2 * rand}
                    elseif rand >= .75
                        rand *= .4
                        {1.5 * rand, 0, 1 * rand, 0}
            'func': (delta, timeOfAnim) =>
                JawOpen = @GetModifierID(1)
                JawOpenState = @GetFlexState(1)
                Tongue_OutOpen = @GetModifierID(2)
                Tongue_OutOpenState = @GetFlexState(2)
                Tongue_UpOpen = @GetModifierID(3)
                Tongue_UpOpenState = @GetFlexState(3)
                Tongue_DownOpen = @GetModifierID(4)
                Tongue_DownOpenState = @GetFlexState(4)
                cPos = math.floor(timeOfAnim * 20) + 1
                data = @talkAnim[cPos]
                return if not data
                {jaw, out, up, down} = data
                volume = @ent\VoiceVolume() * 6
                jaw *= volume
                out *= volume
                up *= volume
                down *= volume
                JawOpenState\SetModifierWeight(JawOpen, jaw)
                Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
                Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
                Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
        }

        {
            'name': 'eyes_blink'
            'autostart': true
            'repeat': true
            'time': 7
            'ids': {'Left_Blink', 'Right_Blink'}
            'create': =>
                @nextBlink = math.random(300, 600) / 1000
                @nextBlinkLength = math.random(50, 75) / 1000
                @min, @max = @nextBlink, @nextBlink + @nextBlinkLength
            'func': (delta, timeOfAnim) =>
                if @min > timeOfAnim or @max < timeOfAnim
                    if @blinkHit
                        @blinkHit = false
                        left, right = @GetModifierID(1), @GetModifierID(2)
                        leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                        leftState\SetModifierWeight(left, 0)
                        rightState\SetModifierWeight(right, 0)
                    return
                left, right = @GetModifierID(1), @GetModifierID(2)
                leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                leftState\SetModifierWeight(left, .9)
                rightState\SetModifierWeight(right, .9)
                @blinkHit = true
        }

        {
            'name': 'hurt'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'JawOpen', 'Frown', 'Grin', 'Scrunch'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, .08)
                @SetModifierWeight(2, .69)
                @SetModifierWeight(3, .36)
                @SetModifierWeight(4, .81)
        }
    }

    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
        for i, flex in pairs child.FLEX_LIST
            flex.id = i - 1
            flex.targetName = "target#{flex.flex}"
        child.FLEX_IDS = {flex.id, flex for flex in *child.FLEX_LIST}
        child.FLEX_TABLE = {flex.flex, flex for flex in *child.FLEX_LIST}
        seq.numid = i for i, seq in pairs child.FLEX_SEQUENCES
        child.FLEX_SEQUENCES_TABLE = {seq.name, seq for seq in *child.FLEX_SEQUENCES}
        child.FLEX_SEQUENCES_TABLE[seq.numid] = seq for seq in *child.FLEX_SEQUENCES
    @__inherited(@)

    @NEXT_HOOK_ID = 0

    new: (data) =>
        @controller = data
        @ent = data.ent
        @states = [FlexState(@, flex, id, scale, speed, active) for {:flex, :id, :scale, :speed, :active} in *@@FLEX_LIST]
        @statesTable = {state\GetFlexName(), state for state in *@states}
        @statesTable[state\GetFlexName()\lower()] = state for state in *@states
        @statesTable[state\GetFlexID()] = state for state in *@states
        @statesIterable = for state in *@states
            continue if not state\GetIsActive()
            state
        @hooks = {}
        @@NEXT_HOOK_ID += 1
        @fid = @@NEXT_HOOK_ID
        @hookID = "PPM2.FlexController.#{@@NEXT_HOOK_ID}"
        @lastThink = RealTime()
        @currentSequences = {}
        @currentSequencesIterable = {}
        @ResetSequences()
        @Hook('OnPlayerChat', @OnPlayerChat)
        @Hook('PlayerStartVoice', @PlayerStartVoice)
        @Hook('PlayerEndVoice', @PlayerEndVoice)
        @Hook('PPM2_HurtAnimation', @PPM2_HurtAnimation)

    StartSequence: (seqID = '') =>
        return @currentSequences[seqID] if @currentSequences[seqID]
        return if not @@FLEX_SEQUENCES_TABLE[seqID]
        @currentSequences[seqID] = FlexSequence(@, @@FLEX_SEQUENCES_TABLE[seqID])
        @currentSequencesIterable = [seq for i, seq in pairs @currentSequences]
        return @currentSequences[seqID]

    RestartSequence: (seqID = '') =>
        if @currentSequences[seqID]
            @currentSequences[seqID]\Reset()
            return @currentSequences[seqID]
        return @StartSequence(seqID)

    EndSequence: (seqID = '', callStop = true) =>
        return false if not @currentSequences[seqID]
        @currentSequences[seqID]\Stop() if callStop
        @currentSequences[seqID] = nil
        @currentSequencesIterable = [seq for i, seq in pairs @currentSequences]
        return true

    ResetSequences: =>
        for seq in *@currentSequencesIterable
            seq\Stop()

        @currentSequences = {}
        @currentSequencesIterable = {}
        state\Think(1000) for state in *@statesIterable

        for seq in *@@FLEX_SEQUENCES
            continue if not seq.autostart
            @StartSequence(seq.name)

    PlayerRespawn: =>
        @ResetSequences()

    HasSequence: (seqID = '') => @currentSequences[seqID] and true or false

    GetFlexState: (name = '') => @statesTable[name]
    DataChanges: (state) =>
    GetEntity: => @ent
    GetData: => @controller
    GetController: => @controller

    Hook: (id, func) =>
        newFunc = (...) ->
            if not IsValid(@ent)
                @ent = @GetData().ent
            if not IsValid(@ent) or @GetData()\GetData() ~= @ent\GetPonyData()
                @RemoveHooks()
                return
            func(@, ...)
            return nil
        hook.Add id, @hookID, newFunc
        table.insert(@hooks, id)

    OnPlayerChat: (ply = NULL, text = '', teamOnly = false, isDead = false) =>
        return if ply ~= @ent or teamOnly or isDead
        switch text\lower()
            when 'o'
                @StartSequence('ooo')
            when ':o'
                @StartSequence('ooo')
            when 'о'
                @StartSequence('ooo')
            when 'О'
                @StartSequence('ooo')
            when ':о'
                @StartSequence('ooo')
            when ':О'
                @StartSequence('ooo')
            when ':3'
                @StartSequence('cat')
            when ':з'
                @StartSequence('cat')
            when ':d'
                @StartSequence('big_grin')
            when 'xd'
                @StartSequence('xd')
            when 'exdi'
                @StartSequence('xd')
            when ':p'
                @StartSequence('tongue')
            when ':р'
                @StartSequence('tongue')
            when ':Р'
                @StartSequence('tongue')
            when ':c'
                @StartSequence('sad')
            when ':('
                @StartSequence('sad')
            when '('
                @StartSequence('sad')
            when ':с'
                @StartSequence('sad')
            when ':С'
                @StartSequence('sad')
            else
                if string.find(text, 'hehehe') or string.find(text, 'hahaha')
                    @StartSequence('greeny')
                else
                    @StartSequence('talk')
    PlayerStartVoice: (ply = NULL) =>
        return if ply ~= @ent
        @StartSequence('talk_endless')
    PlayerEndVoice: (ply = NULL) =>
        return if ply ~= @ent
        @EndSequence('talk_endless')
    PPM2_HurtAnimation: (ply = NULL) =>
        return if ply ~= @ent
        @RestartSequence('hurt')

    RemoveHooks: =>
        for iHook in *@hooks
            hook.Remove iHook, @hookID

    Think: =>
        return if DISABLE_FLEXES\GetBool()
        if not IsValid(@ent)
            @ent = @GetData().ent
        return if not IsValid(@ent) or @ent\IsDormant() or not @ent\Alive()
        delta = RealTime() - @lastThink
        @lastThink = RealTime()
        state\Think(delta) for state in *@statesIterable
        for seq in *@currentSequencesIterable
            if not seq\IsValid()
                @EndSequence(seq\GetName(), false)
                break
            seq\Think(delta)

do
    ppm2_disable_flexes = (cvar, oldval, newval) ->
        for ply in *player.GetAll()
            data = ply\GetPonyData()
            continue if not data
            flex = data\GetFlexController()
            continue if not flex
            flex\ResetSequences()
    cvars.AddChangeCallback 'ppm2_disable_flexes', ppm2_disable_flexes, 'ppm2_disable_flexes'

PPM2.PonyFlexController = PonyFlexController
PPM2.GetFlexController = (model = 'models/ppm/player_default_base_new.mdl') -> PonyFlexController.AVALIABLE_CONTROLLERS[model]