Added a missing require to Entity

Added a missing require to Entity

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

export ^

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 bottommost position of the Entity's hitbox.
		@bottom = 0
		-- The center x position of the Entity's hitbox.
		@centerX = 0
		-- The center y position of the Entity's hitbox.
		@centerY = 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 if 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 if graphic != nil

		@__hitbox\assignTo @

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

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

	-- Y position of the Entity in the Scene.
	y: (value) =>
		if 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

		@__graphic.layer = value if @__graphic != nil

		if @__scene == nil
			@__layer = value
			return @__layer

		@__scene\removeRender @
		@__layer = if value then value else nil
		@__scene\addRender @
		@__layer

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

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

		@__scene\removeType(@) if @__type != ""
		@__type = value
		@__scene\addType(@) if @__type != ""

		@__type

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

		@__mask\assignTo(nil) if @__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

	-- The World object is deprecated for FlashPunk-like compatibility
	-- TODO give a massage about this kept for porting compatibility
	world: (value) =>
		@__scene = value if 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 null if none were collided.
	collide: (type, x, y) =>
		return nil if @__scene == nil

		return nil if not @collidable or @__scene.__typeFirst == nil
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y
		e = @__scene.typeFirst
		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
					if e.__mask == nil or e.__mask\collide @HITBOX
						@x @__collide_x
						@y @__collide_y
						return e

				e = e.__typeNext

			@x @__collide_x
			@y @__colide_y

			return 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
				if e.__mask\collide (if e.__mask != nil then e.__mask else e.HITBOX)
					@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
			if types != nil
				for t in *types
					e = @collide t, x, y
					return e if 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.__mask else e.__hitbox)
				@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.
	-- @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.
	-- @return	The array, populated with all collided Entities.
	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.__mask else e.__hitbox)

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y
		return

	-- Populates an array with all collided Entities of multiple types.
	-- @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.
	-- @return	The array, populated with all collided Entities.
	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 if @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, originY) =>
		@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
			@originX, @originY = -@originX, -@originY

	-- 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) =>
		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) =>
		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

	__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
			if 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
							break if @moveCollideX e
							@x @x! + sign
						else
							@x @x! + sign

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

			if 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
							break if @moveCollideY e
							@y @y! + sign
						else
							@y @y! + sign

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

	-- 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 * @__point.x + @__point.y * @__point.y > amount * amount
		@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) =>
		@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) =>
		@y top + @originY + padding if @y! - @originY < top + padding
		@y bottom - @height + @originX - padding if @y! - @originY + @height > bottom - padding

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

require "LunoPunk.graphics.Graphiclist"
require "LunoPunk.LP"
require "LunoPunk.Mask"
require "LunoPunk.Tweener"
require "LunoPunk.utils.moonscript"

export ^

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 bottommost position of the Entity's hitbox.
		@bottom = 0
		-- The center x position of the Entity's hitbox.
		@centerX = 0
		-- The center y position of the Entity's hitbox.
		@centerY = 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 if 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 if graphic != nil

		@__hitbox\assignTo @

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

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

	-- Y position of the Entity in the Scene.
	y: (value) =>
		if 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

		@__graphic.layer = value if @__graphic != nil

		if @__scene == nil
			@__layer = value
			return @__layer

		@__scene\removeRender @
		@__layer = if value then value else nil
		@__scene\addRender @
		@__layer

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

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

		@__scene\removeType(@) if @__type != ""
		@__type = value
		@__scene\addType(@) if @__type != ""

		@__type

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

		@__mask\assignTo(nil) if @__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

	-- The World object is deprecated for FlashPunk-like compatibility
	-- TODO give a massage about this kept for porting compatibility
	world: (value) =>
		@__scene = value if 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 null if none were collided.
	collide: (type, x, y) =>
		return nil if @__scene == nil

		return nil if not @collidable or @__scene.__typeFirst == nil
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y
		e = @__scene.typeFirst
		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
					if e.__mask == nil or e.__mask\collide @HITBOX
						@x @__collide_x
						@y @__collide_y
						return e

				e = e.__typeNext

			@x @__collide_x
			@y @__colide_y

			return 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
				if e.__mask\collide (if e.__mask != nil then e.__mask else e.HITBOX)
					@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
			if types != nil
				for t in *types
					e = @collide t, x, y
					return e if 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.__mask else e.__hitbox)
				@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.
	-- @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.
	-- @return	The array, populated with all collided Entities.
	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.__mask else e.__hitbox)

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y
		return

	-- Populates an array with all collided Entities of multiple types.
	-- @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.
	-- @return	The array, populated with all collided Entities.
	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 if @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, originY) =>
		@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
			@originX, @originY = -@originX, -@originY

	-- 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) =>
		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) =>
		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

	__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
			if 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
							break if @moveCollideX e
							@x @x! + sign
						else
							@x @x! + sign

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

			if 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
							break if @moveCollideY e
							@y @y! + sign
						else
							@y @y! + sign

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

	-- 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 * @__point.x + @__point.y * @__point.y > amount * amount
		@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) =>
		@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) =>
		@y top + @originY + padding if @y! - @originY < top + padding
		@y bottom - @height + @originX - padding if @y! - @originY + @height > bottom - padding

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


dict: support multiple definitions

dict: support multiple definitions

Former-commit-id: be3f9da85e4935686e57e99017434126dcfe7b4e [formerly 35fe251786d73eb7513918d15c6b488a6df934e4]
Former-commit-id: b12d6ba91acc9ec9cfec9a837a28bfe8cc63bf4a

-- GNU GCIDE dict, see tools/gcide2sqlite.lua
sql = require'lsqlite3'

lookup = (s, d, word) =>
  db = sql.open 'cache/words.sqlite3'
  stmt = db\prepare "SELECT * FROM words WHERE lower(word) = lower(?)"
  code = stmt\bind_values word
  out = {}
  i = 0
  for row in stmt\rows!
    i = i +1
    vals = stmt\get_values!
    word = vals[1]
    pos = vals[2]
    if pos then pos = " (#{ivar2.util.italic pos})" else pos = ''
    field = vals[3]
    if field then field = " #{field}" else field = ''
    definition = vals[4]
    table.insert out, "[#{ivar2.util.bold i}] #{pos}#{field} : #{definition}"
  db\close!
  if #out == 0
    -- Invalid name
    reply 'Nope. You suck. '
  else
    say "[#{ivar2.util.bold word}] #{table.concat out, ' '}"

PRIVMSG:
  '^%pdict (.+)$': lookup
  '^%pdefine (.+)$': lookup

-- GNU GCIDE dict, see tools/gcide2sqlite.lua
sql = require'lsqlite3'

lookup = (s, d, word) =>
  db = sql.open 'cache/words.sqlite3'
  stmt = db\prepare "SELECT * FROM words WHERE lower(word) = lower(?)"
  code = stmt\bind_values word
  code = stmt\step!
  if code == sqlite3.DONE
    -- Invalid name
    db\close!
    reply 'Nope. You suck. '
    return
  vals = stmt\get_values!
  db\close!
  pos = vals[2]
  if pos then pos = " (#{ivar2.util.italic pos})" else pos = ''
  field = vals[3]
  if field then field = " #{field}" else field = ''
  definition = vals[4]
  say "[#{ivar2.util.bold vals[1]}]#{pos}#{field} : #{definition}"

PRIVMSG:
  '^%pdict (.+)$': lookup


fix device reference

fix device reference

Req = require "req"
Res = require "res"
log = require "log"
ctrl = require "device"

