Add what the corresponding HaxePunk version used is

import Point from require "LunoPunk.geometry.Point"
import Rectangle from require "LunoPunk.geometry.Rectangle"
import Matrix from require "LunoPunk.geometry.Matrix"
import Tweener from require "LunoPunk.Tweener"

local LP

-- for Lua 5.3 compatibility
table.unpack = unpack if table.unpack == nil

class lp
	-- Private
	BASELAYER = 10
	DEG = -180 / math.pi
	INT_MAX_VALUE = nil
	NUMBER_MAX_VALUE = nil
	RAD = math.pi / 180
	VERSION = "0.0.1"
	HAXEPUNK_VERSION = "2.5.1"
	__scene = nil

	_gameTime: nil
	_renderTime: nil
	_systemTime: nil
	_updateTime: nil
	assignedFrameRate: nil
	blackColor: 0x00000000
	bounds: nil
	buffer: nil
	camera: Point!
	console: nil
	defaultFont: "default"
	elapsed: 0
	engine: nil
	entity: nil
	fixed: false
	focused: nil
	frameRate: 0
	log: nil
	matrix: Matrix!
	orientations: nil
	pan: nil
	point: Point!
	point2: Point!
	random: -> math.random!
	randomSeed: nil
	rate: 0
	rect: Rectangle!
	renderMode = (value) ->
		return __renderMode if value == nil or __renderMode == value
		__renderMode = value

		-- recreate screen for buffer rendering
		if LP.screen == nil
			LP.screen = Screen!
		else
			LP.screen.init!

		value

	scene: nil
	screen: nil
	sprite: nil
	tweener: Tweener!
	volume: 1
	watch: nil
	world: nil
	zero: Point!

	-- Window/resolution related fields
	fullscreen: false
	halfHeight: -> LP.height/2
	halfWidth: -> LP.width/2
	height: 0
	width: 0
	windowHeight: 0
	windowWidth: 0

	alarm: (delay, complete, typeweenType, tweener) ->
	anchorTo: (object, anchor, distance) ->
	angle: (x1, y1, x2, y2) ->
	angleDifference: (angle1, angle2) ->
	angleXY: (object, angle, length, x, y) ->

	-- Approaches the value towards the target, by the specified amount, without overshooting the target.
	-- @param	value	The starting value.
	-- @param	target	The target that you want value to approach.
	-- @param	amount	How much you want the value to approach target by.
	-- @return	The new value.
	approach: (value, target, amount) -> if value < target - amount
		value + amount
	else if value > target + amount
		value - amount
	else
		target

	choose: (objs) ->

	-- Clamps the value within the minimum and maximum values.
	-- @param	value		The Float to evaluate.
	-- @param	min			The minimum range.
	-- @param	max			The maximum range.
	-- @return	The clamped value.
	clamp: (value, min, max) -> if value < min
		min
	else if value > max
		max
	else
		value

	clampInRect: (object, x, y, width, height, padding) ->
	clear: (t) -> while #t > 0 do table.remove t
	consoleEnabled: ->
	createBitmap: (width, height, transparent, color) ->

	-- Find the distance between two points.
	-- @param	x1		The first x-position.
	-- @param	y1		The first y-position.
	-- @param	x2		The second x-position.
	-- @param	y2		The second y-position.
	-- @return	The distance.
	distance: (x1, y1, x2, y2) -> math.sqrt (x2 - x1)^2 + (y2 - y1)^2

	istanceRectPoint: (px, py, rx, ry, rw, rh) ->

	-- Find the distance between two rectangles. Will return 0 if the rectangles overlap.
	-- @param	x1		The x-position of the first rect.
	-- @param	y1		The y-position of the first rect.
	-- @param	w1		The width of the first rect.
	-- @param	h1		The height of the first rect.
	-- @param	x2		The x-position of the second rect.
	-- @param	y2		The y-position of the second rect.
	-- @param	w2		The width of the second rect.
	-- @param	h2		The height of the second rect.
	-- @return	The distance.
	distanceRects: (x1, y1, w1, h1, x2, y2, w2, h2) ->
		if x1 < x2 + w2 and x2 < x1 + w1
			return 0 if y1 < y2 + h2 and y2 < y1 + h1
			return y1 - (y2 + h2) if y1 > y2
			return y2 - (y1 + h1)

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

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

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

	-- Find the distance between a point and a rectangle. Returns 0 if the point is within the rectangle.
	-- @param	px		The x-position of the point.
	-- @param	py		The y-position of the point.
	-- @param	rx		The x-position of the rect.
	-- @param	ry		The y-position of the rect.
	-- @param	rw		The width of the rect.
	-- @param	rh		The height of the rect.
	-- @return	The distance.
	distanceRectPoint: (px, py, rx, ry, rw, rh) ->
		if px >= rx and px <= rx + rw
			return 0 if py >= ry and py <= ry + rh
			return py - (ry + rh) if py > ry
			return ry - py

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

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

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

	-- Find the squared distance between two points.
	-- @param	x1		The first x-position.
	-- @param	y1		The first y-position.
	-- @param	x2		The second x-position.
	-- @param	y2		The second y-position.
	-- @return	The squared distance.
	distanceSquared: (x1, y1, xprint2, y2) -> (x2 - x1)^2 + (y2 - y1)^2

	frames: (from_frame, to_frame, skip) ->
	getImage: (source) -> love.graphics.newImage source
	getColorHSV: (h, s, v) ->
	getColorHue: (color) ->

	getColorSaturation: (color) ->
	getColorValue: (color) ->

	-- Take a 12 bit RGB integer and turn it into a table
	convertColor: (color) -> { lp.getRed(color), lp.getGreen(color), lp.getBlue(color) }

	-- Make a 12 bit RGB integer
	getColorRGB: (r, g, b) -> r * 0x10000 + g * 0x100 + b

	-- Get the red value from an RGB 12 bit integer
	getRed: (color) -> math.floor color / 0x10000

	-- Get the green value from an RGB 12 bit integer
	getGreen: (color) -> math.floor color / 0x100 % 0x100

	-- Get the blue value from an RGB 12 bit integer
	getBlue: (color) -> math.floor color % 0x100

	-- Finds the index of the specified item within a table
	-- Assumes the table is an array indexed table
	indexOf: (tbl, item) -> for i, v in ipairs tbl do return i if item == v

	-- read-only
	DEG: -> DEG

	-- read-only
	NUMBER_MAX_VALUE: -> NUMBER_MAX_VALUE

	-- read-only
	RAD: -> RAD

	-- read-only
	VERSION: -> VERSION

	-- read-only
	BASELAYER: -> BASELAYER

	-- Returns true if the scene doesn't match the internal scene or if the
	-- internal scene is null
	sceneIsNull: => @scene != @__scene or @__scene == nil

	-- Linear interpolation between two values.
	-- @param	a		First value.
	-- @param	b		Second value.
	-- @param	t		Interpolation factor.
	-- @return	When t=0, returns a. When t=1, returns b. When t=0.5, will return halfway between a and b. Etc.
	lerp: (a, b, t = 1) -> a + (b - a) * t

	-- Linear interpolation between two colors.
	-- @param	fromColor		First color.
	-- @param	toColor			Second color.
	-- @param	t				Interpolation value. Clamped to the range [0, 1].
	-- return	RGB component-interpolated color value.
	colorLerp: (fromColor, toColor, t = 1) ->
		if t <= 0
			fromColor
		else if t >= 1
			toColor
		else
			f = LP.convertColor fromColor
			d = LP.convertColor toColor
			d[1] -= f[1]
			d[2] -= f[2]
			d[3] -= f[3]
			f[1] = d[1] * t
			f[2] = d[2] * t
			f[3] = d[3] * t
			LP.getColorRGB table.unpack f

	next: (current, options, loop) ->
	prev: (current, options, loop) ->

	rand: (amount) -> math.random(amount)
	randomizeSeed: -> math.randomseed os.time()

	resetCamera: -> camera.x, camera.y = 0, 0

	resize: (width, height) ->
		-- resize scene to scale
		width /= LP.screen\fullScaleX!
		height = height / LP.screen\fullScaleY!
		LP.width, LP.height = width, height
		LP.bounds.width, LP.bounds.height = width, height
		LP.screen\resize!

	rotateAround: (object, anchor, angle, relative) ->
	round: (num, precision) ->
	scale: (value, min, max, min2, max2) ->
	scaleClamp: (value, min, max, min2, max2) ->

	-- Sets the camera's x and y
	setCamera: (x, y) -> LP.camera.x, LP.camera.y = x, y
	shuffle: (a) ->

	-- Finds the sign of the provided value.
	-- @param	value		The Float to evaluate.
	-- @return	1 if value > 0, -1 if value < 0, and 0 when value == 0.
	sign: (value) -> if value < 0
		-1
	elseif value == 0
		0
	else
		1

	-- Steps the object towards a point.
	-- @param	object		Object to move (must have an x and y property).
	-- @param	x			X position to step towards.
	-- @param	y			Y position to step towards.
	-- @param	distance	The distance to step (will not overshoot target).
	stepTowards: (object, x, y, distance) ->
		assert object\x != nil or object\y != nil, "Object must have x and y components"
		object\x LP.approach object\x!, x, distance
		object\y LP.approach object\y!, y, distance

	-- Swaps the current item between a and b. Useful for quick state/string/value swapping.
	-- @param	current		The currently selected item.
	-- @param	a			Item a.
	-- @param	b			Item b.
	-- @return	Returns a if current is b, and b if current is a.
	swap: (current, a, b) -> if current == a then b else a

	swapScene: => @__scene = @scene
	timeFlag: ->
	tween: (object, values, duration, options) ->

	-- Used to determine the Löve version is exactly a version or if it's within the
	-- acceptable range of [version..range)
	-- @param	version		The string containing the major.minor Löve version
	-- @param	range		The string containing the major.minor max Löve version
	-- @return	true if version matches the Löve version
	__love: (version, range) ->
		if range
			release = tonumber(LunoPunk.love_version.release)
			release >= tonumber(version) and release < tonumber(range)
		else
			version == LunoPunk.love_version.release

LP = lp!

-- Setup some initial things since the user didn't
if not LunoPunk
	import extract_love_version, set_love_title from require "LunoPunk.Config"
	extract_love_version love._version
	set_love_title!

-- Don't allow unknown Löve versions
if not (LP.__love "0.8", "12.0")
	assert false, "Unsupported Löve version" .. LunoPunk.love_version.release

{ :LP }

import Point from require "LunoPunk.geometry.Point"
import Rectangle from require "LunoPunk.geometry.Rectangle"
import Matrix from require "LunoPunk.geometry.Matrix"
import Tweener from require "LunoPunk.Tweener"

local LP

-- for Lua 5.3 compatibility
table.unpack = unpack if table.unpack == nil

