Fix moveAtAngle

Fix moveAtAngle

* Looks like the version I was looking at has since been updated

import Graphiclist from require "LunoPunk.graphics.Graphiclist"
import LP from require "LunoPunk.LP"
import Mask from require "LunoPunk.Mask"
import Tweener from require "LunoPunk.Tweener"
import moon_type from require "LunoPunk.utils.moonscript"
import Math from require "LunoPunk.utils.Math"

class Entity extends Tweener
	-- Constructor. Can be usd to place the Entity and assign a graphic and mask.
	-- @param	x			X position to place the Entity.
	-- @param	y			Y position to place the Entity.
	-- @param	graphic		Graphic to assign to the Entity.
	-- @param	mask		Mask to assign to the Entity.
	new: (x = 0, y = 0, graphic = nil, mask = nil) =>
		super!
		-- If the Entity should render.
		@visible = true
		-- If the Entity should respond to collision checks.
		@collidable = true
		-- If the entity should follow the camera.
		@followCamera = false
		-- X position of the Entity in the Scene.
		@__x = x
		-- Y position of the Entity in the Scene.
		@__y = y
		-- X origin of the Entity's hitbox.
		@originX = 0
		-- Y origin of the Entity's hitbox.
		@originY = 0
		-- Width of the Entity's hitbox.
		@width = 0
		-- Height of the Entity's hitbox.
		@height = 0

		-- The collision type, used for collision checking.
		@__typeNext = nil
		@__typePrev = nil
		@__type = ""
		@name = ""

		@__point = LP.point
		@__camera = LP.point2

		-- Collision information.
		@__hitbox = Mask!
		-- An optional Mask component, used for specialized collision. If this is not assigned, collision checks will use the Entity's hitbox by default.
		@mask mask unless mask == nil
		@__moveX, @__moveY = 0, 0
		@__collide_x, @__collide_y = 0, 0

		-- The rendering layer of this Entity. Higher layers are rendered first.
		@__layer = LP.BASELAYER!

		-- Graphical component to render to the screen.
		@graphic graphic unless graphic == nil

		@__hitbox\assignTo @

	-- X position of the Entity in the Scene.
	x: (value) =>
		unless value == nil
			@__x = value
			return @__x

		if @followCamera
			@__x + LP.camera.x
		else
			@__x

	-- Y position of the Entity in the Scene.
	y: (value) =>
		unless value == nil
			@__y = value
			return @__y

		if @followCamera
			@__y + LP.camera.y
		else
			@__y

	-- Some helper accessors

	-- If the Entity collides with the camera rectangle.
	onCamera: =>
		return false if @__scene == nil
		@collideRect @x!, @y!, @__scene.camera.x, @__scene.camera.y, LP.width, LP.height

	-- Half the Entity's width.
	halfWidth: => @width/2
	-- Half the Entity's height.
	halfHeight: => @height/2
	-- The center x position of the Entity's hitbox.
	centerX: => @x! - @originX + @halfWidth!
	-- The center y position of the Entity's hitbox.
	centerY: => @y! - @originY + @halfHeight!
	-- The leftmost position of the Entity's hitbox.
	left: => @x! - @originX
	-- The rightmost position of the Entity's hitbox.
	right: => @x! - @originX + @width
	-- The topmost position of the Entity's hitbox.
	top: => @y! - @originY
	-- The bottommost position of the Entity's hitbox.
	bottom: => @y! - @originY + @height

	layer: (value) =>
		return @__layer if value == nil or value == @__layer

		@__graphic.layer = value unless @__graphic == nil

		if @__scene == nil
			-- Reset to the default on false
			@__layer = if value then value else LP.BASELAYER!
			return @__layer

		@__scene\__removeRender @
		-- Reset to the default on false
		@__layer = if value then value else LP.BASELAYER!
		@__scene\__addRender @
		@__layer

	type: (value) =>
		return @__type if value == nil

		if @__scene == nil
			@__type = value
			return @__type

		@__scene\__removeType(@) unless @__type == ""
		@__type = value
		@__scene\__addType(@) unless @__type == ""

		@__type

	mask: (value) =>
		return @__mask if value == nil or @__mask == value

		@__mask\assignTo(nil) unless @__mask == nil
		@__mask = if value then value else nil
		@__mask\assignTo(@) if value

		@__mask

	graphic: (value) =>
		return @__graphic if value == nil or @__graphic == value

		value.layer = @__layer if value
		@__graphic = if value then value else nil
		@__graphic

	scene: (value) =>
		@__scene = value unless value == nil or @__scene == value
		-- Special case to unset @__scene
		@__scene = nil if value == false
		@__scene

	-- The World object is deprecated for FlashPunk-like compatibility
	-- TODO give a massage about this kept for porting compatibility
	world: (value) =>
		@__scene = value unless value == nil
		@__scene

	-- Override this, called when the Entity is added to a Scene.
	added: =>
	-- Override this, called when the Entity is removed from a Scene.
	removed: =>
	-- Updates the Entity.
	update: => super!

	-- Renders the Entity. If you override this for special behaviour,
	-- remember to call super.render() to render the Entity's graphic.
	render: =>
		if @__graphic != nil and @__graphic.visible
			if @__graphic.relative
				@__point.x = @x!
				@__point.y = @y!
			else
				@__point.x, @__point.y = 0, 0
				@__camera.x = if @__scene == nil then LP.camera.x else @__scene.camera.x
				@__camera.y = if @__scene == nil then LP.camera.y else @__scene.camera.y

			@__graphic\render @__point, @__camera

	-- Checks for a collision against an Entity type.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or nil if none were collided.
	collide: (type, x, y) =>
		return nil if @__scene == nil
		return nil if not @collidable or @__scene\typeFirst type == nil

		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y
		e = @__scene\typeFirst type

		if @__mask == nil
			while e != nil
				if do
						c1 = e.collidable and e != @
						c2 = x - @originX + @width >= e\x! - e.originX
						c3 = y - @originY + @height >= e\y! - e.originY
						c4 = x - @originX <= e\x! - e.originX + e.width
						c5 = y - @originY <= e\y! - e.originY + e.height
						c1 and c2 and c3 and c4 and c5
					if e.__mask == nil or e.__mask\collide @HITBOX
						@x @__collide_x
						@y @__collide_y
						return e

				e = e.__typeNext

			@x @__collide_x
			@y @__collide_y

			return nil

		while e != nil
			if do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width >= e\x! - e.originX
					c3 = y - @originY + @height >= e\y! - e.originY
					c4 = x - @originX <= e\x! - e.originX + e.width
					c5 = y - @originY <= e\y! - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if e.__mask\collide (if e.__mask == nil then e.HITBOX else e.__mask)
					@x @__collide_x
					@y @__collide_y
					return e

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y

		return nil

	-- Checks for collision against multiple Entity types.
	-- @param	types		An Array or Vector of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or null if none were collided.
	collideTypes: (types, x, y) =>
		return nil if @__scene == nil

		if type(types) == "string"
			return @collide types, x, y
		else
			unless types == nil
				for t in *types
					e = @collide t, x, y
					return e unless e == nil

		return nil

	-- Checks if this Entity collides with a specific Entity.
	-- @param	e		The Entity to collide against.
	-- @param	x		Virtual x position to place this Entity.
	-- @param	y		Virtual y position to place this Entity.
	-- @return	The Entity if they overlap, or null if they don't.
	collideWith: (e, x, y) =>
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		c = do
			c1 = @collidable and e.collidable
			c2 = x - @originX + @width > e.x - e.originX
			c3 = y - @originY + @height > e.y - e.originY
			c4 = x - @originX < e.x - e.originX + e.width
			c5 = y - @originY < e.y - e.originY + e.height
			c1 and c2 and c3 and c4 and c5
		if c
			if @__mask == nil
				if e.__mask == nil or e.__mask\collide @__hitbox
					@x @__collide_x
					@y @__collide_y
					return e
				@x @__collide_x
				@y @__collide_y
				return nil

			if (if @__mask\collide(e.__mask) == nil then e.__hitbox else e.__mask)
				@x @__collide_x
				@y @__collide_y
				return e

		@x @__collide_x
		@y @__collide_y
		return nil

	-- Checks if this Entity overlaps the specified rectangle.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @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	If they overlap.
	collideRect: (x, y, rX, rY, rWidth, rHeight) =>
		c = do
			c1 = x - @originX + @width >= rX
			c2 = y - @originY + @height >= rY
			c3 = x - @originX <= rX + rWidth
			c4 = y - @originY <= rY + rHeight
			c1 and c2 and c3 and c4
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x = rX
			LP.entity.y = rY
			LP.entity.width = rWidth
			LP.entity.height = rHeight
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Checks if this Entity overlaps the specified position.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	pX			X position.
	-- @param	pY			Y position.
	-- @return	If the Entity intersects with the position.
	collidePoint: (x, y, pX, pY) =>
		c = do
			c1 = pX >= x - @originX
			c2 = pY >= y - @originY
			c3 = pX < x - @originX + @width
			c4 = pY < y - @originY + height
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x, LP.entity.y = pX, pY
			LP.entity.width, LP.entity.height = 1, 1
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Populates an array with all collided Entities of a type. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	collideInto: (type, x, y, array) =>
		return if @__scene == nil

		e = @__scene.__typeFirst\get type
		return if not @collidable or e == nil

		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		if @__mask == nil
			while e != nil
				c = do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width > e.x - e.originX
					c3 = y - @originY + @height > e.y - e.originY
					c4 = x - @originX < e.x - e.originX + e.width
					c5 = y - @originY < e.y - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if c
					array[#array+1] = e if e__mask == nil or e.__mask\collide @__hitbox

				e = e.__typeNext

			@x @__collide_x
			@y @__collide_y
			return

		while e != nil
			c = do
				c1 = e.collidable and e != @
				c2 = x - @originX + @width > e.x - e.originX
				c3 = y - @originY + @height > e.y - e.originY
				c4 = x - @originX < e.x - e.originX + e.width
				c5 = y - @originY < e.y - e.originY + e.height
				c1 and c2 and c3 and c4 and c5
			if c
				array[#array+1] = e if (if @__mask\collide(e.__mask) == nil then e.__hitbox else e.__mask)

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y
		return

	-- Populates an array with all collided Entities of multiple types.This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	types		An array of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	collideTypesInto: (types, x, y, array) =>
		return if @__scene == nil
		for t in *types
			@collideInto t, x, y, array

	-- Adds the graphic to the Entity via a Graphiclist.
	-- @param	g		Graphic to add.
	addGraphic: (g) =>
		g.layer = @__layer
		if @graphic! == nil
			@graphic g
		else if moon_type(g) == Graphiclist
			@graphic!\add g
		else
			list = Graphiclist!
			list\add graphic unless @graphic! == nil
			list\add g
			@graphic list
			list\setEntity @

		g

	-- Sets the Entity's hitbox properties.
	-- @param	width		Width of the hitbox.
	-- @param	height		Height of the hitbox.
	-- @param	originX		X origin of the hitbox.
	-- @param	originY		Y origin of the hitbox.
	setHitbox: (width, height, originX = 0, originY = 0) =>
		@width, @height = width, height
		@originX, @originY = originX, originY

	-- Sets the Entity's hitbox to match that of the provided object.
	-- @param	o		The object defining the hitbox (eg. an Image or Rectangle).
	setHitboxTo: (o) =>
		@width, @height = o.width, o.height

		if o.originX and o.originY
			@originX, @originY = o.originX, o.originY
		else
			@originX, @originY = -o.x, -o.y

	-- Sets the origin of the Entity.
	-- @param	x		X origin.
	-- @param	y		Y origin.
	setOrigin: (x, y) => @originX, @originY = x, y

	-- Center's the Entity's origin (half width & height).
	centerOrigin: => @originX, @originY = @halfWidth!, @halfHeight!

	-- Calculates the distance from another Entity.
	-- @param	e				The other Entity.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceFrom: (e, useHitboxes = false) =>
		return math.sqrt (@x! - e\x!) * (@x! - e\x!) + (@y! - e\y!) * (@y! - e\y!) if not useHitboxes
		LP.distanceRects @x! - @originX, @y! - @originY, @width, @height, e\x! - e.originX, e\y! - e.originY, e.width, e.height

	-- Calculates the distance from this Entity to the point.
	-- @param	px				X position.
	-- @param	py				Y position.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceToPoint: (px, py, useHitbox = false) =>
		return math.sqrt (@x! - px) * (@x! - px) + (@y! - py) * (@y! - py) if not useHitbox
		LP.distanceRectPoint px, py, @x! - @originX, @y! - @originY, @width, @height

	-- Calculates the distance from this Entity to the rectangle.
	-- @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 distance.
	distanceToRect: (rx, ry, rwidth, rheight) =>
		LP.distanceRects rx, ry, rwidth, rheight, @x! - @originX, @y! - @originY, @width, @height

	-- Gets the class name as a string.
	-- @return	A string representing the class name.
	toString: => @@.__name

	-- This metamethod only applies to Entity and not subclasses
	__tostring: => @toString!

	-- Moves the Entity by the amount, retaining integer values for its x and y.
	-- @param	x			Horizontal offset.
	-- @param	y			Vertical offset.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveBy: (x, y, solidType, sweep) =>
		@__moveX += x
		@__moveY += y
		x, y = Math.round(@__moveX), Math.round(@__moveY)
		@__moveX -= x
		@__moveY -= y

		if solidType == nil
			@x @x! + x
			@y @y! + y
		else
			unless x == 0
				if @collidable and (sweep or @collideTypes(solidType, @x! + x, @y!) != nil)
					sign = if x > 0 then 1 else -1
					while x != 0
						e = @collideTypes solidType, @x! + sign, @y!
						if e == nil
							@x @x! + sign
						else
							break if @moveCollideX e
							@x @x! + sign

						x -= sign
				else
					@x @x! + x

			unless y == 0
				if @collidable and (sweep or @collideTypes(solidType, @x!, @y! + y) != nil)
					sign = if y > 0 then 1 else -1
					while y != 0
						e = @collideTypes solidType, @x!, @y! + sign
						if e == nil
							@y @y! + sign
						else
							break if @moveCollideY e
							@y @y! + sign

						y -= sign

	-- Moves the Entity to the position, retaining integer values for its x and y.
	-- @param	x			X position.
	-- @param	y			Y position.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTo: (x, y, solidType, sweep) => @moveBy x - @x!, y - @y!, solidType, sweep

	-- Moves towards the target position, retaining integer values for its x and y.
	-- @param	x			X target.
	-- @param	y			Y target.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTowards: (x, y, amount, solidType, sweep) =>
		@__point.x, @__point.y = x - @x!, y - @y!
		@__point\normalize amount if @__point.x^2 + @__point.y^2 > amount^2
		@moveBy @__point.x, @__point.y, solidType, sweep

	-- Moves at an angle by a certain amount, retaining integer values for its x and y.
	-- @param	angle		Angle to move at in degrees.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveAtAngle: (angle, amount, solidType, sweep) =>
		angle *= LP.RAD!
		@moveBy math.cos(angle) * amount, math.sin(angle) * amount, solidType, sweep

	-- When you collide with an Entity on the x-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideX: (e) => true

	-- When you collide with an Entity on the y-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideY: (e) => true

	-- Clamps the Entity's hitbox on the x-axis.
	-- @param	left		Left bounds.
	-- @param	right		Right bounds.
	-- @param	padding		Optional padding on the clamp.
	clampHorizontal: (left, right, padding = 0) =>
		@x left + @originX + padding if @x! - @originX < left + padding
		@x right - @width + @originX - padding if @x! - @originX + @width > right - padding

	-- Clamps the Entity's hitbox on the y axis.
	-- @param	top			Min bounds.
	-- @param	bottom		Max bounds.
	-- @param	padding		Optional padding on the clamp.
	clampVertical: (top, bottom, padding = 0) =>
		@y top + @originY + padding if @y! - @originY < top + padding
		@y bottom - @height + @originY - padding if @y! - @originY + @height > bottom - padding

	-- Center graphic inside bounding rect.
	centerGraphicInRect: =>
		@__graphic.x, @__graphic.y = @halfWidth!, @halfHeight! unless @__graphic == nil

{ :Entity }

import Graphiclist from require "LunoPunk.graphics.Graphiclist"
import LP from require "LunoPunk.LP"
import Mask from require "LunoPunk.Mask"
import Tweener from require "LunoPunk.Tweener"
import moon_type from require "LunoPunk.utils.moonscript"
import Math from require "LunoPunk.utils.Math"

class Entity extends Tweener
	-- Constructor. Can be usd to place the Entity and assign a graphic and mask.
	-- @param	x			X position to place the Entity.
	-- @param	y			Y position to place the Entity.
	-- @param	graphic		Graphic to assign to the Entity.
	-- @param	mask		Mask to assign to the Entity.
	new: (x = 0, y = 0, graphic = nil, mask = nil) =>
		super!
		-- If the Entity should render.
		@visible = true
		-- If the Entity should respond to collision checks.
		@collidable = true
		-- If the entity should follow the camera.
		@followCamera = false
		-- X position of the Entity in the Scene.
		@__x = x
		-- Y position of the Entity in the Scene.
		@__y = y
		-- X origin of the Entity's hitbox.
		@originX = 0
		-- Y origin of the Entity's hitbox.
		@originY = 0
		-- Width of the Entity's hitbox.
		@width = 0
		-- Height of the Entity's hitbox.
		@height = 0

		-- The collision type, used for collision checking.
		@__typeNext = nil
		@__typePrev = nil
		@__type = ""
		@name = ""

		@__point = LP.point
		@__camera = LP.point2

		-- Collision information.
		@__hitbox = Mask!
		-- An optional Mask component, used for specialized collision. If this is not assigned, collision checks will use the Entity's hitbox by default.
		@mask mask unless mask == nil
		@__moveX, @__moveY = 0, 0
		@__collide_x, @__collide_y = 0, 0

		-- The rendering layer of this Entity. Higher layers are rendered first.
		@__layer = LP.BASELAYER!

		-- Graphical component to render to the screen.
		@graphic graphic unless graphic == nil

		@__hitbox\assignTo @

	-- X position of the Entity in the Scene.
	x: (value) =>
		unless value == nil
			@__x = value
			return @__x

		if @followCamera
			@__x + LP.camera.x
		else
			@__x

	-- Y position of the Entity in the Scene.
	y: (value) =>
		unless value == nil
			@__y = value
			return @__y

		if @followCamera
			@__y + LP.camera.y
		else
			@__y

	-- Some helper accessors

	-- If the Entity collides with the camera rectangle.
	onCamera: =>
		return false if @__scene == nil
		@collideRect @x!, @y!, @__scene.camera.x, @__scene.camera.y, LP.width, LP.height

	-- Half the Entity's width.
	halfWidth: => @width/2
	-- Half the Entity's height.
	halfHeight: => @height/2
	-- The center x position of the Entity's hitbox.
	centerX: => @x! - @originX + @halfWidth!
	-- The center y position of the Entity's hitbox.
	centerY: => @y! - @originY + @halfHeight!
	-- The leftmost position of the Entity's hitbox.
	left: => @x! - @originX
	-- The rightmost position of the Entity's hitbox.
	right: => @x! - @originX + @width
	-- The topmost position of the Entity's hitbox.
	top: => @y! - @originY
	-- The bottommost position of the Entity's hitbox.
	bottom: => @y! - @originY + @height

	layer: (value) =>
		return @__layer if value == nil or value == @__layer

		@__graphic.layer = value unless @__graphic == nil

		if @__scene == nil
			-- Reset to the default on false
			@__layer = if value then value else LP.BASELAYER!
			return @__layer

		@__scene\__removeRender @
		-- Reset to the default on false
		@__layer = if value then value else LP.BASELAYER!
		@__scene\__addRender @
		@__layer

	type: (value) =>
		return @__type if value == nil

		if @__scene == nil
			@__type = value
			return @__type

		@__scene\__removeType(@) unless @__type == ""
		@__type = value
		@__scene\__addType(@) unless @__type == ""

		@__type

	mask: (value) =>
		return @__mask if value == nil or @__mask == value

		@__mask\assignTo(nil) unless @__mask == nil
		@__mask = if value then value else nil
		@__mask\assignTo(@) if value

		@__mask

	graphic: (value) =>
		return @__graphic if value == nil or @__graphic == value

		value.layer = @__layer if value
		@__graphic = if value then value else nil
		@__graphic

	scene: (value) =>
		@__scene = value unless value == nil or @__scene == value
		-- Special case to unset @__scene
		@__scene = nil if value == false
		@__scene

	-- The World object is deprecated for FlashPunk-like compatibility
	-- TODO give a massage about this kept for porting compatibility
	world: (value) =>
		@__scene = value unless value == nil
		@__scene

	-- Override this, called when the Entity is added to a Scene.
	added: =>
	-- Override this, called when the Entity is removed from a Scene.
	removed: =>
	-- Updates the Entity.
	update: => super!

	-- Renders the Entity. If you override this for special behaviour,
	-- remember to call super.render() to render the Entity's graphic.
	render: =>
		if @__graphic != nil and @__graphic.visible
			if @__graphic.relative
				@__point.x = @x!
				@__point.y = @y!
			else
				@__point.x, @__point.y = 0, 0
				@__camera.x = if @__scene == nil then LP.camera.x else @__scene.camera.x
				@__camera.y = if @__scene == nil then LP.camera.y else @__scene.camera.y

			@__graphic\render @__point, @__camera

	-- Checks for a collision against an Entity type.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or nil if none were collided.
	collide: (type, x, y) =>
		return nil if @__scene == nil
		return nil if not @collidable or @__scene\typeFirst type == nil

		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y
		e = @__scene\typeFirst type

		if @__mask == nil
			while e != nil
				if do
						c1 = e.collidable and e != @
						c2 = x - @originX + @width >= e\x! - e.originX
						c3 = y - @originY + @height >= e\y! - e.originY
						c4 = x - @originX <= e\x! - e.originX + e.width
						c5 = y - @originY <= e\y! - e.originY + e.height
						c1 and c2 and c3 and c4 and c5
					if e.__mask == nil or e.__mask\collide @HITBOX
						@x @__collide_x
						@y @__collide_y
						return e

				e = e.__typeNext

			@x @__collide_x
			@y @__collide_y

			return nil

		while e != nil
			if do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width >= e\x! - e.originX
					c3 = y - @originY + @height >= e\y! - e.originY
					c4 = x - @originX <= e\x! - e.originX + e.width
					c5 = y - @originY <= e\y! - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if e.__mask\collide (if e.__mask == nil then e.HITBOX else e.__mask)
					@x @__collide_x
					@y @__collide_y
					return e

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y

		return nil

	-- Checks for collision against multiple Entity types.
	-- @param	types		An Array or Vector of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or null if none were collided.
	collideTypes: (types, x, y) =>
		return nil if @__scene == nil

		if type(types) == "string"
			return @collide types, x, y
		else
			unless types == nil
				for t in *types
					e = @collide t, x, y
					return e unless e == nil

		return nil

	-- Checks if this Entity collides with a specific Entity.
	-- @param	e		The Entity to collide against.
	-- @param	x		Virtual x position to place this Entity.
	-- @param	y		Virtual y position to place this Entity.
	-- @return	The Entity if they overlap, or null if they don't.
	collideWith: (e, x, y) =>
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		c = do
			c1 = @collidable and e.collidable
			c2 = x - @originX + @width > e.x - e.originX
			c3 = y - @originY + @height > e.y - e.originY
			c4 = x - @originX < e.x - e.originX + e.width
			c5 = y - @originY < e.y - e.originY + e.height
			c1 and c2 and c3 and c4 and c5
		if c
			if @__mask == nil
				if e.__mask == nil or e.__mask\collide @__hitbox
					@x @__collide_x
					@y @__collide_y
					return e
				@x @__collide_x
				@y @__collide_y
				return nil

			if (if @__mask\collide(e.__mask) == nil then e.__hitbox else e.__mask)
				@x @__collide_x
				@y @__collide_y
				return e

		@x @__collide_x
		@y @__collide_y
		return nil

	-- Checks if this Entity overlaps the specified rectangle.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @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	If they overlap.
	collideRect: (x, y, rX, rY, rWidth, rHeight) =>
		c = do
			c1 = x - @originX + @width >= rX
			c2 = y - @originY + @height >= rY
			c3 = x - @originX <= rX + rWidth
			c4 = y - @originY <= rY + rHeight
			c1 and c2 and c3 and c4
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x = rX
			LP.entity.y = rY
			LP.entity.width = rWidth
			LP.entity.height = rHeight
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Checks if this Entity overlaps the specified position.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	pX			X position.
	-- @param	pY			Y position.
	-- @return	If the Entity intersects with the position.
	collidePoint: (x, y, pX, pY) =>
		c = do
			c1 = pX >= x - @originX
			c2 = pY >= y - @originY
			c3 = pX < x - @originX + @width
			c4 = pY < y - @originY + height
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x, LP.entity.y = pX, pY
			LP.entity.width, LP.entity.height = 1, 1
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Populates an array with all collided Entities of a type. This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	collideInto: (type, x, y, array) =>
		return if @__scene == nil

		e = @__scene.__typeFirst\get type
		return if not @collidable or e == nil

		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		if @__mask == nil
			while e != nil
				c = do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width > e.x - e.originX
					c3 = y - @originY + @height > e.y - e.originY
					c4 = x - @originX < e.x - e.originX + e.width
					c5 = y - @originY < e.y - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if c
					array[#array+1] = e if e__mask == nil or e.__mask\collide @__hitbox

				e = e.__typeNext

			@x @__collide_x
			@y @__collide_y
			return

		while e != nil
			c = do
				c1 = e.collidable and e != @
				c2 = x - @originX + @width > e.x - e.originX
				c3 = y - @originY + @height > e.y - e.originY
				c4 = x - @originX < e.x - e.originX + e.width
				c5 = y - @originY < e.y - e.originY + e.height
				c1 and c2 and c3 and c4 and c5
			if c
				array[#array+1] = e if (if @__mask\collide(e.__mask) == nil then e.__hitbox else e.__mask)

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y
		return

	-- Populates an array with all collided Entities of multiple types.This
	-- function does not empty the array, that responsibility is left to the user.
	-- @param	types		An array of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	collideTypesInto: (types, x, y, array) =>
		return if @__scene == nil
		for t in *types
			@collideInto t, x, y, array

	-- Adds the graphic to the Entity via a Graphiclist.
	-- @param	g		Graphic to add.
	addGraphic: (g) =>
		g.layer = @__layer
		if @graphic! == nil
			@graphic g
		else if moon_type(g) == Graphiclist
			@graphic!\add g
		else
			list = Graphiclist!
			list\add graphic unless @graphic! == nil
			list\add g
			@graphic list
			list\setEntity @

		g

	-- Sets the Entity's hitbox properties.
	-- @param	width		Width of the hitbox.
	-- @param	height		Height of the hitbox.
	-- @param	originX		X origin of the hitbox.
	-- @param	originY		Y origin of the hitbox.
	setHitbox: (width, height, originX = 0, originY = 0) =>
		@width, @height = width, height
		@originX, @originY = originX, originY

	-- Sets the Entity's hitbox to match that of the provided object.
	-- @param	o		The object defining the hitbox (eg. an Image or Rectangle).
	setHitboxTo: (o) =>
		@width, @height = o.width, o.height

		if o.originX and o.originY
			@originX, @originY = o.originX, o.originY
		else
			@originX, @originY = -o.x, -o.y

	-- Sets the origin of the Entity.
	-- @param	x		X origin.
	-- @param	y		Y origin.
	setOrigin: (x, y) => @originX, @originY = x, y

	-- Center's the Entity's origin (half width & height).
	centerOrigin: => @originX, @originY = @halfWidth!, @halfHeight!

	-- Calculates the distance from another Entity.
	-- @param	e				The other Entity.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceFrom: (e, useHitboxes = false) =>
		return math.sqrt (@x! - e\x!) * (@x! - e\x!) + (@y! - e\y!) * (@y! - e\y!) if not useHitboxes
		LP.distanceRects @x! - @originX, @y! - @originY, @width, @height, e\x! - e.originX, e\y! - e.originY, e.width, e.height

	-- Calculates the distance from this Entity to the point.
	-- @param	px				X position.
	-- @param	py				Y position.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceToPoint: (px, py, useHitbox = false) =>
		return math.sqrt (@x! - px) * (@x! - px) + (@y! - py) * (@y! - py) if not useHitbox
		LP.distanceRectPoint px, py, @x! - @originX, @y! - @originY, @width, @height

	-- Calculates the distance from this Entity to the rectangle.
	-- @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 distance.
	distanceToRect: (rx, ry, rwidth, rheight) =>
		LP.distanceRects rx, ry, rwidth, rheight, @x! - @originX, @y! - @originY, @width, @height

	-- Gets the class name as a string.
	-- @return	A string representing the class name.
	toString: => @@.__name

	-- This metamethod only applies to Entity and not subclasses
	__tostring: => @toString!

	-- Moves the Entity by the amount, retaining integer values for its x and y.
	-- @param	x			Horizontal offset.
	-- @param	y			Vertical offset.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveBy: (x, y, solidType, sweep) =>
		@__moveX += x
		@__moveY += y
		x, y = Math.round(@__moveX), Math.round(@__moveY)
		@__moveX -= x
		@__moveY -= y

		if solidType == nil
			@x @x! + x
			@y @y! + y
		else
			unless x == 0
				if @collidable and (sweep or @collideTypes(solidType, @x! + x, @y!) != nil)
					sign = if x > 0 then 1 else -1
					while x != 0
						e = @collideTypes solidType, @x! + sign, @y!
						if e == nil
							@x @x! + sign
						else
							break if @moveCollideX e
							@x @x! + sign

						x -= sign
				else
					@x @x! + x

			unless y == 0
				if @collidable and (sweep or @collideTypes(solidType, @x!, @y! + y) != nil)
					sign = if y > 0 then 1 else -1
					while y != 0
						e = @collideTypes solidType, @x!, @y! + sign
						if e == nil
							@y @y! + sign
						else
							break if @moveCollideY e
							@y @y! + sign

						y -= sign

	-- Moves the Entity to the position, retaining integer values for its x and y.
	-- @param	x			X position.
	-- @param	y			Y position.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTo: (x, y, solidType, sweep) => @moveBy x - @x!, y - @y!, solidType, sweep

	-- Moves towards the target position, retaining integer values for its x and y.
	-- @param	x			X target.
	-- @param	y			Y target.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTowards: (x, y, amount, solidType, sweep) =>
		@__point.x, @__point.y = x - @x!, y - @y!
		@__point\normalize amount if @__point.x^2 + @__point.y^2 > amount^2
		@moveBy @__point.x, @__point.y, solidType, sweep

	-- Moves at an angle by a certain amount, retaining integer values for its x and y.
	-- @param	angle		Angle to move at in degrees.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveAtAngle: (angle, amount, solidType, sweep) =>
		angle *= LP.RAD!
		@moveBy math.cos(angle) * amount @x!, math.sin(angle) * amount + @y!, solidType, sweep

	-- When you collide with an Entity on the x-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideX: (e) => true

	-- When you collide with an Entity on the y-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideY: (e) => true

	-- Clamps the Entity's hitbox on the x-axis.
	-- @param	left		Left bounds.
	-- @param	right		Right bounds.
	-- @param	padding		Optional padding on the clamp.
	clampHorizontal: (left, right, padding = 0) =>
		@x left + @originX + padding if @x! - @originX < left + padding
		@x right - @width + @originX - padding if @x! - @originX + @width > right - padding

	-- Clamps the Entity's hitbox on the y axis.
	-- @param	top			Min bounds.
	-- @param	bottom		Max bounds.
	-- @param	padding		Optional padding on the clamp.
	clampVertical: (top, bottom, padding = 0) =>
		@y top + @originY + padding if @y! - @originY < top + padding
		@y bottom - @height + @originY - padding if @y! - @originY + @height > bottom - padding

	-- Center graphic inside bounding rect.
	centerGraphicInRect: =>
		@__graphic.x, @__graphic.y = @halfWidth!, @halfHeight! unless @__graphic == nil

{ :Entity }


hide short test

hide short test

import Widget from require "lapis.html"

quizs = {'if_list_for_range', 'hello'}
model = require('model')
msk_time = require "msk_time"

Helpers = require('views.helpers')

class AllTests extends Widget
  @include Helpers

  content: =>
    div class: "body", ->
      if @prep
        a href: @url_for("prep-quizs"), ->
          text @_("Finished quizes")
        text ' '
        a href: @url_for("prep-quizs-today"), ->
          text @_("(today)")
        text ' '
        a href: @url_for("prep-kr", {name: 'hello'}), ->
          text @_("kr1")
        text ' '
        url = @url_for("prep-kr", {
          name: 'if_list_for_range_short'})
        a href: url, ->
          text @_("kr2")
      p @_("Start new quiz:")
      element 'table', -> element 'tr', ->
        for name in *quizs
          element 'td', ->
            @new_test_button name
      my_quizs = (text, state) ->
        qq = model.my_quizs(@, state)
        if #qq > 0
          p text
        element 'ul', ->
          for quiz in *qq
            element 'li', ->
              time = msk_time(quiz.created_at)
              raw quiz\anchor(@) .. ', ' .. time
              if quiz.state == model.FINISHED
                r = quiz.right_answers
                a = quiz.tasks
                b " | #{r} / #{a}"
      my_quizs @_("Your active quizes:"), model.ACTIVE
      my_quizs @_("Your finished quizes:"), model.FINISHED

import Widget from require "lapis.html"

quizs = require('quiz.all')
model = require('model')
msk_time = require "msk_time"

Helpers = require('views.helpers')

class AllTests extends Widget
  @include Helpers

  content: =>
    div class: "body", ->
      if @prep
        a href: @url_for("prep-quizs"), ->
          text @_("Finished quizes")
        text ' '
        a href: @url_for("prep-quizs-today"), ->
          text @_("(today)")
        text ' '
        a href: @url_for("prep-kr", {name: 'hello'}), ->
          text @_("kr1")
        text ' '
        url = @url_for("prep-kr", {
          name: 'if_list_for_range_short'})
        a href: url, ->
          text @_("kr2")
      p @_("Start new quiz:")
      element 'table', -> element 'tr', ->
        for name, _ in pairs quizs
          element 'td', ->
            @new_test_button name
      my_quizs = (text, state) ->
        qq = model.my_quizs(@, state)
        if #qq > 0
          p text
        element 'ul', ->
          for quiz in *qq
            element 'li', ->
              time = msk_time(quiz.created_at)
              raw quiz\anchor(@) .. ', ' .. time
              if quiz.state == model.FINISHED
                r = quiz.right_answers
                a = quiz.tasks
                b " | #{r} / #{a}"
      my_quizs @_("Your active quizes:"), model.ACTIVE
      my_quizs @_("Your finished quizes:"), model.FINISHED


plugins/data: add WHOX_001 handler

plugins/data: add WHOX_001 handler

import IRCClient from require 'irc'

unpack = unpack or table.unpack

caps = {'extended-join', 'multi-prefix', 'away-notify', 'account-notify',
	'chghost', 'server-time', 'echo-message', 'invite-notify'}

IRCClient\add_hook 'CONNECT', =>
	@channels = {}
	@users    = {}
	@server   = {
		caps:       {}
		ircv3_caps: {}
		batches:    {}
	}

IRCClient\add_hook 'ACK_CAP', =>
	@data.set_caps -= 1
	if @data.set_caps <= 0
		@send_raw 'CAP END'

IRCClient\add_hook 'CONNECT', =>
	-- Welcome
	@data = {} if not @data
	@data.last_connect = os.time()
	@data.set_caps = 0
	@send_raw 'CAP LS 302'
	for i=1, #caps
		@data.set_caps += 1

IRCClient\add_handler 'BATCH', (prefix, args, trail, tags)=>
	tag_type, tag = args[1]\match '(.)(.+)'
	if tag_type == '+'
		@server.batches[tag] = {unpack(args, 2)}
	elseif tag_type == '-'
		@server.batches[tag] = nil

IRCClient\add_handler '005', (prefix, args)=>
	-- Capabilities
	isupport_caps = {select 2, unpack args}
	for cap in *isupport_caps
		if cap\sub(1, 1) == "-"
			-- remove support
			if cap\find "=" then
				@server.caps[cap\match "^%-(.+)="] = nil
			else
				@server.caps[cap\sub 2] = nil
		elseif cap\find "="
			key, value = cap\match '^(.-)=(.+)'
			@server.caps[key] = value
		else
			@server.caps[cap] = true

IRCClient\add_handler 'AWAY', (prefix, args, trail)=>
	nick = prefix\match '^(.-)!'
	@users[nick].away = trail

IRCClient\add_handler 'ACCOUNT', (prefix, args, trail)=>
	nick = prefix\match '^(.-)!'
	@users[nick].account = args[1] != "*" and args[1] or nil

IRCClient\add_handler 'JOIN', (prefix, args, trail, tags={})=>
	-- user JOINs a channel
	local channel
	local account
	if @server.ircv3_caps['extended-join']
		account = args[2] if args[2] != '*'
		channel = args[1]
	elseif @server.ircv3_caps['account-tag'] and tags.account
		account = tags.account
		channel = args[1]
	else
		channel = args[1] or trail
	nick, username, host = prefix\match '^(.-)!(.-)@(.-)$'
	if prefix\match '^.-!.-@.-$'
		if not @users[nick] then
			@users[nick] = {
				account: account
				channels: {
					[channel]: {
						status: ""
					}
				},
				:username,
				:host
			}
		else
			if not @users[nick].channels
				@users[nick].channels = {
					[channel]: {
						status: ""
					}
				}
			else
				@users[nick].channels[channel] = status: ""
		@users[nick].account = account if account
	if not @channels[channel]
		-- ::TODO:: optimize out for ISUPPORT WHOX
		if not @server.ircv3_caps['userhost-in-names']
			@send_raw ('WHO %s')\format channel
		-- else
		--	@send_raw ('NAMES %s')\format channel

		if @server.caps['WHOX']
			@send_raw "WHO #{channel} %nat,001"

		@channels[channel] = {
			users: {
				[nick]: @users[nick]
			}
		}
	else
		@channels[channel].users[nick] = @users[nick]

IRCClient\add_hook 'WHOX_001', (nick, account)=>
	@users[nick].account = account if @users[nick] and account ~= '0'

IRCClient\add_handler 'NICK', (prefix, args, trail)=>
	old = prefix\match('^(.-)!') or prefix
	new = args[1] or trail
	for channel_name in pairs @users[old].channels
		@channels[channel_name].users[new] = @channels[channel_name].users[old]
		@channels[channel_name].users[old] = nil
	@users[new] = @users[old]
	@users[old] = nil

IRCClient\add_handler 'MODE', (prefix, args)=>
	-- User or bot called /mode
	if args[1] and args[1]\sub(1,1) == "#"
		@send_raw ('NAMES %s')\format args[1]

IRCClient\add_handler '353', (prefix, args, trail)=>
	-- Result of NAMES
	channel = args[3]
	statuses = @server.caps.PREFIX and @server.caps.PREFIX\match '%(.-%)(.+)' or "+@"
	statuses = "[" .. statuses\gsub("%p", "%%%1") .. "]"
	for text in trail\gmatch '%S+'
		local status, pre, nick, user, host
		if text\match statuses
			status, pre = text\match ('^(%s+)(.+)')\format statuses
		else
			status, pre = '', text
		if @server.ircv3_caps['userhost-in-names']
			nick, user, host = pre\match '^(.-)!(.-)@(.-)$'
		else
			nick = pre
		if not @users[nick]
			@users[nick] = {channels: {}}
		@users[nick].user = user if user
		@users[nick].host = host if host
		if @channels[channel].users[nick]
			if @users[nick].channels[channel]
				@users[nick].channels[channel].status = status
			else
				@users[nick].channels[channel] = :status
		else
			@channels[channel].users[nick] = @users[nick]
			@users[nick].channels[channel] = :status

IRCClient\add_handler '352', (prefix, args)=>
	_, user, host, _, nick, away = unpack args
	@users[nick] = {channels: {}} if not @users[nick]
	@users[nick].user = user
	@users[nick].host = host
	@users[nick].away = away\sub(1, 1) == "G"

IRCClient\add_handler 'CHGHOST', (prefix, args)=>
	nick = prefix\match '^(.-)!'
	@users[nick].user = args[1]
	@users[nick].host = args[2]

IRCClient\add_handler 'KICK', (prefix, args)=>
	channel = args[1]
	nick = args[2]
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil

IRCClient\add_handler 'PART', (prefix, args)=>
	-- User or bot parted channel, clear from lists
	channel = args[1]
	nick = prefix\match '^(.-)!'
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil -- User left network, garbagecollect

IRCClient\add_handler 'QUIT', (prefix, args)=>
	-- User or bot parted network, nuke from lists
	nick = prefix\match '^(.-)!'
	for channel in pairs @users[nick].channels
		@channels[channel].users[nick] = nil
	@users[nick] = nil

IRCClient\add_handler 'CAP', (prefix, args, trailing)=>
	to_process = {} if args[2] == 'LS' or args[2] == 'ACK' or args[2] == 'NAK'
	if args[2] == 'LS' or args[2] == 'ACK' or args[2] == 'NEW' or args[2] == 'DEL'
		for item in trailing\gmatch '%S+'
			for cap in *caps
				if item == cap
					to_process[#to_process + 1] = cap
	if args[2] == 'LS'
		if #to_process > 0
			@send_raw ('CAP REQ :%s')\format table.concat(to_process, ' ')
		for i=#to_process + 1, #caps
			@fire_hook 'ACK_CAP'
	elseif args[2] == 'NEW'
		to_send = {}
		for item in trailing\gmatch '%S+'
			for cap in *caps
				if item == cap
					to_send[#to_send + 1] = item
		@send_raw ('CAP REQ :%s')\format table.concat(to_send, ' ')
	elseif args[2] == 'DEL'
		for item in trailing\gmatch '%S+'
			@ircv3_caps[item] = nil
	elseif args[2] == 'ACK'
		for cap in *to_process
			key, value = cap\match '^(.-)=(.+)'
			if value
				@server.ircv3_caps[key] = value
			else
				@server.ircv3_caps[cap] = true
			@fire_hook 'ACK_CAP'

import IRCClient from require 'irc'

unpack = unpack or table.unpack

caps = {'extended-join', 'multi-prefix', 'away-notify', 'account-notify',
	'chghost', 'server-time', 'echo-message', 'invite-notify'}

IRCClient\add_hook 'CONNECT', =>
	@channels = {}
	@users    = {}
	@server   = {
		caps:       {}
		ircv3_caps: {}
		batches:    {}
	}

IRCClient\add_hook 'ACK_CAP', =>
	@data.set_caps -= 1
	if @data.set_caps <= 0
		@send_raw 'CAP END'

IRCClient\add_hook 'CONNECT', =>
	-- Welcome
	@data = {} if not @data
	@data.last_connect = os.time()
	@data.set_caps = 0
	@send_raw 'CAP LS 302'
	for i=1, #caps
		@data.set_caps += 1

IRCClient\add_handler 'BATCH', (prefix, args, trail, tags)=>
	tag_type, tag = args[1]\match '(.)(.+)'
	if tag_type == '+'
		@server.batches[tag] = {unpack(args, 2)}
	elseif tag_type == '-'
		@server.batches[tag] = nil

IRCClient\add_handler '005', (prefix, args)=>
	-- Capabilities
	isupport_caps = {select 2, unpack args}
	for cap in *isupport_caps
		if cap\sub(1, 1) == "-"
			-- remove support
			if cap\find "=" then
				@server.caps[cap\match "^%-(.+)="] = nil
			else
				@server.caps[cap\sub 2] = nil
		elseif cap\find "="
			key, value = cap\match '^(.-)=(.+)'
			@server.caps[key] = value
		else
			@server.caps[cap] = true

IRCClient\add_handler 'AWAY', (prefix, args, trail)=>
	nick = prefix\match '^(.-)!'
	@users[nick].away = trail

IRCClient\add_handler 'ACCOUNT', (prefix, args, trail)=>
	nick = prefix\match '^(.-)!'
	@users[nick].account = args[1] != "*" and args[1] or nil

IRCClient\add_handler 'JOIN', (prefix, args, trail, tags={})=>
	-- user JOINs a channel
	local channel
	local account
	if @server.ircv3_caps['extended-join']
		account = args[2] if args[2] != '*'
		channel = args[1]
	elseif @server.ircv3_caps['account-tag'] and tags.account
		account = tags.account
		channel = args[1]
	else
		channel = args[1] or trail
	nick, username, host = prefix\match '^(.-)!(.-)@(.-)$'
	if prefix\match '^.-!.-@.-$'
		if not @users[nick] then
			@users[nick] = {
				account: account
				channels: {
					[channel]: {
						status: ""
					}
				},
				:username,
				:host
			}
		else
			if not @users[nick].channels
				@users[nick].channels = {
					[channel]: {
						status: ""
					}
				}
			else
				@users[nick].channels[channel] = status: ""
		@users[nick].account = account if account
	if not @channels[channel]
		if @server.ircv3_caps['userhost-in-names']
			@send_raw ('NAMES %s')\format channel
		else
			@send_raw ('WHO %s')\format channel
		@channels[channel] = {
			users: {
				[nick]: @users[nick]
			}
		}
	else
		@channels[channel].users[nick] = @users[nick]

IRCClient\add_handler 'NICK', (prefix, args, trail)=>
	old = prefix\match('^(.-)!') or prefix
	new = args[1] or trail
	for channel_name in pairs @users[old].channels
		@channels[channel_name].users[new] = @channels[channel_name].users[old]
		@channels[channel_name].users[old] = nil
	@users[new] = @users[old]
	@users[old] = nil

IRCClient\add_handler 'MODE', (prefix, args)=>
	-- User or bot called /mode
	if args[1] and args[1]\sub(1,1) == "#"
		@send_raw ('NAMES %s')\format args[1]

IRCClient\add_handler '353', (prefix, args, trail)=>
	-- Result of NAMES
	channel = args[3]
	statuses = @server.caps.PREFIX and @server.caps.PREFIX\match '%(.-%)(.+)' or "+@"
	statuses = "[" .. statuses\gsub("%p", "%%%1") .. "]"
	for text in trail\gmatch '%S+'
		local status, pre, nick, user, host
		if text\match statuses
			status, pre = text\match ('^(%s+)(.+)')\format statuses
		else
			status, pre = '', text
		if @server.ircv3_caps['userhost-in-names']
			nick, user, host = pre\match '^(.-)!(.-)@(.-)$'
		else
			nick = pre
		if not @users[nick]
			@users[nick] = {channels: {}}
		@users[nick].user = user if user
		@users[nick].host = host if host
		if @channels[channel].users[nick]
			if @users[nick].channels[channel]
				@users[nick].channels[channel].status = status
			else
				@users[nick].channels[channel] = :status
		else
			@channels[channel].users[nick] = @users[nick]
			@users[nick].channels[channel] = :status

IRCClient\add_handler '352', (prefix, args)=>
	_, user, host, _, nick, away = unpack args
	@users[nick] = {channels: {}} if not @users[nick]
	@users[nick].user = user
	@users[nick].host = host
	@users[nick].away = away\sub(1, 1) == "G"

IRCClient\add_handler 'CHGHOST', (prefix, args)=>
	nick = prefix\match '^(.-)!'
	@users[nick].user = args[1]
	@users[nick].host = args[2]

IRCClient\add_handler 'KICK', (prefix, args)=>
	channel = args[1]
	nick = args[2]
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil

IRCClient\add_handler 'PART', (prefix, args)=>
	-- User or bot parted channel, clear from lists
	channel = args[1]
	nick = prefix\match '^(.-)!'
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil -- User left network, garbagecollect

IRCClient\add_handler 'QUIT', (prefix, args)=>
	-- User or bot parted network, nuke from lists
	nick = prefix\match '^(.-)!'
	for channel in pairs @users[nick].channels
		@channels[channel].users[nick] = nil
	@users[nick] = nil

IRCClient\add_handler 'CAP', (prefix, args, trailing)=>
	to_process = {} if args[2] == 'LS' or args[2] == 'ACK' or args[2] == 'NAK'
	if args[2] == 'LS' or args[2] == 'ACK' or args[2] == 'NEW' or args[2] == 'DEL'
		for item in trailing\gmatch '%S+'
			for cap in *caps
				if item == cap
					to_process[#to_process + 1] = cap
	if args[2] == 'LS'
		if #to_process > 0
			@send_raw ('CAP REQ :%s')\format table.concat(to_process, ' ')
		for i=#to_process + 1, #caps
			@fire_hook 'ACK_CAP'
	elseif args[2] == 'NEW'
		to_send = {}
		for item in trailing\gmatch '%S+'
			for cap in *caps
				if item == cap
					to_send[#to_send + 1] = item
		@send_raw ('CAP REQ :%s')\format table.concat(to_send, ' ')
	elseif args[2] == 'DEL'
		for item in trailing\gmatch '%S+'
			@ircv3_caps[item] = nil
	elseif args[2] == 'ACK'
		for cap in *to_process
			key, value = cap\match '^(.-)=(.+)'
			if value
				@server.ircv3_caps[key] = value
			else
				@server.ircv3_caps[cap] = true
			@fire_hook 'ACK_CAP'


Some cosmetic changes in either_spec

Some cosmetic changes in either_spec

require 'moon.all'
either_mod = require'ngx-oauth.either'
import Left, Right, either, encase, encase2 from either_mod
import mtype from require 'ngx-oauth.util'

behaves_like_either = (type_func, type_name) ->
  other = type_func == Right and Left or Right

  describe 'metatable.__eq', ->
    context 'same Either types with same value', ->
      it 'returns true', -> assert.is_true type_func(42) == type_func(42)

    context 'same Either types with different value', ->
      it 'returns false', -> assert.is_false type_func(42) == type_func(66)

    context 'different Either types with same value', ->
      it 'returns false', -> assert.is_false type_func(42) == other(42)

    context 'Either with non-Either', ->
      it 'returns false', -> assert.is_false type_func(66) == 66

  describe 'metatable.__tostring', ->
    it "returns #{type_name}(<value>)", ->
      assert.same "#{type_name}(42)", tostring(type_func(42))

  describe 'metatable.__type', ->
    it "is #{type_name}", ->
      assert.same type_name, getmetatable(type_func(42)).__type

describe '__call', ->
  it 'is alias for either', ->
    onleft, onright, right = ->, ->, Right(42)
    spy.on(either_mod, 'either')

    either_mod(onleft, onright, right)
    assert.stub(either_mod.either).called_with(onleft, onright, right)

describe 'Left', ->
  behaves_like_either Left, 'Left'

  left = Left(66)

  for func_name in *{'ap', 'map', 'chain'} do
    describe func_name, ->
      it 'returns self', ->
        assert.equal left, left[func_name](Right(42))

describe 'Right', ->
  behaves_like_either Right, 'Right'

  right = Right(42)

  describe 'ap', ->
    fright = Right((x) -> x * 2)

    context 'given Right', ->
      it "returns Right which value is result of applying this Right's value to given Right's value", ->
        given = Right(42)
        result = fright.ap(given)
        assert.equal Right(84), result

    context 'given Left', ->
      it 'returns given Left', ->
        given = Left(66)
        assert.equal given, fright.ap(given)

    context 'neither Left, nor Right', ->
      it 'throws error', ->
        assert.has_error -> fright.ap(66)

    context 'when this value is not a function', ->
      it 'throws error', ->
        assert.has_error -> right.ap(Left(66))

  describe 'map', ->
    it "returns Right which value is result of applying given function to this Right's value", ->
      result = right.map((x) -> x * 2)
      assert.equal Right(84), result

  describe 'chain', ->
    it "returns result of applying given function to this Right's value", ->
      assert.same 84, right.chain((x) -> x * 2)

describe 'either', ->

  before_each ->
    export onleft = mock(->)
    export onright = mock(->)

  context 'given Left', ->
    it "calls onleft handler with Left's value", ->
      either(onleft, onright, Left(66))
      assert.stub(onleft).called_with(66)
      assert.stub(onright).not_called!

  context 'given Right', ->
    it "calls onright handler with Right's value", ->
      either(onleft, onright, Right(42))
      assert.stub(onleft).not_called!
      assert.stub(onright).called_with(42)

  context 'given neither Left, nor Right', ->
    it 'throws error and does not call any handler', ->
      assert.has_error -> either(->, ->, {})
      assert.stub(onleft).not_called!
      assert.stub(onright).not_called!

shared_encase = (encase_func) ->
  it 'returns a function that wraps the given func and passes its arguments to it', ->
    func = mock(->)
    func2 = encase_func(func)
    assert.is_function func2
    func2(1, 2, 3)
    assert.stub(func).called_with(1, 2, 3)

describe 'encase', ->

  shared_encase encase

  context 'when given func has not raised error', ->
    it "nested function returns Right with the func's return value", ->
      result = encase(-> 'hai!')()
      assert.equal Right('hai!'), result

  context 'when given func has raised error', ->
    it 'nested function returns Left with an error message', ->
      result = encase(table.insert)()
      assert.same 'Left', mtype(result)
      assert.match 'bad argument.*', result.value

describe 'encase2', ->

  shared_encase encase2

  context 'when given func returned non-nil value', ->
    func = -> 'OK!', nil

    it "nested function returns Right with the func's 1st result value", ->
      result = encase2(func)()
      assert.equal Right('OK!'), result

  context 'when func returns nil and a value', ->
    func = -> nil, 'FAIL!'

    it "nested function returns Left with the func's 2nd result value", ->
      result = encase2(func)()
      assert.equal Left('FAIL!'), result

require 'moon.all'
either_mod = require'ngx-oauth.either'
import Left, Right, either, encase, encase2 from either_mod
import mtype from require 'ngx-oauth.util'

behaves_like_either = (type_func, type_name) ->
  other = type_func == Right and Left or Right

  describe 'metatable.__eq', ->
    context 'same Either types with same value', ->
      it 'returns true', -> assert.is_true type_func(42) == type_func(42)

    context 'same Either types with different value', ->
      it 'returns false', -> assert.is_false type_func(42) == type_func(66)

    context 'different Either types with same value', ->
      it 'returns false', -> assert.is_false type_func(42) == other(42)

    context 'Either with non-Either', ->
      it 'returns false', -> assert.is_false type_func(66) == 66

  describe 'metatable.__tostring', ->
    it "returns #{type_name}(<value>)", ->
      assert.same "#{type_name}(42)", tostring(type_func(42))

  describe 'metatable.__type', ->
    it "is #{type_name}", ->
      assert.same type_name, getmetatable(type_func(42)).__type

describe '__call', ->
  it 'is alias for either', ->
    onleft, onright, right = ->, ->, Right(42)
    spy.on(either_mod, 'either')

    either_mod(onleft, onright, right)
    assert.stub(either_mod.either).called_with(onleft, onright, right)

describe 'Left', ->
  behaves_like_either Left, 'Left'

  left = Left(66)

  for func_name in *{'ap', 'map', 'chain'} do
    describe func_name, ->
      it 'returns self', ->
        assert.equal left, left[func_name](Right(42))

describe 'Right', ->
  behaves_like_either Right, 'Right'

  right = Right(42)

  describe 'ap', ->
    fright = Right((x) -> x * 2)

    context 'given Right', ->
      it "returns Right which value is result of applying this Right's value to given Right's value", ->
        given = Right(42)
        result = fright.ap(given)
        assert.equal Right(84), result

    context 'given Left', ->
      it 'returns given Left', ->
        given = Left(66)
        assert.equal given, fright.ap(given)

    context 'neither Left, nor Right', ->
      it 'throws error', ->
        assert.has_error -> fright.ap(66)

    context 'when this value is not a function', ->
      it 'throws error', ->
        assert.has_error -> right.ap(Left(66))

  describe 'map', ->
    it "returns Right which value is result of applying given function to this Right's value", ->
      result = right.map((x) -> x * 2)
      assert.equal Right(84), result

  describe 'chain', ->
    it "returns result of applying given function to this Right's value", ->
      assert.same 84, right.chain((x) -> x * 2)

describe 'either', ->

  before_each ->
    export onleft = mock(->)
    export onright = mock(->)

  context 'given Left', ->
    it "calls onleft handler with Left's value", ->
      either(onleft, onright, Left(66))
      assert.stub(onleft).called_with(66)
      assert.stub(onright).not_called()

  context 'given Right', ->
    it "calls onright handler with Right's value", ->
      either(onleft, onright, Right(42))
      assert.stub(onleft).not_called()
      assert.stub(onright).called_with(42)

  context 'given neither Left, nor Right', ->
    it 'throws error and does not call any handler', ->
      assert.has_error -> either(->, ->, {})
      assert.stub(onleft).not_called()
      assert.stub(onright).not_called()

shared_encase = (encase_func) ->
  it 'returns a function that wraps the given func and passes its arguments to it', ->
    func = mock(->)
    func2 = encase_func(func)
    assert.is_function func2
    func2(1, 2, 3)
    assert.stub(func).called_with(1, 2, 3)

describe 'encase', ->

  shared_encase encase

  context 'when given func has not raised error', ->
    it "nested function returns Right with the func's return value", ->
      result = encase(-> 'hai!')()
      assert.equal Right('hai!'), result

  context 'when given func has raised error', ->
    it 'nested function returns Left with an error message', ->
      result = encase(table.insert)()
      assert.same 'Left', mtype(result)
      assert.match 'bad argument.*', result.value

describe 'encase2', ->

  shared_encase encase2

  context 'when given func returned non-nil value', ->
    func = -> 'OK!', nil

    it "nested function returns Right with the func's 1st result value", ->
      result = encase2(func)()
      assert.equal Right('OK!'), result

  context 'when func returns nil and a value', ->
    func = -> nil, 'FAIL!'

    it "nested function returns Left with the func's 2nd result value", ->
      result = encase2(func)()
      assert.equal Left('FAIL!'), result


Return a method for the mixin instead of a class

* This allows the user of the mixin to not have to use the moon.mixin
  function directly

moon = require "moon"

return (t) ->
	clone = class
		clone: =>
			c = @@!
			moon.mixin_table c, @
	moon.mixin t, clone

moon = require "moon"

return class clone
	new: =>
	clone: =>
		c = @@!
		moon.mixin_table c, @
		c