with net.createServer net.TCP
  \listen 80, (conn) ->
    conn\on "receive", (client, data) ->
      req = Req client, data
      res = Res client
      route = "#{req.method} #{req.url}"
      verbose = (msg) ->
        log.info "#{route}: #{msg}"
      clean = ->
        client\close()
        req = nil
        res = nil
        data = nil
        client = nil
        collectgarbage()
        log.debug "heap: #{node.heap()}"

      switch true

        when route\find("GET /status") != nil
          ret = {}
          for name, device in pairs ctrl
            ret[name] = device\value()
          ret['heap'] = node.heap()
          res\send ret, ->
            verbose 'get status'
            clean()

        when route\find("PUT /motor/%a+/%d+") != nil
          device, val = route\match "PUT /motor/(%a+)/(%d+)"
          val = tonumber val
          ctrl[device]\speed val
          res\send "", ->
            verbose "#{device} speed #{val}"
            clean()

        when route\find("PUT /sw/%a+/toggle") != nil
          name = route\match "PUT /sw/(%a+)/toggle"
          ctrl[name]\toggle()
          res\send "", ->
            verbose "toggle sw #{name}"
            clean()

        when route\find("PUT /sw/%a+/on") != nil
          name = route\match "PUT /sw/(%a+)/toggle"
          ctrl[name]\on()
          res\send "", ->
            verbose "on sw #{name}"
            clean()

        when route\find("PUT /sw/%a+/off") != nil
          name = route\match "PUT /sw/(%a+)/toggle"
          ctrl[name]\off()
          res\send "", ->
            verbose "off sw #{name}"
            clean()

	when route\find("GET /reset") != nil
          res\send "", ->
            verbose "reset"
            clean()
            node.restart()

        else
          res\status 404
          res\send "", ->
            verbose "not found"
            clean()

Req = require "req"
Res = require "res"
log = require "log"
ctrl = require "device"

with net.createServer net.TCP
  \listen 80, (conn) ->
    conn\on "receive", (client, data) ->
      req = Req client, data
      res = Res client
      route = "#{req.method} #{req.url}"
      verbose = (msg) ->
        log.info "#{route}: #{msg}"
      clean = ->
        client\close()
        req = nil
        res = nil
        data = nil
        client = nil
        collectgarbage()
        log.debug "heap: #{node.heap()}"

      switch true

        when route\find("GET /status") != nil
          ret = {}
          for name, device in pairs ctrl
            ret[name] = device\value()
          ret['heap'] = node.heap()
          res\send ret, ->
            verbose 'get status'
            clean()

        when route\find("PUT /motor/%a+/%d+") != nil
          device, val = route\match "PUT /motor/(%a+)/(%d+)"
          val = tonumber val
          ctrl.motor[device]\speed val
          res\send "", ->
            verbose "#{device} speed #{val}"
            clean()

        when route\find("PUT /sw/%a+/toggle") != nil
          name = route\match "PUT /sw/(%a+)/toggle"
          ctrl.sw[name]\toggle()
          res\send "", ->
            verbose "toggle sw #{name}"
            clean()

        when route\find("PUT /sw/%a+/on") != nil
          name = route\match "PUT /sw/(%a+)/toggle"
          ctrl.sw[name]\on()
          res\send "", ->
            verbose "on sw #{name}"
            clean()

        when route\find("PUT /sw/%a+/off") != nil
          name = route\match "PUT /sw/(%a+)/toggle"
          ctrl.sw[name]\off()
          res\send "", ->
            verbose "off sw #{name}"
            clean()

	when route\find("GET /reset") != nil
          res\send "", ->
            verbose "reset"
            clean()
            node.restart()

        else
          res\status 404
          res\send "", ->
            verbose "not found"
            clean()


renamed Sludge

renamed Sludge

export ^

class Item
-- used for things on the map that minions can pick up

class Energy extends Item
    draw: =>
        love.graphics.setColor(66, 145, 135)
        love.graphics.rectangle("fill", -5, -5, 10, 10)

class Material extends Item
    draw: =>
        love.graphics.setColor(243, 127, 183)
        love.graphics.rectangle("fill", -5, -5, 10, 10)

class Sludge extends Item
    draw: =>
        love.graphics.setColor(87, 119, 32)
        love.graphics.rectangle("fill", -5, -5, 10, 10)

class Food extends Item
    draw: =>
        love.graphics.setColor(159, 112, 19)
        love.graphics.rectangle("fill", -5, -5, 10, 10)

class Gun extends Item
    draw: =>
        love.graphics.setColor(0, 0, 0)
        love.graphics.rectangle("fill", -5, -5, 10, 10)

export ^

class Item
-- used for things on the map that minions can pick up

class Energy extends Item
    draw: =>
        love.graphics.setColor(66, 145, 135)
        love.graphics.rectangle("fill", -5, -5, 10, 10)

class Material extends Item
    draw: =>
        love.graphics.setColor(243, 127, 183)
        love.graphics.rectangle("fill", -5, -5, 10, 10)

class Fudge extends Item -- not sure of the name? pre-food stuff
    draw: =>
        love.graphics.setColor(87, 119, 32)
        love.graphics.rectangle("fill", -5, -5, 10, 10)

class Food extends Item
    draw: =>
        love.graphics.setColor(159, 112, 19)
        love.graphics.rectangle("fill", -5, -5, 10, 10)

class Gun extends Item
    draw: =>
        love.graphics.setColor(0, 0, 0)
        love.graphics.rectangle("fill", -5, -5, 10, 10)


show help message on page of task

show help message on page of task

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

bgcolor = (task, i) ->
  form = task\quiz().state == model.ACTIVE
  bc = 'white'
  if task\ans_i(i) == task.selected
    bc = 'yellow'
  if not form and task\ans_i(i) == task.selected
    bc = 'red'
  if not form and task\ans_i(i) == 1
    bc = 'green'
  return bc

class Helpers
  ans_form: (task, i) =>
    url = @url_for("answer", {id: task.id})
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input type: "hidden", name: "task", value: task.id
      input type: "hidden", name: "ans", value: i
      input type: "submit", value: @_('Select')

  cancel_form: =>
    url = @url_for("cancel", {id: @quiz.id})
    raw '

'
    element 'hr'
    raw '

'
    font color: 'red', @_("Dangerous zone")
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input {type: "submit", value: @_('Cancel quiz'),
        style: 'background-color: pink'}

  help_for: (task) =>
    quiz_name = task\quiz().name
    func = quizs[quiz_name][task.name]
    r, a1, a2, a3, a4, help = func(@)
    print(help)
    return help

  task_table: (task) =>
    form = task\quiz().state == model.ACTIVE
    element "table", border: 1, ->
      element "tr", ->
        element "td", colspan: 4, ->
          b task.name
          p @help_for(task)
          pre task.text
      element "tr", ->
        for i = 1, 4
          element "td", {width: 100,
              bgcolor: bgcolor(task, i)}, ->
            pre task\ans(i)
            if form
              @ans_form task, i

model = require('model')

bgcolor = (task, i) ->
  form = task\quiz().state == model.ACTIVE
  bc = 'white'
  if task\ans_i(i) == task.selected
    bc = 'yellow'
  if not form and task\ans_i(i) == task.selected
    bc = 'red'
  if not form and task\ans_i(i) == 1
    bc = 'green'
  return bc

class Helpers
  ans_form: (task, i) =>
    url = @url_for("answer", {id: task.id})
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input type: "hidden", name: "task", value: task.id
      input type: "hidden", name: "ans", value: i
      input type: "submit", value: @_('Select')

  cancel_form: =>
    url = @url_for("cancel", {id: @quiz.id})
    raw '