class lp
	-- Private
	BASELAYER = 10
	DEG = -180 / math.pi
	INT_MAX_VALUE = nil
	NUMBER_MAX_VALUE = nil
	RAD = math.pi / 180
	VERSION = "0.0.1"
	__scene = nil

	_gameTime: nil
	_renderTime: nil
	_systemTime: nil
	_updateTime: nil
	assignedFrameRate: nil
	blackColor: 0x00000000
	bounds: nil
	buffer: nil
	camera: Point!
	console: nil
	defaultFont: "default"
	elapsed: 0
	engine: nil
	entity: nil
	fixed: false
	focused: nil
	frameRate: 0
	log: nil
	matrix: Matrix!
	orientations: nil
	pan: nil
	point: Point!
	point2: Point!
	random: -> math.random!
	randomSeed: nil
	rate: 0
	rect: Rectangle!
	renderMode = (value) ->
		return __renderMode if value == nil or __renderMode == value
		__renderMode = value

		-- recreate screen for buffer rendering
		if LP.screen == nil
			LP.screen = Screen!
		else
			LP.screen.init!

		value

	scene: nil
	screen: nil
	sprite: nil
	tweener: Tweener!
	volume: 1
	watch: nil
	world: nil
	zero: Point!

	-- Window/resolution related fields
	fullscreen: false
	halfHeight: -> LP.height/2
	halfWidth: -> LP.width/2
	height: 0
	width: 0
	windowHeight: 0
	windowWidth: 0

	alarm: (delay, complete, typeweenType, tweener) ->
	anchorTo: (object, anchor, distance) ->
	angle: (x1, y1, x2, y2) ->
	angleDifference: (angle1, angle2) ->
	angleXY: (object, angle, length, x, y) ->

	-- Approaches the value towards the target, by the specified amount, without overshooting the target.
	-- @param	value	The starting value.
	-- @param	target	The target that you want value to approach.
	-- @param	amount	How much you want the value to approach target by.
	-- @return	The new value.
	approach: (value, target, amount) -> if value < target - amount
		value + amount
	else if value > target + amount
		value - amount
	else
		target

	choose: (objs) ->

	-- Clamps the value within the minimum and maximum values.
	-- @param	value		The Float to evaluate.
	-- @param	min			The minimum range.
	-- @param	max			The maximum range.
	-- @return	The clamped value.
	clamp: (value, min, max) -> if value < min
		min
	else if value > max
		max
	else
		value

	clampInRect: (object, x, y, width, height, padding) ->
	clear: (t) -> while #t > 0 do table.remove t
	consoleEnabled: ->
	createBitmap: (width, height, transparent, color) ->

	-- Find the distance between two points.
	-- @param	x1		The first x-position.
	-- @param	y1		The first y-position.
	-- @param	x2		The second x-position.
	-- @param	y2		The second y-position.
	-- @return	The distance.
	distance: (x1, y1, x2, y2) -> math.sqrt (x2 - x1)^2 + (y2 - y1)^2

	istanceRectPoint: (px, py, rx, ry, rw, rh) ->

	-- Find the distance between two rectangles. Will return 0 if the rectangles overlap.
	-- @param	x1		The x-position of the first rect.
	-- @param	y1		The y-position of the first rect.
	-- @param	w1		The width of the first rect.
	-- @param	h1		The height of the first rect.
	-- @param	x2		The x-position of the second rect.
	-- @param	y2		The y-position of the second rect.
	-- @param	w2		The width of the second rect.
	-- @param	h2		The height of the second rect.
	-- @return	The distance.
	distanceRects: (x1, y1, w1, h1, x2, y2, w2, h2) ->
		if x1 < x2 + w2 and x2 < x1 + w1
			return 0 if y1 < y2 + h2 and y2 < y1 + h1
			return y1 - (y2 + h2) if y1 > y2
			return y2 - (y1 + h1)

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

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

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

	-- Find the distance between a point and a rectangle. Returns 0 if the point is within the rectangle.
	-- @param	px		The x-position of the point.
	-- @param	py		The y-position of the point.
	-- @param	rx		The x-position of the rect.
	-- @param	ry		The y-position of the rect.
	-- @param	rw		The width of the rect.
	-- @param	rh		The height of the rect.
	-- @return	The distance.
	distanceRectPoint: (px, py, rx, ry, rw, rh) ->
		if px >= rx and px <= rx + rw
			return 0 if py >= ry and py <= ry + rh
			return py - (ry + rh) if py > ry
			return ry - py

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

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

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

	-- Find the squared distance between two points.
	-- @param	x1		The first x-position.
	-- @param	y1		The first y-position.
	-- @param	x2		The second x-position.
	-- @param	y2		The second y-position.
	-- @return	The squared distance.
	distanceSquared: (x1, y1, xprint2, y2) -> (x2 - x1)^2 + (y2 - y1)^2

	frames: (from_frame, to_frame, skip) ->
	getImage: (source) -> love.graphics.newImage source
	getColorHSV: (h, s, v) ->
	getColorHue: (color) ->

	getColorSaturation: (color) ->
	getColorValue: (color) ->

	-- Take a 12 bit RGB integer and turn it into a table
	convertColor: (color) -> { lp.getRed(color), lp.getGreen(color), lp.getBlue(color) }

	-- Make a 12 bit RGB integer
	getColorRGB: (r, g, b) -> r * 0x10000 + g * 0x100 + b

	-- Get the red value from an RGB 12 bit integer
	getRed: (color) -> math.floor color / 0x10000

	-- Get the green value from an RGB 12 bit integer
	getGreen: (color) -> math.floor color / 0x100 % 0x100

	-- Get the blue value from an RGB 12 bit integer
	getBlue: (color) -> math.floor color % 0x100

	-- Finds the index of the specified item within a table
	-- Assumes the table is an array indexed table
	indexOf: (tbl, item) -> for i, v in ipairs tbl do return i if item == v

	-- read-only
	DEG: -> DEG

	-- read-only
	NUMBER_MAX_VALUE: -> NUMBER_MAX_VALUE

	-- read-only
	RAD: -> RAD

	-- read-only
	VERSION: -> VERSION

	-- read-only
	BASELAYER: -> BASELAYER

	-- Returns true if the scene doesn't match the internal scene or if the
	-- internal scene is null
	sceneIsNull: => @scene != @__scene or @__scene == nil

	-- Linear interpolation between two values.
	-- @param	a		First value.
	-- @param	b		Second value.
	-- @param	t		Interpolation factor.
	-- @return	When t=0, returns a. When t=1, returns b. When t=0.5, will return halfway between a and b. Etc.
	lerp: (a, b, t = 1) -> a + (b - a) * t

	-- Linear interpolation between two colors.
	-- @param	fromColor		First color.
	-- @param	toColor			Second color.
	-- @param	t				Interpolation value. Clamped to the range [0, 1].
	-- return	RGB component-interpolated color value.
	colorLerp: (fromColor, toColor, t = 1) ->
		if t <= 0
			fromColor
		else if t >= 1
			toColor
		else
			f = LP.convertColor fromColor
			d = LP.convertColor toColor
			d[1] -= f[1]
			d[2] -= f[2]
			d[3] -= f[3]
			f[1] = d[1] * t
			f[2] = d[2] * t
			f[3] = d[3] * t
			LP.getColorRGB table.unpack f

	next: (current, options, loop) ->
	prev: (current, options, loop) ->

	rand: (amount) -> math.random(amount)
	randomizeSeed: -> math.randomseed os.time()

	resetCamera: -> camera.x, camera.y = 0, 0

	resize: (width, height) ->
		-- resize scene to scale
		width /= LP.screen\fullScaleX!
		height = height / LP.screen\fullScaleY!
		LP.width, LP.height = width, height
		LP.bounds.width, LP.bounds.height = width, height
		LP.screen\resize!

	rotateAround: (object, anchor, angle, relative) ->
	round: (num, precision) ->
	scale: (value, min, max, min2, max2) ->
	scaleClamp: (value, min, max, min2, max2) ->

	-- Sets the camera's x and y
	setCamera: (x, y) -> LP.camera.x, LP.camera.y = x, y
	shuffle: (a) ->

	-- Finds the sign of the provided value.
	-- @param	value		The Float to evaluate.
	-- @return	1 if value > 0, -1 if value < 0, and 0 when value == 0.
	sign: (value) -> if value < 0
		-1
	elseif value == 0
		0
	else
		1

	-- Steps the object towards a point.
	-- @param	object		Object to move (must have an x and y property).
	-- @param	x			X position to step towards.
	-- @param	y			Y position to step towards.
	-- @param	distance	The distance to step (will not overshoot target).
	stepTowards: (object, x, y, distance) ->
		assert object\x != nil or object\y != nil, "Object must have x and y components"
		object\x LP.approach object\x!, x, distance
		object\y LP.approach object\y!, y, distance

	-- Swaps the current item between a and b. Useful for quick state/string/value swapping.
	-- @param	current		The currently selected item.
	-- @param	a			Item a.
	-- @param	b			Item b.
	-- @return	Returns a if current is b, and b if current is a.
	swap: (current, a, b) -> if current == a then b else a

	swapScene: => @__scene = @scene
	timeFlag: ->
	tween: (object, values, duration, options) ->

	-- Used to determine the Löve version is exactly a version or if it's within the
	-- acceptable range of [version..range)
	-- @param	version		The string containing the major.minor Löve version
	-- @param	range		The string containing the major.minor max Löve version
	-- @return	true if version matches the Löve version
	__love: (version, range) ->
		if range
			release = tonumber(LunoPunk.love_version.release)
			release >= tonumber(version) and release < tonumber(range)
		else
			version == LunoPunk.love_version.release

LP = lp!

-- Setup some initial things since the user didn't
if not LunoPunk
	import extract_love_version, set_love_title from require "LunoPunk.Config"
	extract_love_version love._version
	set_love_title!

-- Don't allow unknown Löve versions
if not (LP.__love "0.8", "12.0")
	assert false, "Unsupported Löve version" .. LunoPunk.love_version.release

{ :LP }


fixed yubikey

fixed yubikey

http = require "lapis.nginx.http"
import render_html from require "lapis.html"
import from_json,to_json,slugify, escape from require "lapis.util"
import encode_base64,hmac_sha1 from require "lapis.util.encoding"

UsersModel=require "lazuli.modules.user_management.models.users"
YubiCloudModel=require "lazuli.modules.user_management.models.yubicloud"

frandom = nil
ranval=->
  frandom or= assert io.open "/dev/urandom", "rb"
  s = frandom\read 4
  assert s\len! == 4
  v = 0
  for i = 1, 4
    v = 256 * v + s\byte i
  return v

class YubiCloud
  new: (@app,@modulename,@config) =>
    @apiurl = @config.apiurl or "http://api.yubico.com/wsapi/2.0/verify?id=1&otp=$OTP$&nonce=$NONCE$&timestamp=1"
    @required = @config.required or false
    @takeControlOfForm = @takeControlOfForm or true

  fillUrl: (otp,nonce) =>
    @apiurl\gsub("%$OTP%$",escape otp)\gsub("%$NONCE%$",escape nonce)

  tryLogin: (params) =>
    if params.yubikey and type(params.yubikey)=="string" and params.yubikey\len! >0
      params.yubikey=params.yubikey\lower!
      nonce=@mkNonce!
      query=@fillUrl params.yubikey, nonce
      res=http.simple query
      if res and res\find "status=OK", 1, true and res\find "nonce="..nonce, 1, true
        idstr=params.yubikey\sub(1,-33)
        entry=YubiCloudModel\find idstr: idstr
        if entry
          user=entry\get_user!
          if user
            return user, res
          return nil, "OTP -> user resulution failed."
        return nil, "OTP unknown. idstr: "..idstr
      return nil, "invalid OTP, details: "..query.." => "..res
    if @required
      return nil, "YubiKey OTP required."
    return false,"no OTP, skipping."

  mkNonce: =>
    now=os.time!
    hash=slugify now..encode_base64 hmac_sha1 now..ranval!,ranval!..now
    hash=hash\sub 1,20
    hash\lower!

  getLoginOkHtml:    => ""
  getLoginErrorHtml: => ""

  getLoginHtml: =>
    render_html ->
      div class: "pure-control-group yubigroup", ->
        label for: "yubikey", "YubiCloud:"
        input id: "yubikey", type: "yubikey", name: "yubikey", autocomplete: "off", placeholder: "YubiKey OTP", autofocus: @takeControlOfForm and "autofocus" or nil
        if @takeControlOfForm
          input type: "button", value: "X", id: "closeYubiKeyBtn"
      if @takeControlOfForm
        script ->
          raw [[
            document.getElementsByClassName("usernamegroup")[0].hidden=true
            document.getElementsByClassName("passwordgroup")[0].hidden=true
            document.getElementById("closeYubiKeyBtn").onclick=function(){
              document.getElementsByClassName("usernamegroup")[0].hidden=false
              document.getElementsByClassName("passwordgroup")[0].hidden=false
              document.getElementsByClassName("yubigroup")[0].hidden=true
              document.getElementById("username").focus()
            }
          ]]

http = require "lapis.nginx.http"
import render_html from require "lapis.html"
import from_json,to_json,slugify, escape from require "lapis.util"
import encode_base64,hmac_sha1 from require "lapis.util.encoding"

UsersModel=require "lazuli.modules.user_management.models.users"
YubiCloudModel=require "lazuli.modules.user_management.models.yubicloud"

frandom = nil
ranval=->
  frandom or= assert io.open "/dev/urandom", "rb"
  s = frandom\read 4
  assert s\len! == 4
  v = 0
  for i = 1, 4
    v = 256 * v + s\byte i
  return v

class YubiCloud
  new: (@app,@modulename,@config) =>
    @apiurl = @config.apiurl or "http://api.yubico.com/wsapi/2.0/verify?id=1&otp=$OTP$&nonce=$NONCE$&timestamp=1"
    @required = @config.required or false
    @takeControlOfForm = @takeControlOfForm or true

  fillUrl: (otp,nonce) =>
    @apiurl\gsub("%$OTP%$",escape otp)\gsub("%$NONCE%$",escape nonce)

  tryLogin: (params) =>
    if params.yubikey
      params.yubikey=params.yubikey\lower!
      nonce=@mkNonce!
      query=@fillUrl params.yubikey, nonce
      res=http.simple query
      if res and res\find "status=OK", 1, true and res\find "nonce="..nonce, 1, true
        idstr=params.yubikey\sub(1,-33)
        entry=YubiCloudModel\find idstr: idstr
        if entry
          user=entry\get_user!
          if user
            return user, res
          return nil, "OTP -> user resulution failed."
        return nil, "OTP unknown. idstr: "..idstr
      return nil, "invalid OTP, details: "..query.." => "..res
    if @required
      return nil, res
    return false, res

  mkNonce: =>
    now=os.time!
    hash=slugify now..encode_base64 hmac_sha1 now..ranval!,ranval!..now
    hash=hash\sub 1,20
    hash\lower!

  getLoginOkHtml:    => ""
  getLoginErrorHtml: => ""

  getLoginHtml: =>
    render_html ->
      div class: "pure-control-group yubigroup", ->
        label for: "yubikey", "YubiCloud:"
        input id: "yubikey", type: "yubikey", name: "yubikey", autocomplete: "off", placeholder: "YubiKey OTP", autofocus: @takeControlOfForm and "autofocus" or nil
        if @takeControlOfForm
          input type: "button", value: "X", id: "closeYubiKeyBtn"
      if @takeControlOfForm
        script ->
          raw [[
            document.getElementsByClassName("usernamegroup")[0].hidden=true
            document.getElementsByClassName("passwordgroup")[0].hidden=true
            document.getElementById("closeYubiKeyBtn").onclick=function(){
              document.getElementsByClassName("usernamegroup")[0].hidden=false
              document.getElementsByClassName("passwordgroup")[0].hidden=false
              document.getElementsByClassName("yubigroup")[0].hidden=true
              document.getElementById("username").focus()
            }
          ]]


github: increase the number of events we handle

util = require 'util'

hex_to_char = (x) ->
  string.char(tonumber(x, 16))

unescape = (url) ->
  url\gsub("%%(%x%x)", hex_to_char)

handlers = {
  push: (repo, destination, json) ->
    branch = util.bold(json.ref\gsub 'refs/heads/', '')
    for i, c in pairs(json.commits)
      if i > 3
        break
      message = c.message\gsub '\n.*', ''
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{branch}, #{c.author.username}: #{message} #{c.url}"
    if #json.commits > 3
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{branch}, #{#json.commits-3} more commits not displayed."
  watch: (repo, destination, json) ->
    action = json.action
    if action == 'started'
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{json.sender.login}: +1 ★ (Total: #{json.repository.stargazers_count})"
    else
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{json.sender.login}: #{action} watching"
  ping: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: ping"
  commit_comment: (repo, destination, json) ->
    --commenter = json.comment.user.login
    body = json.body
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{json.sender.login}: commented #{body}"
  pull_request: (repo, destination, json) ->
    action = json.action
    number = util.bold json.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{json.sender.login}: #{action} PR ##{number}: #{json.pull_request.title} #{json.pull_request.html_url}"
  issues: (repo, destination, json) ->
    action = json.action
    nr = util.bold json.issue.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{json.sender.login}: #{action} issue ##{nr}: #{json.issue.title} #{json.issue.html_url}"
  issue_comment: (repo, destination, json) ->
    action = json.action
    nr = util.bold json.issue.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{json.sender.login}: comment on issue ##{nr}: #{json.comment.body} #{json.issue.html_url}"
  fork: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{json.sender.login}: forked to #{json.forkee.html_url}"
  create: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{json.sender.login}: created #{json.ref_type} #{util.bold json.ref}"
  delete: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{json.sender.login}: deleted #{json.ref_type} #{util.bold json.ref}"
  gollum: (repo, destination, json) ->
    return
  release: (repo, destination, json) ->
    return
}

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = unescape(destination)
  json = util.json.decode(req.body)

  repo = json.repository.full_name

  event = req.headers['X-GitHub-Event']
  handler = handlers[event]
  if handler
    handler(repo, destination, json)
  else
    ivar2\Log('error', 'Unknown github event: %s', event)

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}

util = require 'util'

hex_to_char = (x) ->
  string.char(tonumber(x, 16))

unescape = (url) ->
  url\gsub("%%(%x%x)", hex_to_char)

handlers = {
  push: (repo, destination, json) ->
    branch = json.ref\gsub 'refs/heads/', ''
    for i, c in pairs(json.commits)
      if i > 3
        break
      message = c.message\gsub '\n.*', ''
      ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{c.author.username}: #{message} #{c.url}"
    if #json.commits > 3
      ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{#json.commits-3} more commits not displayed."
  watch: (repo, destination, json) ->
    action = json.action
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{json.sender.login}: #{action} watching"

}

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = unescape(destination)
  json = util.json.decode(req.body)

  repo = json.repository.full_name

  event = req.headers['X-GitHub-Event']
  handler = handlers[event]
  if handler
    handler(repo, destination, json)
  else
    ivar2\Log('error', 'Unknown github event: %s', event)

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}


Fixed gmod damage scaling

Fixed gmod damage scaling