'
    element 'hr'
    raw '

'
    font color: 'red', @_("Dangerous zone")
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input {type: "submit", value: @_('Cancel quiz'),
        style: 'background-color: pink'}

  task_table: (task) =>
    form = task\quiz().state == model.ACTIVE
    element "table", border: 1, ->
      element "tr", ->
        element "td", colspan: 4, ->
          b task.name
          raw ' '
          pre task.text
      element "tr", ->
        for i = 1, 4
          element "td", {width: 100,
              bgcolor: bgcolor(task, i)}, ->
            pre task\ans(i)
            if form
              @ans_form task, i


Fixed legs are drawing while dead

Fixed legs are drawing while dead

--
-- 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.
--

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')

class PonyRenderController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}
    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
    @__inherited(@)
    @NEXT_OBJ_ID = 0

    CompileTextures: => @GetTextureController()\CompileTextures() if @GetTextureController and @GetTextureController()
    new: (data) =>
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        @isValid = true
        @networkedData = data
        @ent = data.ent
        @hideModels = false
        @modelCached = data\GetModel()
        @IGNORE_DRAW = false
        @CompileTextures() if @ent
        @CreateLegs() if @ent == LocalPlayer()
        @socksModel = data\GetSocksModel()
        @socksModel\SetNoDraw(false) if IsValid(@socksModel)
        @CreateFlexController() if @ent
    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"
    GetEntity: => @ent
    GetData: => @networkedData
    GetModel: => @networkedData\GetModel()

    GetLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        @CreateLegs() if not IsValid()
        return @legsModel
    CreateLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        for ent in *ents.GetAll()
            if ent.isPonyLegsModel
                ent\Remove()
        @legsModel = ClientsideModel(@modelCached)
        with @legsModel
            .isPonyLegsModel = true
            .lastRedrawFix = 0
            \SetNoDraw(true)
            .__PPM2_PonyData = @GetData()

        @GetData()\GetWeightController()\UpdateWeight(@legsModel)

        @lastLegUpdate = CurTime()
        @legClipPlanePos = Vector(0, 0, 0)
        @legBGSetup = CurTime()
        @legUpdateFrame = 0
        @legClipDot = 0
        @duckOffsetHack = @@LEG_CLIP_OFFSET_STAND
        @legsClipPlane = @@LEG_CLIP_VECTOR
        return @legsModel

    @LEG_SHIFT_CONST = 24
    @LEG_SHIFT_CONST_VEHICLE = 14
    @LEG_Z_CONST = 0
    @LEG_Z_CONST_VEHICLE = 20
    @LEG_ANIM_SPEED_CONST = 1
    @LEG_CLIP_OFFSET_STAND = 28
    @LEG_CLIP_OFFSET_DUCK = 12
    @LEG_CLIP_OFFSET_VEHICLE = 11

    UpdateLegs: =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        return unless IsValid(@legsModel)
        return if @legUpdateFrame == FrameNumber()
        @legUpdateFrame = FrameNumber()
        ctime = CurTime()
        ply = @ent
        seq = ply\GetSequence()

        if seq ~= @legSeq
            @legSeq = seq
            @legsModel\ResetSequence(seq)

        if @legBGSetup < ctime
            @legBGSetup = ctime + 1
            for group in *ply\GetBodyGroups()
                @legsModel\SetBodygroup(group.id, ply\GetBodygroup(group.id))

        with @legsModel
            \FrameAdvance(ctime - @lastLegUpdate)
            \SetPlaybackRate(@@LEG_ANIM_SPEED_CONST * ply\GetPlaybackRate())
            @lastLegUpdate = ctime
            \SetPoseParameter('move_x',       (ply\GetPoseParameter('move_x')     * 2) - 1)
            \SetPoseParameter('move_y',       (ply\GetPoseParameter('move_y')     * 2) - 1)
            \SetPoseParameter('move_yaw',     (ply\GetPoseParameter('move_yaw')   * 360) - 180)
            \SetPoseParameter('body_yaw',     (ply\GetPoseParameter('body_yaw')   * 180) - 90)
            \SetPoseParameter('spine_yaw',    (ply\GetPoseParameter('spine_yaw')  * 180) - 90)

        if ply\InVehicle()
            local bonePos

            if bone = @legsModel\LookupBone('LrigNeck1')
                if boneData = @legsModel\GetBonePosition(bone)
                    bonePos = boneData

            veh = ply\GetVehicle()
            vehAng = veh\GetAngles()
            eyepos = EyePos()
            vehAng\RotateAroundAxis(vehAng\Up(), 90)

            clipAng = Angle(vehAng.p, vehAng.y, vehAng.r)
            clipAng\RotateAroundAxis(clipAng\Right(), -90)

            @legsClipPlane = clipAng\Forward()
            @legsModel\SetRenderAngles(vehAng)

            drawPos = Vector(@@LEG_SHIFT_CONST_VEHICLE, 0, @@LEG_Z_CONST_VEHICLE)
            drawPos\Rotate(vehAng)
            @legsModel\SetPos(eyepos - drawPos)
            @legsModel\SetRenderOrigin(eyepos - drawPos)

            if not bonePos
                legClipPlanePos = Vector(0, 0, @@LEG_CLIP_OFFSET_VEHICLE)
                legClipPlanePos\Rotate(vehAng)
                @legClipPlanePos = eyepos - legClipPlanePos
            else
                @legClipPlanePos = bonePos

        else
            @legsClipPlane = @@LEG_CLIP_VECTOR
            eangles = EyeAngles()
            yaw = eangles.y - ply\GetPoseParameter('head_yaw') * 180 + 90
            newAng = Angle(0, yaw, 0)
            rad = math.rad(yaw)
            sin, cos = math.sin(rad), math.cos(rad)
            pos = ply\GetPos()
            {:x, :y, :z} = pos
            newPos = Vector(x - cos * @@LEG_SHIFT_CONST, y - sin * @@LEG_SHIFT_CONST, z + @@LEG_Z_CONST)
            if ply\Crouching()
                @duckOffsetHack = @@LEG_CLIP_OFFSET_DUCK
            else
                @duckOffsetHack = Lerp(0.1, @duckOffsetHack, @@LEG_CLIP_OFFSET_STAND)

            @legsModel\SetRenderAngles(newAng)
            @legsModel\SetAngles(newAng)
            @legsModel\SetRenderOrigin(newPos)
            @legsModel\SetPos(newPos)

            if bone = @legsModel\LookupBone('LrigNeck1')
                if boneData = @legsModel\GetBonePosition(bone)
                    @legClipPlanePos = boneData
                else
                    @legClipPlanePos = Vector(x, y, z + @duckOffsetHack)
            else
                @legClipPlanePos = Vector(x, y, z + @duckOffsetHack)

        @legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

    @LEG_CLIP_VECTOR = Vector(0, 0, -1)
    @LEGS_MAX_DISTANCE = 60 ^ 2
    DrawLegs: (start3D = false) =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        @CreateLegs() unless IsValid(@legsModel)
        return unless IsValid(@legsModel)
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        if USE_RENDER_OVERRIDE\GetBool()
            @legsModel\SetNoDraw(false)
            rTime = RealTime()
            if @legsModel.lastRedrawFix < rTime
                @legsModel\DrawModel()
                @legsModel.lastRedrawFix = rTime + 5
            if not @legsModel.RenderOverride
                @legsModel.RenderOverride = -> @DrawLegsOverride()
                @legsModel\DrawModel()
            return
        else
            @legsModel\SetNoDraw(true)
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
        cam.Start3D() if start3D

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs(@legsModel)

        if LEGS_RENDER_TYPE\GetBool() and NABLE_FLASHLIGHT_PASS\GetBool()
            render.PushFlashlightMode(true)
            @GetTextureController()\PreDrawLegs(@legsModel)
            if sizes = @GetData()\GetSizeController()
                sizes\ModifyNeck(@legsModel)
                sizes\ModifyLegs(@legsModel)
                sizes\ModifyScale(@legsModel)
            @legsModel\DrawModel()
            @GetTextureController()\PostDrawLegs(@legsModel)
            render.PopFlashlightMode()

        render.PopCustomClipPlane()
        cam.End3D() if start3D
        render.EnableClipping(oldClip)

    DrawLegsOverride: =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        return if not @ent\Alive()
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs(@legsModel)

        render.PopCustomClipPlane()
        render.EnableClipping(oldClip)

    DrawLegsDepth: (start3D = false) =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        @CreateLegs() unless IsValid(@legsModel)
        return unless IsValid(@legsModel)
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
        cam.Start3D() if start3D

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs()

        render.PopCustomClipPlane()
        cam.End3D() if start3D
        render.EnableClipping(oldClip)

    IsValid: => IsValid(@ent) and @isValid
    Reset: =>
        @flexes\Reset() if @flexes and @flexes.Reset
        @GetTextureController()\Reset() if @GetTextureController and @GetTextureController() and @GetTextureController().Reset
        @GetTextureController()\ResetTextures() if @GetTextureController and @GetTextureController()
    Remove: =>
        @flexes\Remove() if @flexes
        @GetTextureController()\Remove() if @GetTextureController and @GetTextureController()
        @isValid = false

    PlayerDeath: =>
        return if not @isValid
        @HideModels(true)
        @GetTextureController()\ResetTextures() if @GetTextureController() and @ent\IsPony()

    PlayerRespawn: =>
        return if not @isValid
        @HideModels(false) if @ent\IsPony()
        @flexes\PlayerRespawn() if @flexes
        @GetTextureController()\ResetTextures() if @GetTextureController()

    DrawModels: =>
        @socksModel\DrawModel() if IsValid(@socksModel)
    HideModels: (status = true) =>
        return if @hideModels == status
        @socksModel\SetNoDraw(status) if IsValid(@socksModel)
        @hideModels = status

    PreDraw: (ent = @ent) =>
        return if not @isValid
        @GetTextureController()\PreDraw(ent)
        @GetTextureController()\UpdateSocks(@ent, @socksModel) if IsValid(@socksModel) and PPM2.ALTERNATIVE_RENDER\GetBool()
        @flexes\Think(ent) if @flexes
    PostDraw: (ent = @ent) =>
        return if not @isValid
        @GetTextureController()\PostDraw(ent)

    @ARMS_MATERIAL_INDEX = 0
    PreDrawArms: (ent) =>
        return if not @isValid
        if ent and not @armsWeightSetup
            @armsWeightSetup = true
            weight = @GetData()\GetWeight()
            vec = Vector(weight, weight, weight)
            for i = 1, 13
                ent\ManipulateBoneScale(i, vec)
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBody())
    PostDrawArms: (ent) =>
        return if not @isValid
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX)

    DataChanges: (state) =>
        return if not @isValid
        return if not @ent
        @GetTextureController()\DataChanges(state)
        @flexes\DataChanges(state) if @flexes
        switch state\GetKey()
            when 'Weight'
                @armsWeightSetup = false
                @GetData()\GetWeightController()\UpdateWeight(@legsModel) if IsValid(@legsModel)
            when 'SocksModel'
                @socksModel = @GetData()\GetSocksModel()
                @socksModel\SetNoDraw(@hideModels) if IsValid(@socksModel)
                @GetTextureController()\UpdateSocks(@ent, @socksModel) if @GetTextureController() and IsValid(@socksModel)
            when 'NoFlex'
                if state\GetValue()
                    @flexes\ResetSequences() if @flexes
                    @flexes = nil
                else
                    @CreateFlexController()
    GetTextureController: =>
        return @renderController if not @isValid
        if not @renderController
            cls = PPM2.GetTextureController(@modelCached)
            @renderController = cls(@)
        @renderController.ent = @ent
        return @renderController
    CreateFlexController: =>
        return @flexes if not @isValid
        return if @GetData()\GetNoFlex()
        if not @flexes
            cls = PPM2.GetFlexController(@modelCached)
            return if not cls
            @flexes = cls(@)
        @flexes.ent = @ent
        return @flexes
    GetFlexController: => @flexes

class NewPonyRenderController extends PonyRenderController
    @MODELS = {'models/ppm/player_default_base_new.mdl', 'models/ppm/player_default_base_new_nj.mdl'}

    new: (data) =>
        super(data)
        @upperManeModel = data\GetUpperManeModel()
        @lowerManeModel = data\GetLowerManeModel()
        @tailModel = data\GetTailModel()
        @upperManeModel\SetNoDraw(@hideModels) if IsValid(@upperManeModel)
        @lowerManeModel\SetNoDraw(@hideModels) if IsValid(@lowerManeModel)
        @tailModel\SetNoDraw(@hideModels) if IsValid(@tailModel)
    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

    DataChanges: (state) =>
        return if not @ent
        return if not @isValid
        switch state\GetKey()
            when 'UpperManeModel'
                @upperManeModel = @GetData()\GetUpperManeModel()
                @upperManeModel\SetNoDraw(@hideModels) if IsValid(@upperManeModel)
                @GetTextureController()\UpdateUpperMane(@ent, @upperManeModel) if @GetTextureController() and IsValid(@upperManeModel)
            when 'LowerManeModel'
                @lowerManeModel = @GetData()\GetLowerManeModel()
                @lowerManeModel\SetNoDraw(@hideModels) if IsValid(@lowerManeModel)
                @GetTextureController()\UpdateLowerMane(@ent, @lowerManeModel) if @GetTextureController() and IsValid(@lowerManeModel)
            when 'TailModel'
                @tailModel = @GetData()\GetTailModel()
                @tailModel\SetNoDraw(@hideModels) if IsValid(@tailModel)
                @GetTextureController()\UpdateTail(@ent, @tailModel) if @GetTextureController() and IsValid(@tailModel)
        super(state)

    DrawModels: =>
        @upperManeModel\DrawModel() if IsValid(@upperManeModel)
        @lowerManeModel\DrawModel() if IsValid(@lowerManeModel)
        @tailModel\DrawModel() if IsValid(@tailModel)
        super()
    HideModels: (status = true) =>
        return if @hideModels == status
        @upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
        @lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
        @tailModel\SetNoDraw(status) if IsValid(@tailModel)
        super(status)

    PreDraw: (ent = @ent) =>
        super(ent)
        if PPM2.ALTERNATIVE_RENDER\GetBool()
            textures = @GetTextureController()
            return if not textures
            textures\UpdateUpperMane(@ent, @upperManeModel) if IsValid(@upperManeModel)
            textures\UpdateLowerMane(@ent, @lowerManeModel) if IsValid(@lowerManeModel)
            textures\UpdateTail(@ent, @tailModel) if IsValid(@tailModel)