--
-- 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_SCAILING = CreateConVar('ppm2_sv_dmg', '1', {FCVAR_NOTIFY}, 'Enable hitbox damage scailing')
HEAD = CreateConVar('ppm2_sv_dmg_head', '2', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in head')
CHEST = CreateConVar('ppm2_sv_dmg_chest', '1', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in chest')
STOMACH = CreateConVar('ppm2_sv_dmg_stomach', '1', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in stomach')
LEFTARM = CreateConVar('ppm2_sv_dmg_lfhoof', '0.75', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in left-forward hoof')
RIGHTARM = CreateConVar('ppm2_sv_dmg_rfhoof', '0.75', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in right-forward hoof')
LEFTLEG = CreateConVar('ppm2_sv_dmg_lbhoof', '0.75', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in back-forward hoof')
RIGHTLEG = CreateConVar('ppm2_sv_dmg_rbhoof', '0.75', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in back-forward hoof')

sk_player_head = GetConVar('sk_player_head')
sk_player_chest = GetConVar('sk_player_chest')
sk_player_stomach = GetConVar('sk_player_stomach')
sk_player_arm = GetConVar('sk_player_arm')
sk_player_leg = GetConVar('sk_player_leg')

hook.Add 'ScalePlayerDamage', 'PPM2.PlayerDamage', (group = HITGROUP_GENERIC, dmg) =>
	return if not @IsPonyCached()
	return if not ENABLE_SCAILING\GetBool()

	-- Reset damage to its original value
	-- https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/game/server/player.cpp#L180-L184
	-- https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/game/server/player.cpp#L923-L946

	--switch group
	--	when HITGROUP_HEAD
	--		dmg\ScaleDamage(1 / sk_player_head\GetFloat())
	--	when HITGROUP_CHEST
	--		dmg\ScaleDamage(1 / sk_player_chest\GetFloat())
	--	when HITGROUP_STOMACH
	--		dmg\ScaleDamage(1 / sk_player_stomach\GetFloat())
	--	when HITGROUP_RIGHTARM
	--		dmg\ScaleDamage(1 / sk_player_arm\GetFloat())
	--	when HITGROUP_RIGHTLEG
	--		dmg\ScaleDamage(1 / sk_player_leg\GetFloat())

	-- but fuck gmod
	-- https://github.com/Facepunch/garrysmod/blob/cf725f3f66072c83e4d96674814670c97eebb43d/garrysmod/gamemodes/base/gamemode/player.lua#L510

	switch group
		when HITGROUP_HEAD
			dmg\ScaleDamage(0.5)
		when HITGROUP_LEFTARM, HITGROUP_RIGHTARM, HITGROUP_LEFTLEG, HITGROUP_RIGHTLEG, HITGROUP_GEAR
			dmg\ScaleDamage(4)

	switch group
		when HITGROUP_HEAD
			dmg\ScaleDamage(HEAD\GetFloat())
		when HITGROUP_CHEST
			dmg\ScaleDamage(CHEST\GetFloat())
		when HITGROUP_STOMACH
			dmg\ScaleDamage(STOMACH\GetFloat())
		when HITGROUP_LEFTARM
			dmg\ScaleDamage(LEFTARM\GetFloat())
		when HITGROUP_RIGHTARM
			dmg\ScaleDamage(RIGHTARM\GetFloat())
		when HITGROUP_LEFTLEG
			dmg\ScaleDamage(RIGHTLEG\GetFloat())
		when HITGROUP_RIGHTLEG
			dmg\ScaleDamage(RIGHTLEG\GetFloat())
		when HITGROUP_GEAR
			dmg\ScaleDamage(CHEST\GetFloat())
		else
			dmg\ScaleDamage(1)

--
-- 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_SCAILING = CreateConVar('ppm2_sv_dmg', '1', {FCVAR_NOTIFY}, 'Enable hitbox damage scailing')
HEAD = CreateConVar('ppm2_sv_dmg_head', '2', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in head')
CHEST = CreateConVar('ppm2_sv_dmg_chest', '1', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in chest')
STOMACH = CreateConVar('ppm2_sv_dmg_stomach', '1', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in stomach')
LEFTARM = CreateConVar('ppm2_sv_dmg_lfhoof', '0.75', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in left-forward hoof')
RIGHTARM = CreateConVar('ppm2_sv_dmg_rfhoof', '0.75', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in right-forward hoof')
LEFTLEG = CreateConVar('ppm2_sv_dmg_lbhoof', '0.75', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in back-forward hoof')
RIGHTLEG = CreateConVar('ppm2_sv_dmg_rbhoof', '0.75', {FCVAR_NOTIFY}, 'Damage scale when pony-player got shot in back-forward hoof')

sk_player_head = GetConVar('sk_player_head')
sk_player_chest = GetConVar('sk_player_chest')
sk_player_stomach = GetConVar('sk_player_stomach')
sk_player_arm = GetConVar('sk_player_arm')
sk_player_leg = GetConVar('sk_player_leg')

hook.Add 'ScalePlayerDamage', 'PPM2.PlayerDamage', (group = HITGROUP_GENERIC, dmg) =>
	return if not @IsPonyCached()

	-- Reset damage to its original value
	-- https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/game/server/player.cpp#L180-L184
	-- https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/game/server/player.cpp#L923-L946
	switch group
		when HITGROUP_HEAD
			dmg\ScaleDamage(1 / sk_player_head\GetFloat())
		when HITGROUP_CHEST
			dmg\ScaleDamage(1 / sk_player_chest\GetFloat())
		when HITGROUP_STOMACH
			dmg\ScaleDamage(1 / sk_player_stomach\GetFloat())
		when HITGROUP_RIGHTARM
			dmg\ScaleDamage(1 / sk_player_arm\GetFloat())
		when HITGROUP_RIGHTLEG
			dmg\ScaleDamage(1 / sk_player_leg\GetFloat())

	return if not ENABLE_SCAILING\GetBool()

	switch group
		when HITGROUP_HEAD
			dmg\ScaleDamage(HEAD\GetFloat())
		when HITGROUP_CHEST
			dmg\ScaleDamage(CHEST\GetFloat())
		when HITGROUP_STOMACH
			dmg\ScaleDamage(STOMACH\GetFloat())
		when HITGROUP_LEFTARM
			dmg\ScaleDamage(LEFTARM\GetFloat())
		when HITGROUP_RIGHTARM
			dmg\ScaleDamage(RIGHTARM\GetFloat())
		when HITGROUP_LEFTLEG
			dmg\ScaleDamage(RIGHTLEG\GetFloat())
		when HITGROUP_RIGHTLEG
			dmg\ScaleDamage(RIGHTLEG\GetFloat())
		when HITGROUP_GEAR
			dmg\ScaleDamage(CHEST\GetFloat())
		else
			dmg\ScaleDamage(1)


Don't update data if it is already set up

--
-- Copyright (C) 2017-2020 DBotThePony

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

net = DLib.net

class PPM2.NetworkChangeState
	new: (key = '', keyValid = '', newValue, obj, len = 24, ply = NULL) =>
		@key = key
		@keyValid = keyValid
		@oldValue = obj[key]
		@newValue = newValue
		@ply = ply
		@time = CurTimeL()
		@rtime = RealTimeL()
		@stime = SysTime()
		@obj = obj
		@objID = obj.netID
		@len = len
		@rlen = len - 24 -- ID - 16 bits, variable id - 8 bits
		@cantApply = false
		@networkChange = true
	GetPlayer: => @ply
	ChangedByClient: => not @networkChange or IsValid(@ply)
	ChangedByPlayer: => not @networkChange or IsValid(@ply)
	ChangedByServer: => not @networkChange or not IsValid(@ply)
	GetKey: => @keyValid
	GetVariable: => @keyValid
	GetVar: => @keyValid
	GetKeyInternal: => @key
	GetVariableInternal: => @key
	GetVarInternal: => @key
	GetNewValue: => @newValue
	GetValue: => @newValue
	GetCantApply: => @cantApply
	SetCantApply: (val) => @cantApply = val
	NewValue: => @newValue
	GetOldValue: => @oldValue
	OldValue: => @oldValue
	CurTime: => @time
	GetCurTime: => @time
	GetReceiveTime: => @time
	GetReceiveStamp: => @time
	RealTimeL: => @rtime
	GetRealTimeL: => @rtime
	SysTime: => @stime
	GetSysTime: => @stime
	GetObject: => @obj
	GetNWObject: => @obj
	GetNetworkedObject: => @obj
	GetLength: => @rlen
	GetRealLength: => @len
	ChangedByNetwork: => @networkChange

	Revert: => @obj[@key] = @oldValue if not @cantApply
	Apply: => @obj[@key] = @newValue if not @cantApply

wUInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteUInt(arg, size)

wInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteInt(arg, size)

rUInt = (size = 8, min = 0, max = 255) ->
	return -> math.Clamp(net.ReadUInt(size), min, max)

rInt = (size = 8, min = -128, max = 127) ->
	return -> math.Clamp(net.ReadInt(size), min, max)

rFloat = (min = 0, max = 255) ->
	return -> math.Clamp(net.ReadFloat(), min, max)

wFloat = net.WriteFloat
rBool = net.ReadBool
wBool = net.WriteBool
rColor = net.ReadColor
wColor = net.WriteColor
rString = net.ReadString
wString = net.WriteString

class NetworkedPonyData extends PPM2.ModifierBase
	@REGISTRY = {}

	@AddNetworkVar = (getName = 'Var', readFunc = (->), writeFunc = (->), defValue, onSet = ((val) => val), networkByDefault = true) =>
		defFunc = defValue
		defFunc = (-> defValue) if type(defValue) ~= 'function'
		strName = "_NW_#{getName}"
		@NW_NextVarID += 1
		id = @NW_NextVarID
		tab = {:strName, :readFunc, :getName, :writeFunc, :defValue, :defFunc, :id, :onSet}
		table.insert(@NW_Vars, tab)
		@NW_VarsTable[id] = tab
		@__base[strName] = defFunc()

		@__base["Get#{getName}"] = => @[strName]
		@__base["Set#{getName}"] = (val = defFunc(), networkNow = networkByDefault) =>
			oldVal = @[strName]
			nevVal = onSet(@, val)
			@[strName] = nevVal
			state = PPM2.NetworkChangeState(strName, getName, nevVal, @)
			state.networkChange = false
			@SetLocalChange(state)
			return unless networkNow and @NETWORKED and (CLIENT and @ent == LocalPlayer() or SERVER)
			net.Start(@@NW_Modify)
			net.WriteUInt(@GetNetworkID(), 16)
			net.WriteUInt(id, 16)
			writeFunc(nevVal)
			net.SendToServer() if CLIENT
			net.Broadcast() if SERVER

	@NetworkVar = (...) => @AddNetworkVar(...)
	@GetSet = (fname, fvalue) =>
		@__base["Get#{fname}"] = => @[fvalue]
		@__base["Set#{fname}"] = (fnewValue = @[fvalue]) =>
			oldVal = @[fvalue]
			@[fvalue] = fnewValue
			state = PPM2.NetworkChangeState(fvalue, fname, fnewValue, @)
			state.networkChange = false
			@SetLocalChange(state)

	if CLIENT
		hook.Add 'OnEntityCreated', 'PPM2.NW_WAIT', (ent) -> timer.Simple 0, ->
			return if not IsValid(ent)
			return if not @REGISTRY[ent\EntIndex()]
			return if @REGISTRY[ent\EntIndex()].done_setup or IsValid(@REGISTRY[ent\EntIndex()].ent)
			@REGISTRY[ent\EntIndex()]\SetupEntity(ent)

		hook.Add 'NotifyShouldTransmit', 'PPM2.NW_WAIT', (ent, should) ->
			return if not should
			return if not @REGISTRY[ent\EntIndex()]
			return if @REGISTRY[ent\EntIndex()].done_setup or IsValid(@REGISTRY[ent\EntIndex()].ent)
			@REGISTRY[ent\EntIndex()]\SetupEntity(ent)

		-- clientside entities
		hook.Add 'EntityRemoved', 'PPM2.NW_WAIT', (ent) -> @REGISTRY[ent]\Remove() if @REGISTRY[ent]

	@OnNetworkedCreated = (ply = NULL, len = 0, nwobj) =>
		if CLIENT
			netID = net.ReadUInt16()
			entid = net.ReadUInt16()
			obj = @NW_Objects[netID] or @(netID, entid)
			@REGISTRY[entid] = obj
			obj.NETWORKED = true
			obj.CREATED_BY_SERVER = true
			obj.NETWORKED_PREDICT = true
			obj\ReadNetworkData()
		else
			ply[@NW_CooldownTimer] = ply[@NW_CooldownTimer] or 0
			ply[@NW_CooldownTimerCount] = ply[@NW_CooldownTimerCount] or 0

			if ply[@NW_CooldownTimer] < RealTimeL()
				ply[@NW_CooldownTimerCount] = 1
				ply[@NW_CooldownTimer] = RealTimeL() + 10
			else
				ply[@NW_CooldownTimerCount] += 1

			if ply[@NW_CooldownTimerCount] >= 3
				ply[@NW_CooldownMessage] = ply[@NW_CooldownMessage] or 0
				if ply[@NW_CooldownMessage] < RealTimeL()
					PPM2.Message 'Player ', ply, " is creating #{@__name} too quickly!"
					ply[@NW_CooldownMessage] = RealTimeL() + 1
				return

			waitID = net.ReadUInt16()
			obj = @(nil, ply)
			obj.NETWORKED_PREDICT = true
			obj\ReadNetworkData()
			obj\Create()
			timer.Simple 0.5, ->
				return if not IsValid(ply)
				net.Start(@NW_ReceiveID)
				net.WriteUInt(waitID, 16)
				net.WriteUInt(obj.netID, 16)
				net.Send(ply)

	@OnNetworkedModify = (ply = NULL, len = 0) =>
		id = net.ReadUInt16()
		obj = @NW_Objects[id]

		unless obj
			return if CLIENT
			net.Start(@NW_Rejected)
			net.WriteUInt(id, 16)
			net.Send(ply)
			return

		if IsValid(ply) and obj.ent ~= ply
			error('Invalid realm for player being specified. If you are running on your own net.* library, check up your code') if CLIENT
			net.Start(@NW_Rejected)
			net.WriteUInt(id, 16)
			net.Send(ply)
			return

		varID = net.ReadUInt16()
		varData = @NW_VarsTable[varID]
		return unless varData
		{:strName, :getName, :readFunc, :writeFunc, :onSet} = varData
		newVal = onSet(obj, readFunc())
		return if newVal == obj["Get#{getName}"](obj)
		state = PPM2.NetworkChangeState(strName, getName, newVal, obj, len, ply)
		state\Apply()
		obj\NetworkDataChanges(state)
		if SERVER
			net.Start(@NW_Modify)
			net.WriteUInt(id, 16)
			net.WriteUInt(varID, 16)
			writeFunc(newVal)
			net.SendOmit(ply)

	@OnNetworkedDelete = (ply = NULL, len = 0) =>
		id = net.ReadUInt16()
		obj = @NW_Objects[id]
		return unless obj
		obj\Remove(true)

	@ReadNetworkData = =>
		output = {strName, {getName, readFunc()} for _, {:getName, :strName, :readFunc} in ipairs @NW_Vars}
		return output

	@RenderTasks = {}
	@CheckTasks = {}

	@NW_Vars = {}
	@NW_VarsTable = {}
	@NW_Objects = {}
	@O_Slots = {} if CLIENT
	@NW_Waiting = {}
	@NW_WaitID = -1
	@NW_NextVarID = -1
	@NW_Create = 'PPM2.NW.Created'
	@NW_Modify = 'PPM2.NW.Modified'
	@NW_Broadcast = 'PPM2.NW.ModifiedBroadcast'
	@NW_Remove = 'PPM2.NW.Removed'
	@NW_Rejected = 'PPM2.NW.Rejected'
	@NW_ReceiveID = 'PPM2.NW.ReceiveID'
	@NW_CooldownTimerCount = 'ppm2_NW_CooldownTimerCount'
	@NW_CooldownTimer = 'ppm2_NW_CooldownTimer'
	@NW_CooldownMessage = 'ppm2_NW_CooldownMessage'
	@NW_NextObjectID = 0
	@NW_NextObjectID_CL = 0x60000

	if SERVER
		net.pool(@NW_Create)
		net.pool(@NW_Modify)
		net.pool(@NW_Remove)
		net.pool(@NW_ReceiveID)
		net.pool(@NW_Rejected)
		net.pool(@NW_Broadcast)

	net.Receive @NW_Create, (len = 0, ply = NULL, obj) -> @OnNetworkedCreated(ply, len, obj)
	net.Receive @NW_Modify, (len = 0, ply = NULL, obj) -> @OnNetworkedModify(ply, len, obj)
	net.Receive @NW_Remove, (len = 0, ply = NULL, obj) -> @OnNetworkedDelete(ply, len, obj)

	if SERVER
		net.ReceiveAntispam(@NW_Create)
		net.ReceiveAntispam(@NW_Remove)

	net.Receive @NW_ReceiveID, (len = 0, ply = NULL) ->
		return if SERVER
		waitID = net.ReadUInt16()
		netID = net.ReadUInt16()
		obj = @NW_Waiting[waitID]
		@NW_Waiting[waitID] = nil
		return unless obj
		obj.NETWORKED = true
		@NW_Objects[obj.netID] = nil
		obj.netID = netID
		obj.waitID = nil
		@NW_Objects[netID] = obj

	net.Receive @NW_Rejected, (len = 0, ply = NULL) ->
		return if SERVER
		netID = net.ReadUInt16()
		obj = @NW_Objects[netID]
		return unless obj
		return if obj.__LastReject and obj.__LastReject > RealTimeL()
		obj.__LastReject = RealTimeL() + 3
		obj.NETWORKED = false
		obj\Create()

	net.Receive @NW_Broadcast, (len = 0, ply = NULL) ->
		return if SERVER
		netID = net.ReadUInt16()
		obj = @NW_Objects[netID]
		return unless obj
		obj\ReadNetworkData(len, ply)

	@GetSet('UpperManeModel', 'm_upperManeModel')
	@GetSet('LowerManeModel', 'm_lowerManeModel')
	@GetSet('TailModel', 'm_tailModel')
	@GetSet('SocksModel', 'm_socksModel')
	@GetSet('HornModel', 'm_hornmodel')
	@GetSet('ClothesModel', 'm_clothesmodel')
	@GetSet('NewSocksModel', 'm_newSocksModel')

	-- @NetworkVar('Fly',                  rBool,   wBool,                 false)
	@NetworkVar('DisableTask',          rBool,   wBool,                 false)
	@NetworkVar('UseFlexLerp',          rBool,   wBool,                  true)
	@NetworkVar('FlexLerpMultiplier',   rFloat(0, 10),  wFloat,             1)

	@SetupModifiers: =>
		for key, value in pairs PPM2.PonyDataRegistry
			if value.modifiers
				@RegisterModifier(value.getFunc, 0, 0)
				@SetModifierMinMaxFinal(value.getFunc, value.min, value.max) if value.min or value.max
				@SetupLerpTables(value.getFunc)
				strName = '_NW_' .. value.getFunc
				funcLerp = 'Calculate' .. value.getFunc
				@__base['Get' .. value.getFunc] = => @[funcLerp](@, @[strName])

	for key, value in pairs PPM2.PonyDataRegistry
		@NetworkVar(value.getFunc, value.read, value.write, value.default)

	new: (netID, ent) =>
		super()

		@m_upperManeModel = Entity(-1)
		@m_lowerManeModel = Entity(-1)
		@m_tailModel = Entity(-1)
		@m_socksModel = Entity(-1)
		@m_newSocksModel = Entity(-1)

		@recomputeTextures = true
		@isValid = true
		@removed = false
		@valid = true
		@NETWORKED = false
		@NETWORKED_PREDICT = false

		@[data.strName] = data.defFunc() for _, data in ipairs @@NW_Vars when data.defFunc

		if SERVER
			@netID = @@NW_NextObjectID
			@@NW_NextObjectID += 1
		else
			netID = -1 if netID == nil
			@netID = netID

		@@NW_Objects[@netID] = @

		if CLIENT
			for i = 1, 1024
				if not @@O_Slots[i]
					@slotID = i
					@@O_Slots[i] = @
					break

			error('no more free pony data edicts') if not @slotID

		@entID = isnumber(ent) and ent or ent\EntIndex()
		ent = Entity(ent) if isnumber(ent)

		return if not IsValid(ent)
		@ent = ent
		@modelCached = ent\GetModel() if IsValid(ent)
		@SetupEntity(ent)

	GetEntity: => @ent or NULL
	IsValid: => @isValid
	GetModel: => @modelCached
	EntIndex: => @entID
	ObjectSlot: => @slotID
	GetObjectSlot: => @slotID

	Clone: (target = @ent) =>
		copy = @@(nil, target)
		@ApplyDataToObject(copy)
		return copy

	SetupEntity: (ent) =>
		if @@REGISTRY[ent]
			return if @@REGISTRY[ent]\GetOwner() and IsValid(@@REGISTRY[ent]\GetOwner()) and @@REGISTRY[ent]\GetOwner() ~= @GetOwner()
			@@REGISTRY[ent]\Remove() if @@REGISTRY[ent].Remove and @@REGISTRY[ent] ~= @

		return unless IsValid(ent)

		if @@REGISTRY[ent\EntIndex()]
			return if @@REGISTRY[ent\EntIndex()]\GetOwner() and IsValid(@@REGISTRY[ent\EntIndex()]\GetOwner()) and @@REGISTRY[ent\EntIndex()]\GetOwner() ~= @GetOwner()
			@@REGISTRY[ent\EntIndex()]\Remove() if @@REGISTRY[ent\EntIndex()].Remove and @@REGISTRY[ent\EntIndex()] ~= @

		@ent = ent
		@done_setup = true
		ent\SetPonyData(@)
		@entTable = @ent\GetTable()

		@modelCached = ent\GetModel()
		ent\PPMBonesModifier()
		@flightController = PPM2.PonyflyController(@) if not @flightController
		@entID = ent\EntIndex() if not @entID
		@lastLerpThink = RealTimeL() if not @lastLerpThink

		@ModelChanges(@modelCached, @modelCached)
		@Reset()

		timer.Simple(0, -> @GetRenderController()\CompileTextures() if @GetRenderController()) if CLIENT

		@@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
		@@CheckTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task\GetDisableTask()]

	ModelChanges: (old = @ent\GetModel(), new = old) =>
		@modelCached = new

		@ent\SetNW2Bool('ppm2_fly', false)

		timer.Simple 0.5, ->
			return unless IsValid(@ent)
			@Reset()

	GenericDataChange: (state) =>
		hook.Run 'PPM2_PonyDataChanges', @ent, @, state

		if state\GetKey() == 'DisableTask'
			@@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
			@@CheckTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task\GetDisableTask()]

		@GetSizeController()\DataChanges(state) if @ent and @GetBodygroupController()
		@GetBodygroupController()\DataChanges(state) if @ent and @GetBodygroupController()

		@GetWeightController()\DataChanges(state) if @ent and @GetWeightController()

		if CLIENT and @ent
			@GetRenderController()\DataChanges(state) if @GetRenderController()

	ResetScale: =>
		if scale = @GetSizeController()
			scale\ResetScale()

	ModifyScale: =>
		if scale = @GetSizeController()
			scale\ModifyScale()

	GetIsEditor: => @is_editor_data or false
	SetIsEditor: (value = false) => @is_editor_data = value

	Reset: =>
		if scale = @GetSizeController()
			scale\Reset()
		if CLIENT
			@GetWeightController()\Reset() if @GetWeightController() and @GetWeightController().Reset
			@GetRenderController()\Reset() if @GetRenderController() and @GetRenderController().Reset
			@GetBodygroupController()\Reset() if @GetBodygroupController() and @GetBodygroupController().Reset

	GetHoofstepVolume: =>
		return 0.8 if @ShouldMuffleHoosteps()
		return 1

	ShouldMuffleHoosteps: =>
		return @GetSocksAsModel() or @GetSocksAsNewModel()

	PlayerRespawn: =>
		return if not IsValid(@ent)
		@entTable.__cachedIsPony = @ent\IsPony()
		if not @entTable.__cachedIsPony
			return if @alreadyCalledRespawn
			@alreadyCalledRespawn = true
			@alreadyCalledDeath = true
		else
			@alreadyCalledRespawn = false
			@alreadyCalledDeath = false
		@ApplyBodygroups(CLIENT, true)

		@ent\SetNW2Bool('ppm2_fly', false)

		@ent\PPMBonesModifier()

		if scale = @GetSizeController()
			scale\PlayerRespawn()

		if weight = @GetWeightController()
			weight\PlayerRespawn()

		if CLIENT
			@deathRagdollMerged = false
			@GetRenderController()\PlayerRespawn() if @GetRenderController()
			@GetBodygroupController()\MergeModels(@ent) if IsValid(@ent) and @GetBodygroupController().MergeModels

	PlayerDeath: =>
		return if not IsValid(@ent)

		if @ent.__ppmBonesModifiers
			@ent.__ppmBonesModifiers\Remove()

		@entTable.__cachedIsPony = @ent\IsPony()

		if not @entTable.__cachedIsPony
			return if @alreadyCalledDeath
			@alreadyCalledDeath = true
		else
			@alreadyCalledDeath = false

		@ent\SetNW2Bool('ppm2_fly', false)

		if scale = @GetSizeController()
			scale\PlayerDeath()

		if weight = @GetWeightController()
			weight\PlayerDeath()

		if CLIENT
			@DoRagdollMerge()
			@GetRenderController()\PlayerDeath() if @GetRenderController()

	DoRagdollMerge: =>
		return if @deathRagdollMerged
		bgController = @GetBodygroupController()
		rag = @ent\GetRagdollEntity()

		if not bgController.MergeModels
			@deathRagdollMerged = true
		elseif IsValid(rag)
			@deathRagdollMerged = true
			bgController\MergeModels(rag)

	ApplyBodygroups: (updateModels = CLIENT) => @GetBodygroupController()\ApplyBodygroups(updateModels) if @ent
	SetLocalChange: (state) => @GenericDataChange(state)
	NetworkDataChanges: (state) => @GenericDataChange(state)

	GetPonyRaceFlags: =>
		switch @GetRace()
			when PPM2.RACE_EARTH
				return 0
			when PPM2.RACE_PEGASUS
				return PPM2.RACE_HAS_WINGS
			when PPM2.RACE_UNICORN
				return PPM2.RACE_HAS_HORN
			when PPM2.RACE_ALICORN
				return PPM2.RACE_HAS_HORN + PPM2.RACE_HAS_WINGS

	SlowUpdate: =>
		@GetBodygroupController()\SlowUpdate() if @GetBodygroupController()
		@GetWeightController()\SlowUpdate() if @GetWeightController()
		if scale = @GetSizeController()
			scale\SlowUpdate()

		if SERVER and IsValid(@ent) and @ent\IsPlayer()
			arms = @ent\GetHands()

			if IsValid(arms)
				cond = @GetPonyRaceFlags()\band(PPM2.RACE_HAS_HORN) ~= 0 and @ent\GetInfoBool('ppm2_cl_vm_magic_hands', true) and PPM2.HAND_BODYGROUP_MAGIC or PPM2.HAND_BODYGROUP_HOOVES
				arms\SetBodygroup(PPM2.HAND_BODYGROUP_ID, cond)

	Think: =>
	RenderScreenspaceEffects: =>
		time = RealTimeL()
		delta = time - @lastLerpThink
		@lastLerpThink = time
		if @isValid and IsValid(@ent)
			for _, change in ipairs @TriggerLerpAll(delta * 5)
				state = PPM2.NetworkChangeState('_NW_' .. change[1], change[1], change[2] + @['_NW_' .. change[1]], @)
				state\SetCantApply(true)
				@GenericDataChange(state)

	GetFlightController: => @flightController

	GetRenderController: =>
		return if SERVER
		return @renderController if not @isValid
		if not @renderController or @modelCached ~= @modelRender
			@modelRender = @modelCached
			cls = PPM2.GetRenderController(@modelCached)
			if @renderController and cls == @renderController.__class
				@renderController.ent = @ent
				PPM2.DebugPrint('Skipping render controller recreation for ', @ent, ' as part of ', @)
				return @renderController
			@renderController\Remove() if @renderController
			@renderController = cls(@)
		@renderController.ent = @ent
		return @renderController

	GetWeightController: =>
		return @weightController if not @isValid
		if not @weightController or @modelCached ~= @modelWeight
			@modelCached = @modelCached or @ent\GetModel()
			@modelWeight = @modelCached
			cls = PPM2.GetPonyWeightController(@modelCached)
			if @weightController and cls == @weightController.__class
				@weightController.ent = @ent
				PPM2.DebugPrint('Skipping weight controller recreation for ', @ent, ' as part of ', @)
				return @weightController
			@weightController\Remove() if @weightController
			@weightController = cls(@)
		@weightController.ent = @ent
		return @weightController

	GetSizeController: =>
		return @scaleController if not @isValid
		if not @scaleController or @modelCached ~= @modelScale
			@modelCached = @modelCached or @ent\GetModel()
			@modelScale = @modelCached
			cls = PPM2.GetSizeController(@modelCached)
			if @scaleController and cls == @scaleController.__class
				@scaleController.ent = @ent
				PPM2.DebugPrint('Skipping size controller recreation for ', @ent, ' as part of ', @)
				return @scaleController
			@scaleController\Remove() if @scaleController
			@scaleController = cls(@)
		@scaleController.ent = @ent
		return @scaleController
	GetScaleController: => @GetSizeController()

	GetBodygroupController: =>
		return @bodygroups if not @isValid
		if not @bodygroups or @modelBodygroups ~= @modelCached
			@modelCached = @modelCached or @ent\GetModel()
			@modelBodygroups = @modelCached
			cls = PPM2.GetBodygroupController(@modelCached)
			if @bodygroups and cls == @bodygroups.__class
				@bodygroups.ent = @ent
				PPM2.DebugPrint('Skipping bodygroup controller recreation for ', @ent, ' as part of ', @)
				return @bodygroups
			@bodygroups\Remove() if @bodygroups
			@bodygroups = cls(@)
		@bodygroups.ent = @ent
		return @bodygroups

	Remove: (byClient = false) =>
		@removed = true
		@@NW_Objects[@netID] = nil if SERVER or @NETWORKED
		@@O_Slots[@slotID] = nil if @slotID
		@isValid = false
		@ent = @GetEntity() if not IsValid(@ent)
		@@REGISTRY[@ent] = nil if IsValid(@ent) and @@REGISTRY[@ent] == @
		@@REGISTRY[@entID] = nil if @@REGISTRY[@entID] == @
		@GetWeightController()\Remove() if @GetWeightController()

		if CLIENT
			@GetRenderController()\Remove() if @GetRenderController()
			if IsValid(@ent) and @ent.__ppm2_task_hit
				@entTable.__ppm2_task_hit = false
				@ent\SetNoDraw(false)

		@GetBodygroupController()\Remove() if @GetBodygroupController()
		@GetSizeController()\Remove() if @GetSizeController()

		@flightController\Switch(false) if @flightController
		@@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
		@@CheckTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task\GetDisableTask()]

	__tostring: => "[#{@@__name}:#{@netID}|#{@ent}]"

	GetOwner: => @ent
	IsNetworked: => @NETWORKED
	IsGoingToNetwork: => @NETWORKED_PREDICT
	SetIsGoingToNetwork: (val = @NETWORKED) => @NETWORKED_PREDICT = val
	IsLocal: => @isLocal
	IsLocalObject: => @isLocal
	GetNetworkID: => @netID
	NetworkID: => @netID
	NetID: => @netID

	ComputeMagicColor: =>
		color = @GetHornMagicColor()

		if not @GetSeparateMagicColor()
			if not @GetSeparateEyes()
				color = @GetEyeIrisTop()\Lerp(0.5, @GetEyeIrisBottom())
			else
				lerpLeft = @GetEyeIrisTopLeft()\Lerp(0.5, @GetEyeIrisBottomLeft())
				lerpRight = @GetEyeIrisTopRight()\Lerp(0.5, @GetEyeIrisBottomRight())
				color = lerpLeft\Lerp(0.5, lerpRight)

		return color

	ReadNetworkData: (len = 24, ply = NULL, silent = false, applyEntities = true) =>
		data = @@ReadNetworkData()
		validPly = IsValid(ply)
		states = [PPM2.NetworkChangeState(key, keyValid, newVal, @, len, ply) for key, {keyValid, newVal} in pairs data]
		for _, state in ipairs states
			if not validPly or applyEntities or not isentity(state\GetValue())
				state\Apply()
				@NetworkDataChanges(state) unless silent

	NetworkedIterable: (grabEntities = true) =>
		data = [{getName, @[strName]} for _, {:strName, :getName} in ipairs @@NW_Vars when grabEntities or not isentity(@[strName])]
		return data

	ApplyDataToObject: (target, applyEntities = false) =>
		target["Set#{key}"](target, value) for {key, value} in *@NetworkedIterable(applyEntities) when target["Set#{key}"]
		return target

	WriteNetworkData: => writeFunc(@[strName]) for _, {:strName, :writeFunc} in ipairs @@NW_Vars

	ReBroadcast: =>
		return false if not @NETWORKED
		return false if CLIENT
		net.Start(@@NW_Broadcast)
		net.WriteUInt16(@netID)
		@WriteNetworkData()
		net.Broadcast()
		return true

	Create: =>
		return if @NETWORKED
		return if CLIENT and @CREATED_BY_SERVER -- wtf
		@NETWORKED = true if SERVER
		@NETWORKED_PREDICT = true

		if SERVER
			net.Start(@@NW_Create)
			net.WriteUInt16(@netID)
			net.WriteEntity(@ent)
			@WriteNetworkData()
			filter = RecipientFilter()
			filter\AddAllPlayers()
			filter\RemovePlayer(@ent) if IsValid(@ent) and @ent\IsPlayer()
			net.Send(filter)
		else
			@@NW_WaitID += 1
			@waitID = @@NW_WaitID

			net.Start(@@NW_Create)
			before = net.BytesWritten()

			net.WriteUInt16(@waitID)
			@WriteNetworkData()
			after = net.BytesWritten()

			net.SendToServer()
			@@NW_Waiting[@waitID] = @
			return after - before

	NetworkTo: (targets = {}) =>
		net.Start(@@NW_Create)
		net.WriteUInt16(@netID)
		net.WriteEntity(@ent)
		@WriteNetworkData()
		net.Send(targets)

PPM2.NetworkedPonyData = NetworkedPonyData

if CLIENT
	net.Receive 'PPM2.NotifyDisconnect', ->
		netID = net.ReadUInt16()
		data = NetworkedPonyData.NW_Objects[netID]
		return if not data
		data\Remove()

	net.Receive 'PPM2.PonyDataRemove', ->
		netID = net.ReadUInt16()
		data = NetworkedPonyData.NW_Objects[netID]
		return if not data
		data\Remove()
else
	hook.Add 'PlayerJoinTeam', 'PPM2.TeamWaypoint', (ply, new) ->
		ply.__ppm2_modified_jump = false
	hook.Add 'OnPlayerChangedTeam', 'PPM2.TeamWaypoint', (ply, old, new) ->
		ply.__ppm2_modified_jump = false

_G.LocalPonyData = () -> LocalPlayer()\GetPonyData()
_G.LocalPonydata = () -> LocalPlayer()\GetPonyData()

do
	REGISTRY = NetworkedPonyData.REGISTRY

	nullify = ->
		for _, ent in ipairs ents.GetAll()
			if ent.__PPM2_PonyData and ent.__PPM2_PonyData.Remove
				ProtectedCall -> ent.__PPM2_PonyData\Remove()

		for _, ent in ipairs ents.GetAll()
			ent.__PPM2_PonyData = nil

		for key, obj in pairs(REGISTRY)
			if obj.Remove
				ProtectedCall -> obj\Remove()

			REGISTRY[key] = nil

	nullify()

	entMeta = FindMetaTable('Entity')
	EntIndex = entMeta.EntIndex

	entMeta.GetPonyData = =>
		index = EntIndex(@)
		return REGISTRY[index] if index > 0
		return REGISTRY[@]

	entMeta.SetPonyData = (data) =>
		index = EntIndex(@)

		if index > 0
			REGISTRY[index] = data
			return

		REGISTRY[@] = data

--
-- Copyright (C) 2017-2020 DBotThePony

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

net = DLib.net

class PPM2.NetworkChangeState
	new: (key = '', keyValid = '', newValue, obj, len = 24, ply = NULL) =>
		@key = key
		@keyValid = keyValid
		@oldValue = obj[key]
		@newValue = newValue
		@ply = ply
		@time = CurTimeL()
		@rtime = RealTimeL()
		@stime = SysTime()
		@obj = obj
		@objID = obj.netID
		@len = len
		@rlen = len - 24 -- ID - 16 bits, variable id - 8 bits
		@cantApply = false
		@networkChange = true
	GetPlayer: => @ply
	ChangedByClient: => not @networkChange or IsValid(@ply)
	ChangedByPlayer: => not @networkChange or IsValid(@ply)
	ChangedByServer: => not @networkChange or not IsValid(@ply)
	GetKey: => @keyValid
	GetVariable: => @keyValid
	GetVar: => @keyValid
	GetKeyInternal: => @key
	GetVariableInternal: => @key
	GetVarInternal: => @key
	GetNewValue: => @newValue
	GetValue: => @newValue
	GetCantApply: => @cantApply
	SetCantApply: (val) => @cantApply = val
	NewValue: => @newValue
	GetOldValue: => @oldValue
	OldValue: => @oldValue
	CurTime: => @time
	GetCurTime: => @time
	GetReceiveTime: => @time
	GetReceiveStamp: => @time
	RealTimeL: => @rtime
	GetRealTimeL: => @rtime
	SysTime: => @stime
	GetSysTime: => @stime
	GetObject: => @obj
	GetNWObject: => @obj
	GetNetworkedObject: => @obj
	GetLength: => @rlen
	GetRealLength: => @len
	ChangedByNetwork: => @networkChange

	Revert: => @obj[@key] = @oldValue if not @cantApply
	Apply: => @obj[@key] = @newValue if not @cantApply

wUInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteUInt(arg, size)

wInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteInt(arg, size)

rUInt = (size = 8, min = 0, max = 255) ->
	return -> math.Clamp(net.ReadUInt(size), min, max)

rInt = (size = 8, min = -128, max = 127) ->
	return -> math.Clamp(net.ReadInt(size), min, max)

rFloat = (min = 0, max = 255) ->
	return -> math.Clamp(net.ReadFloat(), min, max)

wFloat = net.WriteFloat
rBool = net.ReadBool
wBool = net.WriteBool
rColor = net.ReadColor
wColor = net.WriteColor
rString = net.ReadString
wString = net.WriteString

class NetworkedPonyData extends PPM2.ModifierBase
	@REGISTRY = {}

	@AddNetworkVar = (getName = 'Var', readFunc = (->), writeFunc = (->), defValue, onSet = ((val) => val), networkByDefault = true) =>
		defFunc = defValue
		defFunc = (-> defValue) if type(defValue) ~= 'function'
		strName = "_NW_#{getName}"
		@NW_NextVarID += 1
		id = @NW_NextVarID
		tab = {:strName, :readFunc, :getName, :writeFunc, :defValue, :defFunc, :id, :onSet}
		table.insert(@NW_Vars, tab)
		@NW_VarsTable[id] = tab
		@__base[strName] = defFunc()

		@__base["Get#{getName}"] = => @[strName]
		@__base["Set#{getName}"] = (val = defFunc(), networkNow = networkByDefault) =>
			oldVal = @[strName]
			nevVal = onSet(@, val)
			@[strName] = nevVal
			state = PPM2.NetworkChangeState(strName, getName, nevVal, @)
			state.networkChange = false
			@SetLocalChange(state)
			return unless networkNow and @NETWORKED and (CLIENT and @ent == LocalPlayer() or SERVER)
			net.Start(@@NW_Modify)
			net.WriteUInt(@GetNetworkID(), 16)
			net.WriteUInt(id, 16)
			writeFunc(nevVal)
			net.SendToServer() if CLIENT
			net.Broadcast() if SERVER

	@NetworkVar = (...) => @AddNetworkVar(...)
	@GetSet = (fname, fvalue) =>
		@__base["Get#{fname}"] = => @[fvalue]
		@__base["Set#{fname}"] = (fnewValue = @[fvalue]) =>
			oldVal = @[fvalue]
			@[fvalue] = fnewValue
			state = PPM2.NetworkChangeState(fvalue, fname, fnewValue, @)
			state.networkChange = false
			@SetLocalChange(state)

	if CLIENT
		hook.Add 'OnEntityCreated', 'PPM2.NW_WAIT', (ent) -> timer.Simple 0, ->
			return if not IsValid(ent)
			return if not @REGISTRY[ent\EntIndex()]
			return if @REGISTRY[ent\EntIndex()].done_setup or IsValid(@REGISTRY[ent\EntIndex()].ent)
			@REGISTRY[ent\EntIndex()]\SetupEntity(ent)

		hook.Add 'NotifyShouldTransmit', 'PPM2.NW_WAIT', (ent, should) ->
			return if not should
			return if not @REGISTRY[ent\EntIndex()]
			return if @REGISTRY[ent\EntIndex()].done_setup or IsValid(@REGISTRY[ent\EntIndex()].ent)
			@REGISTRY[ent\EntIndex()]\SetupEntity(ent)

		-- clientside entities
		hook.Add 'EntityRemoved', 'PPM2.NW_WAIT', (ent) -> @REGISTRY[ent]\Remove() if @REGISTRY[ent]

	@OnNetworkedCreated = (ply = NULL, len = 0, nwobj) =>
		if CLIENT
			netID = net.ReadUInt16()
			entid = net.ReadUInt16()
			obj = @NW_Objects[netID] or @(netID, entid)
			@REGISTRY[entid] = obj
			obj.NETWORKED = true
			obj.CREATED_BY_SERVER = true
			obj.NETWORKED_PREDICT = true
			obj\ReadNetworkData()
		else
			ply[@NW_CooldownTimer] = ply[@NW_CooldownTimer] or 0
			ply[@NW_CooldownTimerCount] = ply[@NW_CooldownTimerCount] or 0

			if ply[@NW_CooldownTimer] < RealTimeL()
				ply[@NW_CooldownTimerCount] = 1
				ply[@NW_CooldownTimer] = RealTimeL() + 10
			else
				ply[@NW_CooldownTimerCount] += 1

			if ply[@NW_CooldownTimerCount] >= 3
				ply[@NW_CooldownMessage] = ply[@NW_CooldownMessage] or 0
				if ply[@NW_CooldownMessage] < RealTimeL()
					PPM2.Message 'Player ', ply, " is creating #{@__name} too quickly!"
					ply[@NW_CooldownMessage] = RealTimeL() + 1
				return

			waitID = net.ReadUInt16()
			obj = @(nil, ply)
			obj.NETWORKED_PREDICT = true
			obj\ReadNetworkData()
			obj\Create()
			timer.Simple 0.5, ->
				return if not IsValid(ply)
				net.Start(@NW_ReceiveID)
				net.WriteUInt(waitID, 16)
				net.WriteUInt(obj.netID, 16)
				net.Send(ply)

	@OnNetworkedModify = (ply = NULL, len = 0) =>
		id = net.ReadUInt16()
		obj = @NW_Objects[id]

		unless obj
			return if CLIENT
			net.Start(@NW_Rejected)
			net.WriteUInt(id, 16)
			net.Send(ply)
			return

		if IsValid(ply) and obj.ent ~= ply
			error('Invalid realm for player being specified. If you are running on your own net.* library, check up your code') if CLIENT
			net.Start(@NW_Rejected)
			net.WriteUInt(id, 16)
			net.Send(ply)
			return

		varID = net.ReadUInt16()
		varData = @NW_VarsTable[varID]
		return unless varData
		{:strName, :getName, :readFunc, :writeFunc, :onSet} = varData
		newVal = onSet(obj, readFunc())
		return if newVal == obj["Get#{getName}"](obj)
		state = PPM2.NetworkChangeState(strName, getName, newVal, obj, len, ply)
		state\Apply()
		obj\NetworkDataChanges(state)
		if SERVER
			net.Start(@NW_Modify)
			net.WriteUInt(id, 16)
			net.WriteUInt(varID, 16)
			writeFunc(newVal)
			net.SendOmit(ply)

	@OnNetworkedDelete = (ply = NULL, len = 0) =>
		id = net.ReadUInt16()
		obj = @NW_Objects[id]
		return unless obj
		obj\Remove(true)

	@ReadNetworkData = =>
		output = {strName, {getName, readFunc()} for _, {:getName, :strName, :readFunc} in ipairs @NW_Vars}
		return output

	@RenderTasks = {}
	@CheckTasks = {}

	@NW_Vars = {}
	@NW_VarsTable = {}
	@NW_Objects = {}
	@O_Slots = {} if CLIENT
	@NW_Waiting = {}
	@NW_WaitID = -1
	@NW_NextVarID = -1
	@NW_Create = 'PPM2.NW.Created'
	@NW_Modify = 'PPM2.NW.Modified'
	@NW_Broadcast = 'PPM2.NW.ModifiedBroadcast'
	@NW_Remove = 'PPM2.NW.Removed'
	@NW_Rejected = 'PPM2.NW.Rejected'
	@NW_ReceiveID = 'PPM2.NW.ReceiveID'
	@NW_CooldownTimerCount = 'ppm2_NW_CooldownTimerCount'
	@NW_CooldownTimer = 'ppm2_NW_CooldownTimer'
	@NW_CooldownMessage = 'ppm2_NW_CooldownMessage'
	@NW_NextObjectID = 0
	@NW_NextObjectID_CL = 0x60000

	if SERVER
		net.pool(@NW_Create)
		net.pool(@NW_Modify)
		net.pool(@NW_Remove)
		net.pool(@NW_ReceiveID)
		net.pool(@NW_Rejected)
		net.pool(@NW_Broadcast)

	net.Receive @NW_Create, (len = 0, ply = NULL, obj) -> @OnNetworkedCreated(ply, len, obj)
	net.Receive @NW_Modify, (len = 0, ply = NULL, obj) -> @OnNetworkedModify(ply, len, obj)
	net.Receive @NW_Remove, (len = 0, ply = NULL, obj) -> @OnNetworkedDelete(ply, len, obj)

	if SERVER
		net.ReceiveAntispam(@NW_Create)
		net.ReceiveAntispam(@NW_Remove)

	net.Receive @NW_ReceiveID, (len = 0, ply = NULL) ->
		return if SERVER
		waitID = net.ReadUInt16()
		netID = net.ReadUInt16()
		obj = @NW_Waiting[waitID]
		@NW_Waiting[waitID] = nil
		return unless obj
		obj.NETWORKED = true
		@NW_Objects[obj.netID] = nil
		obj.netID = netID
		obj.waitID = nil
		@NW_Objects[netID] = obj

	net.Receive @NW_Rejected, (len = 0, ply = NULL) ->
		return if SERVER
		netID = net.ReadUInt16()
		obj = @NW_Objects[netID]
		return unless obj
		return if obj.__LastReject and obj.__LastReject > RealTimeL()
		obj.__LastReject = RealTimeL() + 3
		obj.NETWORKED = false
		obj\Create()

	net.Receive @NW_Broadcast, (len = 0, ply = NULL) ->
		return if SERVER
		netID = net.ReadUInt16()
		obj = @NW_Objects[netID]
		return unless obj
		obj\ReadNetworkData(len, ply)

	@GetSet('UpperManeModel', 'm_upperManeModel')
	@GetSet('LowerManeModel', 'm_lowerManeModel')
	@GetSet('TailModel', 'm_tailModel')
	@GetSet('SocksModel', 'm_socksModel')
	@GetSet('HornModel', 'm_hornmodel')
	@GetSet('ClothesModel', 'm_clothesmodel')
	@GetSet('NewSocksModel', 'm_newSocksModel')

	-- @NetworkVar('Fly',                  rBool,   wBool,                 false)
	@NetworkVar('DisableTask',          rBool,   wBool,                 false)
	@NetworkVar('UseFlexLerp',          rBool,   wBool,                  true)
	@NetworkVar('FlexLerpMultiplier',   rFloat(0, 10),  wFloat,             1)

	@SetupModifiers: =>
		for key, value in pairs PPM2.PonyDataRegistry
			if value.modifiers
				@RegisterModifier(value.getFunc, 0, 0)
				@SetModifierMinMaxFinal(value.getFunc, value.min, value.max) if value.min or value.max
				@SetupLerpTables(value.getFunc)
				strName = '_NW_' .. value.getFunc
				funcLerp = 'Calculate' .. value.getFunc
				@__base['Get' .. value.getFunc] = => @[funcLerp](@, @[strName])

	for key, value in pairs PPM2.PonyDataRegistry
		@NetworkVar(value.getFunc, value.read, value.write, value.default)

	new: (netID, ent) =>
		super()

		@m_upperManeModel = Entity(-1)
		@m_lowerManeModel = Entity(-1)
		@m_tailModel = Entity(-1)
		@m_socksModel = Entity(-1)
		@m_newSocksModel = Entity(-1)

		@recomputeTextures = true
		@isValid = true
		@removed = false
		@valid = true
		@NETWORKED = false
		@NETWORKED_PREDICT = false

		@[data.strName] = data.defFunc() for _, data in ipairs @@NW_Vars when data.defFunc

		if SERVER
			@netID = @@NW_NextObjectID
			@@NW_NextObjectID += 1
		else
			netID = -1 if netID == nil
			@netID = netID

		@@NW_Objects[@netID] = @

		if CLIENT
			for i = 1, 1024
				if not @@O_Slots[i]
					@slotID = i
					@@O_Slots[i] = @
					break

			error('no more free pony data edicts') if not @slotID

		@entID = isnumber(ent) and ent or ent\EntIndex()
		ent = Entity(ent) if isnumber(ent)

		return if not IsValid(ent)
		@ent = ent
		@modelCached = ent\GetModel() if IsValid(ent)
		@SetupEntity(ent)

	GetEntity: => @ent or NULL
	IsValid: => @isValid
	GetModel: => @modelCached
	EntIndex: => @entID
	ObjectSlot: => @slotID
	GetObjectSlot: => @slotID

	Clone: (target = @ent) =>
		copy = @@(nil, target)
		@ApplyDataToObject(copy)
		return copy

	SetupEntity: (ent) =>
		if @@REGISTRY[ent]
			return if @@REGISTRY[ent]\GetOwner() and IsValid(@@REGISTRY[ent]\GetOwner()) and @@REGISTRY[ent]\GetOwner() ~= @GetOwner()
			@@REGISTRY[ent]\Remove() if @@REGISTRY[ent].Remove and @@REGISTRY[ent] ~= @

		return unless IsValid(ent)

		if @@REGISTRY[ent\EntIndex()]
			return if @@REGISTRY[ent\EntIndex()]\GetOwner() and IsValid(@@REGISTRY[ent\EntIndex()]\GetOwner()) and @@REGISTRY[ent\EntIndex()]\GetOwner() ~= @GetOwner()
			@@REGISTRY[ent\EntIndex()]\Remove() if @@REGISTRY[ent\EntIndex()].Remove and @@REGISTRY[ent\EntIndex()] ~= @

		@ent = ent
		@done_setup = true
		ent\SetPonyData(@)
		@entTable = @ent\GetTable()

		@modelCached = ent\GetModel()
		ent\PPMBonesModifier()
		@flightController = PPM2.PonyflyController(@)
		@entID = ent\EntIndex()
		@lastLerpThink = RealTimeL()

		@ModelChanges(@modelCached, @modelCached)
		@Reset()

		timer.Simple(0, -> @GetRenderController()\CompileTextures() if @GetRenderController()) if CLIENT
		PPM2.DebugPrint('Ponydata ', @, ' was updated to use for ', @ent)
		@@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
		@@CheckTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task\GetDisableTask()]

	ModelChanges: (old = @ent\GetModel(), new = old) =>
		@modelCached = new

		@ent\SetNW2Bool('ppm2_fly', false)

		timer.Simple 0.5, ->
			return unless IsValid(@ent)
			@Reset()

	GenericDataChange: (state) =>
		hook.Run 'PPM2_PonyDataChanges', @ent, @, state

		if state\GetKey() == 'DisableTask'
			@@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
			@@CheckTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task\GetDisableTask()]

		@GetSizeController()\DataChanges(state) if @ent and @GetBodygroupController()
		@GetBodygroupController()\DataChanges(state) if @ent and @GetBodygroupController()

		@GetWeightController()\DataChanges(state) if @ent and @GetWeightController()

		if CLIENT and @ent
			@GetRenderController()\DataChanges(state) if @GetRenderController()

	ResetScale: =>
		if scale = @GetSizeController()
			scale\ResetScale()

	ModifyScale: =>
		if scale = @GetSizeController()
			scale\ModifyScale()

	GetIsEditor: => @is_editor_data or false
	SetIsEditor: (value = false) => @is_editor_data = value

	Reset: =>
		if scale = @GetSizeController()
			scale\Reset()
		if CLIENT
			@GetWeightController()\Reset() if @GetWeightController() and @GetWeightController().Reset
			@GetRenderController()\Reset() if @GetRenderController() and @GetRenderController().Reset
			@GetBodygroupController()\Reset() if @GetBodygroupController() and @GetBodygroupController().Reset

	GetHoofstepVolume: =>
		return 0.8 if @ShouldMuffleHoosteps()
		return 1

	ShouldMuffleHoosteps: =>
		return @GetSocksAsModel() or @GetSocksAsNewModel()

	PlayerRespawn: =>
		return if not IsValid(@ent)
		@entTable.__cachedIsPony = @ent\IsPony()
		if not @entTable.__cachedIsPony
			return if @alreadyCalledRespawn
			@alreadyCalledRespawn = true
			@alreadyCalledDeath = true
		else
			@alreadyCalledRespawn = false
			@alreadyCalledDeath = false
		@ApplyBodygroups(CLIENT, true)

		@ent\SetNW2Bool('ppm2_fly', false)

		@ent\PPMBonesModifier()

		if scale = @GetSizeController()
			scale\PlayerRespawn()

		if weight = @GetWeightController()
			weight\PlayerRespawn()

		if CLIENT
			@deathRagdollMerged = false
			@GetRenderController()\PlayerRespawn() if @GetRenderController()
			@GetBodygroupController()\MergeModels(@ent) if IsValid(@ent) and @GetBodygroupController().MergeModels

	PlayerDeath: =>
		return if not IsValid(@ent)

		if @ent.__ppmBonesModifiers
			@ent.__ppmBonesModifiers\Remove()

		@entTable.__cachedIsPony = @ent\IsPony()

		if not @entTable.__cachedIsPony
			return if @alreadyCalledDeath
			@alreadyCalledDeath = true
		else
			@alreadyCalledDeath = false

		@ent\SetNW2Bool('ppm2_fly', false)

		if scale = @GetSizeController()
			scale\PlayerDeath()

		if weight = @GetWeightController()
			weight\PlayerDeath()

		if CLIENT
			@DoRagdollMerge()
			@GetRenderController()\PlayerDeath() if @GetRenderController()

	DoRagdollMerge: =>
		return if @deathRagdollMerged
		bgController = @GetBodygroupController()
		rag = @ent\GetRagdollEntity()

		if not bgController.MergeModels
			@deathRagdollMerged = true
		elseif IsValid(rag)
			@deathRagdollMerged = true
			bgController\MergeModels(rag)

	ApplyBodygroups: (updateModels = CLIENT) => @GetBodygroupController()\ApplyBodygroups(updateModels) if @ent
	SetLocalChange: (state) => @GenericDataChange(state)
	NetworkDataChanges: (state) => @GenericDataChange(state)

	GetPonyRaceFlags: =>
		switch @GetRace()
			when PPM2.RACE_EARTH
				return 0
			when PPM2.RACE_PEGASUS
				return PPM2.RACE_HAS_WINGS
			when PPM2.RACE_UNICORN
				return PPM2.RACE_HAS_HORN
			when PPM2.RACE_ALICORN
				return PPM2.RACE_HAS_HORN + PPM2.RACE_HAS_WINGS

	SlowUpdate: =>
		@GetBodygroupController()\SlowUpdate() if @GetBodygroupController()
		@GetWeightController()\SlowUpdate() if @GetWeightController()
		if scale = @GetSizeController()
			scale\SlowUpdate()

		if SERVER and IsValid(@ent) and @ent\IsPlayer()
			arms = @ent\GetHands()

			if IsValid(arms)
				cond = @GetPonyRaceFlags()\band(PPM2.RACE_HAS_HORN) ~= 0 and @ent\GetInfoBool('ppm2_cl_vm_magic_hands', true) and PPM2.HAND_BODYGROUP_MAGIC or PPM2.HAND_BODYGROUP_HOOVES
				arms\SetBodygroup(PPM2.HAND_BODYGROUP_ID, cond)

	Think: =>
	RenderScreenspaceEffects: =>
		time = RealTimeL()
		delta = time - @lastLerpThink
		@lastLerpThink = time
		if @isValid and IsValid(@ent)
			for _, change in ipairs @TriggerLerpAll(delta * 5)
				state = PPM2.NetworkChangeState('_NW_' .. change[1], change[1], change[2] + @['_NW_' .. change[1]], @)
				state\SetCantApply(true)
				@GenericDataChange(state)

	GetFlightController: => @flightController

	GetRenderController: =>
		return if SERVER
		return @renderController if not @isValid
		if not @renderController or @modelCached ~= @modelRender
			@modelRender = @modelCached
			cls = PPM2.GetRenderController(@modelCached)
			if @renderController and cls == @renderController.__class
				@renderController.ent = @ent
				PPM2.DebugPrint('Skipping render controller recreation for ', @ent, ' as part of ', @)
				return @renderController
			@renderController\Remove() if @renderController
			@renderController = cls(@)
		@renderController.ent = @ent
		return @renderController

	GetWeightController: =>
		return @weightController if not @isValid
		if not @weightController or @modelCached ~= @modelWeight
			@modelCached = @modelCached or @ent\GetModel()
			@modelWeight = @modelCached
			cls = PPM2.GetPonyWeightController(@modelCached)
			if @weightController and cls == @weightController.__class
				@weightController.ent = @ent
				PPM2.DebugPrint('Skipping weight controller recreation for ', @ent, ' as part of ', @)
				return @weightController
			@weightController\Remove() if @weightController
			@weightController = cls(@)
		@weightController.ent = @ent
		return @weightController

	GetSizeController: =>
		return @scaleController if not @isValid
		if not @scaleController or @modelCached ~= @modelScale
			@modelCached = @modelCached or @ent\GetModel()
			@modelScale = @modelCached
			cls = PPM2.GetSizeController(@modelCached)
			if @scaleController and cls == @scaleController.__class
				@scaleController.ent = @ent
				PPM2.DebugPrint('Skipping size controller recreation for ', @ent, ' as part of ', @)
				return @scaleController
			@scaleController\Remove() if @scaleController
			@scaleController = cls(@)
		@scaleController.ent = @ent
		return @scaleController
	GetScaleController: => @GetSizeController()

	GetBodygroupController: =>
		return @bodygroups if not @isValid
		if not @bodygroups or @modelBodygroups ~= @modelCached
			@modelCached = @modelCached or @ent\GetModel()
			@modelBodygroups = @modelCached
			cls = PPM2.GetBodygroupController(@modelCached)
			if @bodygroups and cls == @bodygroups.__class
				@bodygroups.ent = @ent
				PPM2.DebugPrint('Skipping bodygroup controller recreation for ', @ent, ' as part of ', @)
				return @bodygroups
			@bodygroups\Remove() if @bodygroups
			@bodygroups = cls(@)
		@bodygroups.ent = @ent
		return @bodygroups

	Remove: (byClient = false) =>
		@removed = true
		@@NW_Objects[@netID] = nil if SERVER or @NETWORKED
		@@O_Slots[@slotID] = nil if @slotID
		@isValid = false
		@ent = @GetEntity() if not IsValid(@ent)
		@@REGISTRY[@ent] = nil if IsValid(@ent) and @@REGISTRY[@ent] == @
		@@REGISTRY[@entID] = nil if @@REGISTRY[@entID] == @
		@GetWeightController()\Remove() if @GetWeightController()

		if CLIENT
			@GetRenderController()\Remove() if @GetRenderController()
			if IsValid(@ent) and @ent.__ppm2_task_hit
				@entTable.__ppm2_task_hit = false
				@ent\SetNoDraw(false)

		@GetBodygroupController()\Remove() if @GetBodygroupController()
		@GetSizeController()\Remove() if @GetSizeController()

		@flightController\Switch(false) if @flightController
		@@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
		@@CheckTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task\GetDisableTask()]

	__tostring: => "[#{@@__name}:#{@netID}|#{@ent}]"

	GetOwner: => @ent
	IsNetworked: => @NETWORKED
	IsGoingToNetwork: => @NETWORKED_PREDICT
	SetIsGoingToNetwork: (val = @NETWORKED) => @NETWORKED_PREDICT = val
	IsLocal: => @isLocal
	IsLocalObject: => @isLocal
	GetNetworkID: => @netID
	NetworkID: => @netID
	NetID: => @netID

	ComputeMagicColor: =>
		color = @GetHornMagicColor()

		if not @GetSeparateMagicColor()
			if not @GetSeparateEyes()
				color = @GetEyeIrisTop()\Lerp(0.5, @GetEyeIrisBottom())
			else
				lerpLeft = @GetEyeIrisTopLeft()\Lerp(0.5, @GetEyeIrisBottomLeft())
				lerpRight = @GetEyeIrisTopRight()\Lerp(0.5, @GetEyeIrisBottomRight())
				color = lerpLeft\Lerp(0.5, lerpRight)

		return color

	ReadNetworkData: (len = 24, ply = NULL, silent = false, applyEntities = true) =>
		data = @@ReadNetworkData()
		validPly = IsValid(ply)
		states = [PPM2.NetworkChangeState(key, keyValid, newVal, @, len, ply) for key, {keyValid, newVal} in pairs data]
		for _, state in ipairs states
			if not validPly or applyEntities or not isentity(state\GetValue())
				state\Apply()
				@NetworkDataChanges(state) unless silent

	NetworkedIterable: (grabEntities = true) =>
		data = [{getName, @[strName]} for _, {:strName, :getName} in ipairs @@NW_Vars when grabEntities or not isentity(@[strName])]
		return data

	ApplyDataToObject: (target, applyEntities = false) =>
		target["Set#{key}"](target, value) for {key, value} in *@NetworkedIterable(applyEntities) when target["Set#{key}"]
		return target

	WriteNetworkData: => writeFunc(@[strName]) for _, {:strName, :writeFunc} in ipairs @@NW_Vars

	ReBroadcast: =>
		return false if not @NETWORKED
		return false if CLIENT
		net.Start(@@NW_Broadcast)
		net.WriteUInt16(@netID)
		@WriteNetworkData()
		net.Broadcast()
		return true

	Create: =>
		return if @NETWORKED
		return if CLIENT and @CREATED_BY_SERVER -- wtf
		@NETWORKED = true if SERVER
		@NETWORKED_PREDICT = true

		if SERVER
			net.Start(@@NW_Create)
			net.WriteUInt16(@netID)
			net.WriteEntity(@ent)
			@WriteNetworkData()
			filter = RecipientFilter()
			filter\AddAllPlayers()
			filter\RemovePlayer(@ent) if IsValid(@ent) and @ent\IsPlayer()
			net.Send(filter)
		else
			@@NW_WaitID += 1
			@waitID = @@NW_WaitID

			net.Start(@@NW_Create)
			before = net.BytesWritten()

			net.WriteUInt16(@waitID)
			@WriteNetworkData()
			after = net.BytesWritten()

			net.SendToServer()
			@@NW_Waiting[@waitID] = @
			return after - before

	NetworkTo: (targets = {}) =>
		net.Start(@@NW_Create)
		net.WriteUInt16(@netID)
		net.WriteEntity(@ent)
		@WriteNetworkData()
		net.Send(targets)

PPM2.NetworkedPonyData = NetworkedPonyData

if CLIENT
	net.Receive 'PPM2.NotifyDisconnect', ->
		netID = net.ReadUInt16()
		data = NetworkedPonyData.NW_Objects[netID]
		return if not data
		data\Remove()

	net.Receive 'PPM2.PonyDataRemove', ->
		netID = net.ReadUInt16()
		data = NetworkedPonyData.NW_Objects[netID]
		return if not data
		data\Remove()
else
	hook.Add 'PlayerJoinTeam', 'PPM2.TeamWaypoint', (ply, new) ->
		ply.__ppm2_modified_jump = false
	hook.Add 'OnPlayerChangedTeam', 'PPM2.TeamWaypoint', (ply, old, new) ->
		ply.__ppm2_modified_jump = false

_G.LocalPonyData = () -> LocalPlayer()\GetPonyData()
_G.LocalPonydata = () -> LocalPlayer()\GetPonyData()

do
	REGISTRY = NetworkedPonyData.REGISTRY

	nullify = ->
		for _, ent in ipairs ents.GetAll()
			if ent.__PPM2_PonyData and ent.__PPM2_PonyData.Remove
				ProtectedCall -> ent.__PPM2_PonyData\Remove()

		for _, ent in ipairs ents.GetAll()
			ent.__PPM2_PonyData = nil

		for key, obj in pairs(REGISTRY)
			if obj.Remove
				ProtectedCall -> obj\Remove()

			REGISTRY[key] = nil

	nullify()

	entMeta = FindMetaTable('Entity')
	EntIndex = entMeta.EntIndex

	entMeta.GetPonyData = =>
		index = EntIndex(@)
		return REGISTRY[index] if index > 0
		return REGISTRY[@]

	entMeta.SetPonyData = (data) =>
		index = EntIndex(@)

		if index > 0
			REGISTRY[index] = data
			return

		REGISTRY[@] = data


Update db keys for broker minimap icon

Update db keys for broker minimap icon


Change pidfile

Change pidfile