PPM2.PonyRenderController = PonyRenderController
PPM2.NewPonyRenderController = NewPonyRenderController
PPM2.GetPonyRenderController = (model = 'models/ppm/player_default_base.mdl') -> PonyRenderController.AVALIABLE_CONTROLLERS[model\lower()] or PonyRenderController
PPM2.GetPonyRendererController = PPM2.GetPonyRenderController
PPM2.GetRenderController = PPM2.GetPonyRenderController
PPM2.GetRendererController = PPM2.GetPonyRenderController

--
-- 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.
--

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')

class PonyRenderController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}
    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
    @__inherited(@)
    @NEXT_OBJ_ID = 0

    CompileTextures: => @GetTextureController()\CompileTextures() if @GetTextureController and @GetTextureController()
    new: (data) =>
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        @isValid = true
        @networkedData = data
        @ent = data.ent
        @hideModels = false
        @modelCached = data\GetModel()
        @IGNORE_DRAW = false
        @CompileTextures() if @ent
        @CreateLegs() if @ent == LocalPlayer()
        @socksModel = data\GetSocksModel()
        @socksModel\SetNoDraw(false) if IsValid(@socksModel)
        @CreateFlexController() if @ent
    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"
    GetEntity: => @ent
    GetData: => @networkedData
    GetModel: => @networkedData\GetModel()

    GetLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        @CreateLegs() if not IsValid()
        return @legsModel
    CreateLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        for ent in *ents.GetAll()
            if ent.isPonyLegsModel
                ent\Remove()
        @legsModel = ClientsideModel(@modelCached)
        with @legsModel
            .isPonyLegsModel = true
            .lastRedrawFix = 0
            \SetNoDraw(true)
            .__PPM2_PonyData = @GetData()

        @GetData()\GetWeightController()\UpdateWeight(@legsModel)

        @lastLegUpdate = CurTime()
        @legClipPlanePos = Vector(0, 0, 0)
        @legBGSetup = CurTime()
        @legUpdateFrame = 0
        @legClipDot = 0
        @duckOffsetHack = @@LEG_CLIP_OFFSET_STAND
        @legsClipPlane = @@LEG_CLIP_VECTOR
        return @legsModel

    @LEG_SHIFT_CONST = 24
    @LEG_SHIFT_CONST_VEHICLE = 14
    @LEG_Z_CONST = 0
    @LEG_Z_CONST_VEHICLE = 20
    @LEG_ANIM_SPEED_CONST = 1
    @LEG_CLIP_OFFSET_STAND = 28
    @LEG_CLIP_OFFSET_DUCK = 12
    @LEG_CLIP_OFFSET_VEHICLE = 11

    UpdateLegs: =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        return unless IsValid(@legsModel)
        return if @legUpdateFrame == FrameNumber()
        @legUpdateFrame = FrameNumber()
        ctime = CurTime()
        ply = @ent
        seq = ply\GetSequence()

        if seq ~= @legSeq
            @legSeq = seq
            @legsModel\ResetSequence(seq)

        if @legBGSetup < ctime
            @legBGSetup = ctime + 1
            for group in *ply\GetBodyGroups()
                @legsModel\SetBodygroup(group.id, ply\GetBodygroup(group.id))

        with @legsModel
            \FrameAdvance(ctime - @lastLegUpdate)
            \SetPlaybackRate(@@LEG_ANIM_SPEED_CONST * ply\GetPlaybackRate())
            @lastLegUpdate = ctime
            \SetPoseParameter('move_x',       (ply\GetPoseParameter('move_x')     * 2) - 1)
            \SetPoseParameter('move_y',       (ply\GetPoseParameter('move_y')     * 2) - 1)
            \SetPoseParameter('move_yaw',     (ply\GetPoseParameter('move_yaw')   * 360) - 180)
            \SetPoseParameter('body_yaw',     (ply\GetPoseParameter('body_yaw')   * 180) - 90)
            \SetPoseParameter('spine_yaw',    (ply\GetPoseParameter('spine_yaw')  * 180) - 90)

        if ply\InVehicle()
            local bonePos

            if bone = @legsModel\LookupBone('LrigNeck1')
                if boneData = @legsModel\GetBonePosition(bone)
                    bonePos = boneData

            veh = ply\GetVehicle()
            vehAng = veh\GetAngles()
            eyepos = EyePos()
            vehAng\RotateAroundAxis(vehAng\Up(), 90)

            clipAng = Angle(vehAng.p, vehAng.y, vehAng.r)
            clipAng\RotateAroundAxis(clipAng\Right(), -90)

            @legsClipPlane = clipAng\Forward()
            @legsModel\SetRenderAngles(vehAng)

            drawPos = Vector(@@LEG_SHIFT_CONST_VEHICLE, 0, @@LEG_Z_CONST_VEHICLE)
            drawPos\Rotate(vehAng)
            @legsModel\SetPos(eyepos - drawPos)
            @legsModel\SetRenderOrigin(eyepos - drawPos)

            if not bonePos
                legClipPlanePos = Vector(0, 0, @@LEG_CLIP_OFFSET_VEHICLE)
                legClipPlanePos\Rotate(vehAng)
                @legClipPlanePos = eyepos - legClipPlanePos
            else
                @legClipPlanePos = bonePos

        else
            @legsClipPlane = @@LEG_CLIP_VECTOR
            eangles = EyeAngles()
            yaw = eangles.y - ply\GetPoseParameter('head_yaw') * 180 + 90
            newAng = Angle(0, yaw, 0)
            rad = math.rad(yaw)
            sin, cos = math.sin(rad), math.cos(rad)
            pos = ply\GetPos()
            {:x, :y, :z} = pos
            newPos = Vector(x - cos * @@LEG_SHIFT_CONST, y - sin * @@LEG_SHIFT_CONST, z + @@LEG_Z_CONST)
            if ply\Crouching()
                @duckOffsetHack = @@LEG_CLIP_OFFSET_DUCK
            else
                @duckOffsetHack = Lerp(0.1, @duckOffsetHack, @@LEG_CLIP_OFFSET_STAND)

            @legsModel\SetRenderAngles(newAng)
            @legsModel\SetAngles(newAng)
            @legsModel\SetRenderOrigin(newPos)
            @legsModel\SetPos(newPos)

            if bone = @legsModel\LookupBone('LrigNeck1')
                if boneData = @legsModel\GetBonePosition(bone)
                    @legClipPlanePos = boneData
                else
                    @legClipPlanePos = Vector(x, y, z + @duckOffsetHack)
            else
                @legClipPlanePos = Vector(x, y, z + @duckOffsetHack)

        @legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

    @LEG_CLIP_VECTOR = Vector(0, 0, -1)
    @LEGS_MAX_DISTANCE = 60 ^ 2
    DrawLegs: (start3D = false) =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        @CreateLegs() unless IsValid(@legsModel)
        return unless IsValid(@legsModel)
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        if USE_RENDER_OVERRIDE\GetBool()
            @legsModel\SetNoDraw(false)
            rTime = RealTime()
            if @legsModel.lastRedrawFix < rTime
                @legsModel\DrawModel()
                @legsModel.lastRedrawFix = rTime + 5
            if not @legsModel.RenderOverride
                @legsModel.RenderOverride = -> @DrawLegsOverride()
                @legsModel\DrawModel()
            return
        else
            @legsModel\SetNoDraw(true)
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
        cam.Start3D() if start3D

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs(@legsModel)

        if LEGS_RENDER_TYPE\GetBool() and NABLE_FLASHLIGHT_PASS\GetBool()
            render.PushFlashlightMode(true)
            @GetTextureController()\PreDrawLegs(@legsModel)
            if sizes = @GetData()\GetSizeController()
                sizes\ModifyNeck(@legsModel)
                sizes\ModifyLegs(@legsModel)
                sizes\ModifyScale(@legsModel)
            @legsModel\DrawModel()
            @GetTextureController()\PostDrawLegs(@legsModel)
            render.PopFlashlightMode()

        render.PopCustomClipPlane()
        cam.End3D() if start3D
        render.EnableClipping(oldClip)

    DrawLegsOverride: =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs(@legsModel)

        render.PopCustomClipPlane()
        render.EnableClipping(oldClip)

    DrawLegsDepth: (start3D = false) =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        @CreateLegs() unless IsValid(@legsModel)
        return unless IsValid(@legsModel)
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
        cam.Start3D() if start3D

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs()

        render.PopCustomClipPlane()
        cam.End3D() if start3D
        render.EnableClipping(oldClip)

    IsValid: => IsValid(@ent) and @isValid
    Reset: =>
        @flexes\Reset() if @flexes and @flexes.Reset
        @GetTextureController()\Reset() if @GetTextureController and @GetTextureController() and @GetTextureController().Reset
        @GetTextureController()\ResetTextures() if @GetTextureController and @GetTextureController()
    Remove: =>
        @flexes\Remove() if @flexes
        @GetTextureController()\Remove() if @GetTextureController and @GetTextureController()
        @isValid = false

    PlayerDeath: =>
        return if not @isValid
        @HideModels(true)
        @GetTextureController()\ResetTextures() if @GetTextureController() and @ent\IsPony()

    PlayerRespawn: =>
        return if not @isValid
        @HideModels(false) if @ent\IsPony()
        @flexes\PlayerRespawn() if @flexes
        @GetTextureController()\ResetTextures() if @GetTextureController()

    DrawModels: =>
        @socksModel\DrawModel() if IsValid(@socksModel)
    HideModels: (status = true) =>
        return if @hideModels == status
        @socksModel\SetNoDraw(status) if IsValid(@socksModel)
        @hideModels = status

    PreDraw: (ent = @ent) =>
        return if not @isValid
        @GetTextureController()\PreDraw(ent)
        @GetTextureController()\UpdateSocks(@ent, @socksModel) if IsValid(@socksModel) and PPM2.ALTERNATIVE_RENDER\GetBool()
        @flexes\Think(ent) if @flexes
    PostDraw: (ent = @ent) =>
        return if not @isValid
        @GetTextureController()\PostDraw(ent)

    @ARMS_MATERIAL_INDEX = 0
    PreDrawArms: (ent) =>
        return if not @isValid
        if ent and not @armsWeightSetup
            @armsWeightSetup = true
            weight = @GetData()\GetWeight()
            vec = Vector(weight, weight, weight)
            for i = 1, 13
                ent\ManipulateBoneScale(i, vec)
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBody())
    PostDrawArms: (ent) =>
        return if not @isValid
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX)

    DataChanges: (state) =>
        return if not @isValid
        return if not @ent
        @GetTextureController()\DataChanges(state)
        @flexes\DataChanges(state) if @flexes
        switch state\GetKey()
            when 'Weight'
                @armsWeightSetup = false
                @GetData()\GetWeightController()\UpdateWeight(@legsModel) if IsValid(@legsModel)
            when 'SocksModel'
                @socksModel = @GetData()\GetSocksModel()
                @socksModel\SetNoDraw(@hideModels) if IsValid(@socksModel)
                @GetTextureController()\UpdateSocks(@ent, @socksModel) if @GetTextureController() and IsValid(@socksModel)
            when 'NoFlex'
                if state\GetValue()
                    @flexes\ResetSequences() if @flexes
                    @flexes = nil
                else
                    @CreateFlexController()
    GetTextureController: =>
        return @renderController if not @isValid
        if not @renderController
            cls = PPM2.GetTextureController(@modelCached)
            @renderController = cls(@)
        @renderController.ent = @ent
        return @renderController
    CreateFlexController: =>
        return @flexes if not @isValid
        return if @GetData()\GetNoFlex()
        if not @flexes
            cls = PPM2.GetFlexController(@modelCached)
            return if not cls
            @flexes = cls(@)
        @flexes.ent = @ent
        return @flexes
    GetFlexController: => @flexes

class NewPonyRenderController extends PonyRenderController
    @MODELS = {'models/ppm/player_default_base_new.mdl', 'models/ppm/player_default_base_new_nj.mdl'}

    new: (data) =>
        super(data)
        @upperManeModel = data\GetUpperManeModel()
        @lowerManeModel = data\GetLowerManeModel()
        @tailModel = data\GetTailModel()
        @upperManeModel\SetNoDraw(@hideModels) if IsValid(@upperManeModel)
        @lowerManeModel\SetNoDraw(@hideModels) if IsValid(@lowerManeModel)
        @tailModel\SetNoDraw(@hideModels) if IsValid(@tailModel)
    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

    DataChanges: (state) =>
        return if not @ent
        return if not @isValid
        switch state\GetKey()
            when 'UpperManeModel'
                @upperManeModel = @GetData()\GetUpperManeModel()
                @upperManeModel\SetNoDraw(@hideModels) if IsValid(@upperManeModel)
                @GetTextureController()\UpdateUpperMane(@ent, @upperManeModel) if @GetTextureController() and IsValid(@upperManeModel)
            when 'LowerManeModel'
                @lowerManeModel = @GetData()\GetLowerManeModel()
                @lowerManeModel\SetNoDraw(@hideModels) if IsValid(@lowerManeModel)
                @GetTextureController()\UpdateLowerMane(@ent, @lowerManeModel) if @GetTextureController() and IsValid(@lowerManeModel)
            when 'TailModel'
                @tailModel = @GetData()\GetTailModel()
                @tailModel\SetNoDraw(@hideModels) if IsValid(@tailModel)
                @GetTextureController()\UpdateTail(@ent, @tailModel) if @GetTextureController() and IsValid(@tailModel)
        super(state)

    DrawModels: =>
        @upperManeModel\DrawModel() if IsValid(@upperManeModel)
        @lowerManeModel\DrawModel() if IsValid(@lowerManeModel)
        @tailModel\DrawModel() if IsValid(@tailModel)
        super()
    HideModels: (status = true) =>
        return if @hideModels == status
        @upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
        @lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
        @tailModel\SetNoDraw(status) if IsValid(@tailModel)
        super(status)

    PreDraw: (ent = @ent) =>
        super(ent)
        if PPM2.ALTERNATIVE_RENDER\GetBool()
            textures = @GetTextureController()
            return if not textures
            textures\UpdateUpperMane(@ent, @upperManeModel) if IsValid(@upperManeModel)
            textures\UpdateLowerMane(@ent, @lowerManeModel) if IsValid(@lowerManeModel)
            textures\UpdateTail(@ent, @tailModel) if IsValid(@tailModel)

PPM2.PonyRenderController = PonyRenderController
PPM2.NewPonyRenderController = NewPonyRenderController
PPM2.GetPonyRenderController = (model = 'models/ppm/player_default_base.mdl') -> PonyRenderController.AVALIABLE_CONTROLLERS[model\lower()] or PonyRenderController
PPM2.GetPonyRendererController = PPM2.GetPonyRenderController
PPM2.GetRenderController = PPM2.GetPonyRenderController
PPM2.GetRendererController = PPM2.GetPonyRenderController


Set global limit for content body, not single file

filesize_limit will limit the content body, and we're going to introduce
one other option to limit separate files.

config = require 'lapis.config'
secrets = require 'secrets'

config {'development', 'development-perftest'}, ->
    mysql ->
        host '127.0.0.1'
        user 'csweek'
        password 'csweek'
        database 'csweek'

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'MG Nedelja informatike - prijave <prijave@csnedelja.mg.edu.rs>'

    port 8080
    num_workers 1
    code_cache 'off'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'debug'
    listen_address '127.0.0.1'
    secret 'this is not so secret!'
    content_prefix '../content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024

    email_cooldown 20

config {'production', 'production-perftest' }, ->
    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password

    port 8989
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'warn'
    listen_address '127.0.0.1'
    content_prefix 'content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024

    email_cooldown 30 * 60

config {'development-perftest', 'production-perftest'}, ->
    code_cache 'on'
    measure_performance -> true

config = require 'lapis.config'
secrets = require 'secrets'

config {'development', 'development-perftest'}, ->
    mysql ->
        host '127.0.0.1'
        user 'csweek'
        password 'csweek'
        database 'csweek'

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'MG Nedelja informatike - prijave <prijave@csnedelja.mg.edu.rs>'

    port 8080
    num_workers 1
    code_cache 'off'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'debug'
    listen_address '127.0.0.1'
    secret 'this is not so secret!'
    content_prefix '../content/'
    filesize_limit 15 * 1024 * 1024

    email_cooldown 20

config {'production', 'production-perftest' }, ->
    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password

    port 8989
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'warn'
    listen_address '127.0.0.1'
    content_prefix 'content/'
    filesize_limit 15 * 1024 * 1024

    email_cooldown 30 * 60

config {'development-perftest', 'production-perftest'}, ->
    code_cache 'on'
    measure_performance -> true


Collision group and damage

Collision group and damage

--
-- 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.
--

PPM2.PLAYER_VIEW_OFFSET = Vector(0, 0, 64 * .7)
PPM2.PLAYER_VIEW_OFFSET_DUCK = Vector(0, 0, 28 * 1.2)

PPM2.PLAYER_VIEW_OFFSET_ORIGINAL = Vector(0, 0, 64)
PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL = Vector(0, 0, 28)

hook.Add 'PlayerSpawn', 'PPM2.Hooks', =>
    if IsValid(@__ppm2_ragdoll)
        @__ppm2_ragdoll\Remove()
        @UnSpectate()
    timer.Simple 0, ->
        return unless @IsValid()
        if @GetPonyData()
            @GetPonyData()\PlayerRespawn()
            net.Start('PPM2.PlayerRespawn')
            net.WriteEntity(@)
            net.Broadcast()

        if @IsPony()
            @__ppm2_pony_view_offset = true
            @SetViewOffset(PPM2.PLAYER_VIEW_OFFSET)
            @SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK)

            return if @GetPonyData()
            timer.Simple 0.5, ->
                net.Start('PPM2.RequestPonyData')
                net.Send(@)
        else
            if @__ppm2_pony_view_offset
                @__ppm2_pony_view_offset = false
                @SetViewOffset(PPM2.PLAYER_VIEW_OFFSET_ORIGINAL)
                @SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL)

hook.Add 'PlayerInitialSpawn', 'PPM2.Hooks', =>
    timer.Simple 0, ->
        return unless @IsValid()
        return unless @IsPony()

        timer.Simple 10, ->
            net.Start('PPM2.RequestPonyData')
            net.Send(@)

do
    REQUIRE_CLIENTS = {}

    safeSendFunction = ->
        for ply, data in pairs REQUIRE_CLIENTS
            if not IsValid(ply)
                REQUIRE_CLIENTS[ply] = nil
                continue

            ent = table.remove(data, 1)
            if not ent
                REQUIRE_CLIENTS[ply] = nil
                continue

            data = ent\GetPonyData()
            continue if not data
            data\NetworkTo(ply)

    errorTrack = (err) ->
        PPM2.Message 'Networking Error: ', err
        PPM2.Message debug.traceback()

    timer.Create 'PPM2.Require', 0.25, 0, -> xpcall(safeSendFunction, errorTrack)
    net.Receive 'PPM2.Require', (len = 0, ply = NULL) ->
        return if not IsValid(ply)
        REQUIRE_CLIENTS[ply] = for ent in *ents.GetAll()
            continue if ent == ply
            data = ent\GetPonyData()
            continue if not data
            ent

net.Receive 'PPM2.EditorStatus', (len = 0, ply = NULL) ->
    return if not IsValid(ply)
    ply\SetNWBool('PPM2.InEditor', net.ReadBool())

ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')

createPlayerRagdoll = =>
    @__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
    @__ppm2_ragdoll = ents.Create('prop_ragdoll')
    rag = @GetRagdollEntity()
    rag\Remove() if IsValid(rag)
    with @__ppm2_ragdoll
        \SetModel(@GetModel())
        \SetPos(@GetPos())
        \SetAngles(@EyeAngles())
        \SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
        \Spawn()
        \Activate()
        hook.Run 'PlayerSpawnedRagdoll', @, @GetModel(), @__ppm2_ragdoll
        .__ppm2_ragdoll_parent = @
        \SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
        \SetNWBool('PPM2.IsDeathRagdoll', true)
        vel = @GetVelocity()
        \SetVelocity(vel)
        \SetAngles(@EyeAngles())
        @Spectate(OBS_MODE_CHASE)
        @SpectateEntity(@__ppm2_ragdoll)
        for boneID = 0, @__ppm2_ragdoll\GetBoneCount() - 1
            physobjID = @__ppm2_ragdoll\TranslateBoneToPhysBone(boneID)
            pos, ang = @GetBonePosition(boneID)
            physobj = @__ppm2_ragdoll\GetPhysicsObjectNum(physobjID)
            physobj\SetVelocity(vel)
            physobj\SetMass(300) -- lol
            physobj\SetPos(pos, true) if pos
            physobj\SetAngles(ang) if ang
        copy = @GetPonyData()\Clone(@__ppm2_ragdoll)
        timer.Simple 0.5, -> copy\Create() if IsValid(@__ppm2_ragdoll)

ALLOW_RAGDOLL_DAMAGE = CreateConVar('ppm2_sv_ragdoll_damage', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Should death ragdoll cause damage?')

hook.Add 'EntityTakeDamage', 'PPM2.DeathRagdoll', (dmg) =>
    attacker = dmg\GetAttacker()
    return if not IsValid(attacker)
    if attacker.__ppm2_ragdoll_parent
        dmg\SetAttacker(attacker.__ppm2_ragdoll_parent)
        if not ALLOW_RAGDOLL_DAMAGE\GetBool()
            dmg\SetDamage(0)
            dmg\SetMaxDamage(0)

hook.Add 'PostPlayerDeath', 'PPM2.Hooks', =>
    return if not @GetPonyData()
    @GetPonyData()\PlayerDeath()
    net.Start('PPM2.PlayerDeath')
    net.WriteEntity(@)
    net.Broadcast()
    if ENABLE_NEW_RAGDOLLS\GetBool() and @IsPony()
        createPlayerRagdoll(@)

hook.Add 'EntityRemoved', 'PPM2.PonyDataRemove', =>
    return if @IsPlayer()
    return if not @GetPonyData()
    with @GetPonyData()
        net.Start('PPM2.PonyDataRemove')
        net.WriteUInt(.netID, 16)
        net.Broadcast()
        \Remove()

hook.Add 'PlayerDisconnected', 'PPM2.NotifyClients', =>
    @__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
    data = @GetPonyData()
    return if not data
    net.Start('PPM2.NotifyDisconnect')
    net.WriteUInt(data.netID, 16)
    net.Broadcast()

BOTS_ARE_PONIES = CreateConVar('ppm2_bots', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Whatever spawn bots as ponies')

hook.Add 'PlayerSetModel', 'PPM2.Bots', =>
    return if not BOTS_ARE_PONIES\GetBool()
    return if not @IsBot()
    @SetModel('models/ppm/player_default_base_new.mdl')
    return true

hook.Add 'PlayerSpawn', 'PPM2.Bots', =>
    return if not BOTS_ARE_PONIES\GetBool()
    return if not @IsBot()
    timer.Simple 1, ->
        return if not IsValid(@)
        @SetViewOffset(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET))
        @SetViewOffsetDucked(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET_DUCK))
        if not @GetPonyData()
            data = PPM2.NetworkedPonyData(nil, @)
            PPM2.Randomize(data)
            data\Create()

--
-- 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.
--

PPM2.PLAYER_VIEW_OFFSET = Vector(0, 0, 64 * .7)
PPM2.PLAYER_VIEW_OFFSET_DUCK = Vector(0, 0, 28 * 1.2)

PPM2.PLAYER_VIEW_OFFSET_ORIGINAL = Vector(0, 0, 64)
PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL = Vector(0, 0, 28)

hook.Add 'PlayerSpawn', 'PPM2.Hooks', =>
    if IsValid(@__ppm2_ragdoll)
        @__ppm2_ragdoll\Remove()
        @UnSpectate()
    timer.Simple 0, ->
        return unless @IsValid()
        if @GetPonyData()
            @GetPonyData()\PlayerRespawn()
            net.Start('PPM2.PlayerRespawn')
            net.WriteEntity(@)
            net.Broadcast()

        if @IsPony()
            @__ppm2_pony_view_offset = true
            @SetViewOffset(PPM2.PLAYER_VIEW_OFFSET)
            @SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK)

            return if @GetPonyData()
            timer.Simple 0.5, ->
                net.Start('PPM2.RequestPonyData')
                net.Send(@)
        else
            if @__ppm2_pony_view_offset
                @__ppm2_pony_view_offset = false
                @SetViewOffset(PPM2.PLAYER_VIEW_OFFSET_ORIGINAL)
                @SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL)

hook.Add 'PlayerInitialSpawn', 'PPM2.Hooks', =>
    timer.Simple 0, ->
        return unless @IsValid()
        return unless @IsPony()

        timer.Simple 10, ->
            net.Start('PPM2.RequestPonyData')
            net.Send(@)

do
    REQUIRE_CLIENTS = {}

    safeSendFunction = ->
        for ply, data in pairs REQUIRE_CLIENTS
            if not IsValid(ply)
                REQUIRE_CLIENTS[ply] = nil
                continue

            ent = table.remove(data, 1)
            if not ent
                REQUIRE_CLIENTS[ply] = nil
                continue

            data = ent\GetPonyData()
            continue if not data
            data\NetworkTo(ply)

    errorTrack = (err) ->
        PPM2.Message 'Networking Error: ', err
        PPM2.Message debug.traceback()

    timer.Create 'PPM2.Require', 0.25, 0, -> xpcall(safeSendFunction, errorTrack)
    net.Receive 'PPM2.Require', (len = 0, ply = NULL) ->
        return if not IsValid(ply)
        REQUIRE_CLIENTS[ply] = for ent in *ents.GetAll()
            continue if ent == ply
            data = ent\GetPonyData()
            continue if not data
            ent

net.Receive 'PPM2.EditorStatus', (len = 0, ply = NULL) ->
    return if not IsValid(ply)
    ply\SetNWBool('PPM2.InEditor', net.ReadBool())

ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')

createPlayerRagdoll = =>
    @__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
    @__ppm2_ragdoll = ents.Create('prop_ragdoll')
    rag = @GetRagdollEntity()
    rag\Remove() if IsValid(rag)
    with @__ppm2_ragdoll
        \SetModel(@GetModel())
        \SetPos(@GetPos())
        \SetAngles(@EyeAngles())
        \SetCollisionGroup(COLLISION_GROUP_PASSABLE_DOOR)
        \Spawn()
        \Activate()
        hook.Run 'PlayerSpawnedRagdoll', @, @GetModel(), @__ppm2_ragdoll
        .__ppm2_ragdoll_parent = @
        \SetCollisionGroup(COLLISION_GROUP_PASSABLE_DOOR)
        \SetNWBool('PPM2.IsDeathRagdoll', true)
        vel = @GetVelocity()
        \SetVelocity(vel)
        \SetAngles(@EyeAngles())
        @Spectate(OBS_MODE_CHASE)
        @SpectateEntity(@__ppm2_ragdoll)
        for boneID = 0, @__ppm2_ragdoll\GetBoneCount() - 1
            physobjID = @__ppm2_ragdoll\TranslateBoneToPhysBone(boneID)
            pos, ang = @GetBonePosition(boneID)
            physobj = @__ppm2_ragdoll\GetPhysicsObjectNum(physobjID)
            physobj\SetVelocity(vel)
            physobj\SetMass(300) -- lol
            physobj\SetPos(pos, true) if pos
            physobj\SetAngles(ang) if ang
        copy = @GetPonyData()\Clone(@__ppm2_ragdoll)
        timer.Simple 0.5, -> copy\Create() if IsValid(@__ppm2_ragdoll)

hook.Add 'EntityTakeDamage', 'PPM2.DeathRagdoll', (dmg) =>
    attacker = dmg\GetAttacker()
    return if not IsValid(attacker)
    if attacker.__ppm2_ragdoll_parent
        dmg\SetAttacker(attacker.__ppm2_ragdoll_parent)

hook.Add 'PostPlayerDeath', 'PPM2.Hooks', =>
    return if not @GetPonyData()
    @GetPonyData()\PlayerDeath()
    net.Start('PPM2.PlayerDeath')
    net.WriteEntity(@)
    net.Broadcast()
    if ENABLE_NEW_RAGDOLLS\GetBool() and @IsPony()
        createPlayerRagdoll(@)

hook.Add 'EntityRemoved', 'PPM2.PonyDataRemove', =>
    return if @IsPlayer()
    return if not @GetPonyData()
    with @GetPonyData()
        net.Start('PPM2.PonyDataRemove')
        net.WriteUInt(.netID, 16)
        net.Broadcast()
        \Remove()

hook.Add 'PlayerDisconnected', 'PPM2.NotifyClients', =>
    @__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
    data = @GetPonyData()
    return if not data
    net.Start('PPM2.NotifyDisconnect')
    net.WriteUInt(data.netID, 16)
    net.Broadcast()

BOTS_ARE_PONIES = CreateConVar('ppm2_bots', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Whatever spawn bots as ponies')

hook.Add 'PlayerSetModel', 'PPM2.Bots', =>
    return if not BOTS_ARE_PONIES\GetBool()
    return if not @IsBot()
    @SetModel('models/ppm/player_default_base_new.mdl')
    return true

hook.Add 'PlayerSpawn', 'PPM2.Bots', =>
    return if not BOTS_ARE_PONIES\GetBool()
    return if not @IsBot()
    timer.Simple 1, ->
        return if not IsValid(@)
        @SetViewOffset(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET))
        @SetViewOffsetDucked(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET_DUCK))
        if not @GetPonyData()
            data = PPM2.NetworkedPonyData(nil, @)
            PPM2.Randomize(data)
            data\Create()