No midza

No midza

lapis = require "lapis"
config = (require "lapis.config").get!
content = require "content"

import after_dispatch from require "lapis.nginx.context"
import to_json from require "lapis.util"
import capture_errors, assert_error, yield_error from require "lapis.application"

class CSWeek extends lapis.Application
    layout: require "views.layout"

    error_handler: (context, code) =>
        code = code or 500
        context = context or self

        with context
            .m, err = content\get "error"
            if err then
                print "Could not load the error page (code #{code}): #{err}"
                .m = { }

            .m[code] = .m[code] or .m.default or { }
            .page_id = "error"
            .status = code
            .m.title = .m[code].title

        render: "error", status: code

    safe_route: (fn) =>
        app = self\app
        capture_errors {
            on_error: =>
                @app\error_handler self

            =>
                fn app
        }

    try_render: (template, context) =>
        context = context or self
        with context
            .nav = assert_error content\get "nav"
            .footer = assert_error content\get "footer"
        render: template

    @before_filter =>
        if #[n for n in *{'production-perftest', 'development-perftest'} when n == config._name] > 0
            after_dispatch ->
                print to_json(ngx.ctx.performance)

    "/": =>
        @app\safe_route ->
            @page_id = "home"
            @m = assert_error content\get "home"
            print @m.title
            @lecturers = assert_error content\get "lecturers"
            @app\try_render "home", self

    handle_404: =>
        @errors = "Route `#{self.req.parsed_url.path or 'unknown'}` not found"
        @app\error_handler self, 404

lapis = require "lapis"
config = (require "lapis.config").get!
content = require "content"

import after_dispatch from require "lapis.nginx.context"
import to_json from require "lapis.util"
import capture_errors, assert_error, yield_error from require "lapis.application"

class CSWeek extends lapis.Application
    layout: require "views.layout"

    error_handler: (context, code) =>
        code = code or 500
        context = context or self

        with context
            .m, err = content\get "error"
            if err then
                print "Could not load the error page (code #{code}): #{err}"
                .m = { }

            .m[code] = .m[code] or .m.default or { }
            .page_id = "error"
            .status = code
            .m.title = .m[code].title

        render: "error", status: code

    safe_route: (fn) =>
        app = self\app
        capture_errors {
            on_error: =>
                @app\error_handler self

            =>
                fn app
        }

    try_render: (template, context) =>
        context = context or self
        with context
            .nav = assert_error content\get "nav"
            .footer = assert_error content\get "footer"
        render: template

    @before_filter =>
        if #[n for n in *{'production-perftest', 'development-perftest'} when n == config._name] > 0
            after_dispatch ->
                print to_json(ngx.ctx.performance)

    "/": =>
        @app\safe_route ->
            @page_id = "home"
            @m = assert_error content\get "home"
            print @m.title
            @lecturers = assert_error content\get "lecturers"
            @app\try_render "home", self

    "/midza": =>
        @app\safe_route ->
            @html ->
                p "dobar chkode"

    handle_404: =>
        @errors = "Route `#{self.req.parsed_url.path or 'unknown'}` not found"
        @app\error_handler self, 404


Set color once in the Matrix:draw() call.

colorize = require "lib.colorize"
fonts = require "resources.fonts"
love = love

class Matrix
  update_time: 0.03
  cur_time: 0
  matrix: {}
  cols: 370
  rows: 40
  overall_time: 0
  letters: { "a", "z", "0", "A", "?", "!"}

  randletter: =>
    return @letters[math.random(1, #@letters)]

  newrow: (row) =>
    ret = {}
    if not row
      for i = 1, @cols
        ret[i] = (math.random! > 0.999) and @randletter() or " "
    else
      for i = 1, @cols
        if row[i] ~= " "
          ret[i] = (math.random! > 0.3) and @randletter() or " "
        else
          ret[i] = (math.random! > 0.99) and @randletter() or " "

    return ret

  update: (dt) =>
    love.graphics.setFont fonts.art
    @overall_time += dt
    -- if @overall_time > 2
    --   @stop!
    @cur_time += dt
    if @cur_time < @update_time
      return
    @cur_time = @cur_time - @update_time
    if #@matrix == @rows
      table.remove @matrix, @rows
    if not @stopped
      table.insert @matrix, 1, @\newrow @matrix[1]
    else
      table.insert @matrix, 1, {}

  draw: =>
    yoffset = 0
    colorize {0, 0, 0, @alpha}, -> love.graphics.rectangle "fill", 0, 0,
                                   love.graphics.getWidth!,
                                   love.graphics.getHeight!
    colorize {20, 80, 20, @alpha}, ->
      for _, row in ipairs(@matrix)
        love.graphics.print(table.concat(row), 0, yoffset)
        yoffset += 15

  setAlpha: (alpha) =>
    @alpha = alpha

  stop: =>
    @stopped = true

  start: =>
    @stopped = false

colorize = require "lib.colorize"
fonts = require "resources.fonts"
love = love

class Matrix
  update_time: 0.03
  cur_time: 0
  matrix: {}
  cols: 370
  rows: 40
  overall_time: 0
  letters: { "a", "z", "0", "A", "?", "!"}

  randletter: =>
    return @letters[math.random(1, #@letters)]

  newrow: (row) =>
    ret = {}
    if not row
      for i = 1, @cols
        ret[i] = (math.random! > 0.999) and @randletter() or " "
    else
      for i = 1, @cols
        if row[i] ~= " "
          ret[i] = (math.random! > 0.3) and @randletter() or " "
        else
          ret[i] = (math.random! > 0.99) and @randletter() or " "

    return ret

  update: (dt) =>
    love.graphics.setFont fonts.art
    @overall_time += dt
    -- if @overall_time > 2
    --   @stop!
    @cur_time += dt
    if @cur_time < @update_time
      return
    @cur_time = @cur_time - @update_time
    if #@matrix == @rows
      table.remove @matrix, @rows
    if not @stopped
      table.insert @matrix, 1, @\newrow @matrix[1]
    else
      table.insert @matrix, 1, {}

  draw: =>
    yoffset = 0
    colorize {0, 0, 0, @alpha}, -> love.graphics.rectangle "fill", 0, 0,
                                   love.graphics.getWidth!,
                                   love.graphics.getHeight!
    for _, row in ipairs(@matrix)
      colorize {20, 80, 20, @alpha}, -> love.graphics.print(table.concat(row), 0, yoffset)
      yoffset += 15

  setAlpha: (alpha) =>
    @alpha = alpha

  stop: =>
    @stopped = true

  start: =>
    @stopped = false


Use Color:Lerp()

Use Color:Lerp()

--
-- Copyright (C) 2017-2018 DBot

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

import FrameNumberL, RealTimeL, StrongEntity, PPM2 from _G
import ALTERNATIVE_RENDER from PPM2
import GetPonyData, IsDormant, PPMBonesModifier, IsPony from FindMetaTable('Entity')

RENDER_HORN_GLOW = CreateConVar('ppm2_horn_glow', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn glow when player uses physgun')
HORN_PARTICLES = CreateConVar('ppm2_horn_particles', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn particles when player uses physgun')
HORN_FP = CreateConVar('ppm2_horn_firstperson', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn effetcs in first person')
HORN_HIDE_BEAM = CreateConVar('ppm2_horn_nobeam', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Hide physgun beam')
TASK_RENDER_TYPE = CreateConVar('ppm2_task_render_type', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Task rendering type (e.g. pony ragdolls and NPCs). 1 - better render; less conflicts; more FPS. 0 - "old-style" render; possible conflicts;')
DRAW_LEGS_DEPTH = CreateConVar('ppm2_render_legsdepth', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Render legs in depth pass. Useful with Boken DoF enabled')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')
SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_cl_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')
SV_SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_sv_draw_hands', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Should draw hooves as viewmodel')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS
	return unless IsValid(arms)
	return unless ply.__cachedIsPony
	return true unless SV_SHOULD_DRAW_VIEWMODEL\GetBool()
	return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
	return unless ply\Alive()
	arms\SetPos(ply\EyePos() + Vector(0, 0, 100))
	wep = ply\GetActiveWeapon()
	if IsValid(wep) and wep.UseHands == false
		return true -- Dafuck?
	return if arms\GetModel() ~= 'models/cppm/pony_arms.mdl'
	data = ply\GetPonyData()
	return unless data
	status = data\GetRenderController()\PreDrawArms(arms)
	return status if status ~= nil
	arms.__ppm2_draw = true

hook.Add 'PostDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS
	return unless IsValid(arms)
	return unless arms.__ppm2_draw
	data = ply\GetPonyData()
	return unless data
	data\GetRenderController()\PostDrawArms(arms)
	arms.__ppm2_draw = false

mat_dxlevel = GetConVar('mat_dxlevel')

timer.Create 'PPM2.CheckDXLevel', 180, 0, ->
	if mat_dxlevel\GetInt() > 90
		timer.Remove 'PPM2.CheckDXLevel'
		return

	PPM2.Message('Direct3D Level is LESS THAN 9.1! This will not work!')

IN_DRAW = false
MARKED_FOR_DRAW = {}

PPM2.PreDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
	return if IN_DRAW or PPM2.__RENDERING_REFLECTIONS

	MARKED_FOR_DRAW = {}

	if not ALTERNATIVE_RENDER\GetBool()
		for _, ply in ipairs player.GetAll()
			if not IsDormant(ply)
				p = IsPony(ply)
				ply.__cachedIsPony = p
				if p
					data = GetPonyData(ply)
					if data
						renderController = data\GetRenderController()
						if renderController
							renderController\PreDraw()
							table.insert(MARKED_FOR_DRAW, renderController)

	if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegsDepth()
					IN_DRAW = false

	return if bDrawingDepth or bDrawingSkybox

	if not LEGS_RENDER_TYPE\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegs()
					IN_DRAW = false

PPM2.PostDrawTranslucentRenderables = (bDrawingDepth, bDrawingSkybox) ->
	if not ALTERNATIVE_RENDER\GetBool() and not bDrawingDepth and not bDrawingSkybox
		for _, draw in ipairs MARKED_FOR_DRAW
			draw\PostDraw()

PPM2.PostDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
	return if IN_DRAW or PPM2.__RENDERING_REFLECTIONS

	if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegsDepth()
					IN_DRAW = false

	return if bDrawingDepth or bDrawingSkybox

	if not TASK_RENDER_TYPE\GetBool()
		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			ent = task.ent
			if IsValid(ent)
				if ent.__cachedIsPony
					ent\SetNoDraw(true)
					ent.__ppm2_task_hit = true
					renderController = task\GetRenderController()
					renderController\PreDraw(ent)
					IN_DRAW = true
					ent\DrawModel()
					IN_DRAW = false
					renderController\PostDraw(ent)
				else
					if ent.__ppm2_task_hit
						ent.__ppm2_task_hit = false
						ent\SetNoDraw(false)
						task\Reset()

	if not ENABLE_NEW_RAGDOLLS\GetBool()
		for _, ply in ipairs player.GetAll()
			alive = ply\Alive()
			ply.__ppm2_last_dead = RealTimeL() + 2 if not alive
			if ply.__cachedIsPony
				if ply\GetPonyData() and not alive
					data = ply\GetPonyData()
					rag = ply\GetRagdollEntity()
					if IsValid(rag)
						renderController = data\GetRenderController()
						data\DoRagdollMerge()
						if renderController
							renderController\PreDraw(rag)
							IN_DRAW = true
							rag\DrawModel()
							IN_DRAW = false
							renderController\PostDraw(rag)

	if LEGS_RENDER_TYPE\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegs()
					IN_DRAW = false

Think = ->
	if TASK_RENDER_TYPE\GetBool()
		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			ent = task.ent
			if IsValid(ent) and ent.__cachedIsPony
				if ent.__ppm2_task_hit
					ent.__ppm2_task_hit = false
					ent\SetNoDraw(false)

				if not ent.__ppm2RenderOverride
					ent = ent\GetEntity()
					ent.__ppm2_oldRenderOverride = ent.RenderOverride
					ent.__ppm2RenderOverride = ->
						renderController = task\GetRenderController()
						renderController\PreDraw(ent, true)
						ent\DrawModel()
						renderController\PostDraw(ent, true)
						ent.__ppm2_oldRenderOverride(ent) if ent.__ppm2_oldRenderOverride
					ent.RenderOverride = ent.__ppm2RenderOverride

PPM2.PrePlayerDraw = =>
	return if PPM2.__RENDERING_REFLECTIONS
	if ALTERNATIVE_RENDER\GetBool()
		with data = GetPonyData(@)
			return if not data
			@__cachedIsPony = IsPony(@)
			return if not @__cachedIsPony
			f = FrameNumberL()
			return if @__ppm2_last_draw == f
			@__ppm2_last_draw = f
			@__ppm2_last_dead = @__ppm2_last_dead or 0
			return if @__ppm2_last_dead > RealTimeL()
			bones = PPMBonesModifier(@)
			if data and bones\CanThink()
				@ResetBoneManipCache()
				bones\ResetBones()
				hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
				bones\Think()
				@ApplyBoneManipulations()
				@_ppmBonesModified = true
			renderController = data\GetRenderController()
			status = renderController\PreDraw() if renderController
	else
		with data = GetPonyData(@)
			return if not data
			@__cachedIsPony = IsPony(@)
			return if not @__cachedIsPony
			f = FrameNumberL()
			return if @__ppm2_last_draw == f
			@__ppm2_last_draw = f
			bones = PPMBonesModifier(@)
			if data and bones\CanThink()
				@ResetBoneManipCache()
				bones\ResetBones()
				hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
				bones\Think()
				@ApplyBoneManipulations()
				@_ppmBonesModified = true

PPM2.PostPlayerDraw = =>
	return if not ALTERNATIVE_RENDER\GetBool() or PPM2.__RENDERING_REFLECTIONS
	with data = GetPonyData(@)
		return if not data or not @__cachedIsPony
		renderController = data\GetRenderController()
		renderController\PostDraw() if renderController

do
	hornGlowStatus = {}
	smokeMaterial = 'ppm2/hornsmoke'
	fireMat = 'particle/fire'
	hornShift = Vector(1, 0.15, 14.5)

	hook.Add 'PreDrawHalos', 'PPM2.HornEffects', =>
		return if not HORN_HIDE_BEAM\GetBool()
		frame = FrameNumberL()
		cTime = (RealTimeL() % 20) * 4
		for ent, status in pairs hornGlowStatus
			if IsValid(ent) and status.frame == frame and IsValid(status.target)
				additional = math.sin(cTime / 2 + status.haloSeed * 3) * 40
				newCol = DLib.AddColor(status.color, Color(additional, additional, additional))
				halo.Add({status.target}, newCol, math.sin(cTime + status.haloSeed) * 4 + 8, math.cos(cTime + status.haloSeed) * 4 + 8, 2)

	hook.Add 'Think', 'PPM2.HornEffects', =>
		frame = FrameNumberL()
		for ent, status in pairs hornGlowStatus
			if not IsValid(ent)
				status.emmiter\Finish() if IsValid(status.emmiter)
				status.emmiterProp\Finish() if IsValid(status.emmiterProp)
				hornGlowStatus[ent] = nil
			elseif status.frame ~= frame
				status.data\SetHornGlow(status.prevStatus)
				status.emmiter\Finish() if IsValid(status.emmiter)
				status.emmiterProp\Finish() if IsValid(status.emmiterProp)
				hornGlowStatus[ent] = nil
			else
				if not status.prevStatus and RENDER_HORN_GLOW\GetBool() and status.data\GetHornGlow() ~= status.isEnabled
					status.data\SetHornGlow(status.isEnabled)
				if status.attach and IsValid(status.target)
					grabHornPos = Vector(hornShift) * status.data\GetPonySize()
					{:Pos, :Ang} = ent\GetAttachment(status.attach)
					grabHornPos\Rotate(Ang)
					if status.isEnabled and IsValid(status.emmiter) and status.nextSmokeParticle < RealTimeL()
						status.nextSmokeParticle = RealTimeL() + math.Rand(0.1, 0.2)
						for i = 1, math.random(1, 4)
							vec = VectorRand()
							calcPos = Pos + grabHornPos + vec
							with particle = status.emmiter\Add(smokeMaterial, calcPos)
								\SetRollDelta(math.rad(math.random(0, 360)))
								\SetPos(calcPos)
								life = math.Rand(0.6, 0.9)
								\SetStartAlpha(math.random(80, 170))
								\SetDieTime(life)
								\SetColor(status.color.r, status.color.g, status.color.b)
								\SetEndAlpha(0)
								size = math.Rand(2, 3)
								\SetEndSize(math.Rand(2, size))
								\SetStartSize(size)
								\SetGravity(Vector())
								\SetAirResistance(10)
								vecRand = VectorRand()
								vecRand.z *= 2
								\SetVelocity(ent\GetVelocity() + vecRand * status.data\GetPonySize() * 2)
								\SetCollide(false)
					if status.isEnabled and IsValid(status.emmiterProp) and status.nextGrabParticle < RealTimeL() and status.mins and status.maxs
						status.nextGrabParticle = RealTimeL() + math.Rand(0.05, 0.3)
						status.emmiterProp\SetPos(status.tpos)
						for i = 1, math.random(2, 6)
							calcPos = Vector(math.Rand(status.mins.x, status.maxs.x), math.Rand(status.mins.y, status.maxs.y), math.Rand(status.mins.z, status.maxs.z))
							with particle = status.emmiterProp\Add(fireMat, calcPos)
								\SetRollDelta(math.rad(math.random(0, 360)))
								\SetPos(calcPos)
								life = math.Rand(0.5, 0.9)
								\SetStartAlpha(math.random(130, 230))
								\SetDieTime(life)
								\SetColor(status.color.r, status.color.g, status.color.b)
								\SetEndAlpha(0)
								\SetEndSize(0)
								\SetStartSize(math.Rand(2, 6))
								\SetGravity(Vector())
								\SetAirResistance(15)
								\SetVelocity(VectorRand() * 6)
								\SetCollide(false)

	hook.Add 'DrawPhysgunBeam', 'PPM2.HornEffects', (physgun = NULL, isEnabled = false, target = NULL, bone = 0, hitPos = Vector()) =>
		return if not @IsPony() or not HORN_FP\GetBool() and @ == LocalPlayer() and not @ShouldDrawLocalPlayer()
		data = @GetPonyData()
		return if not data
		return if data\GetRace() ~= PPM2.RACE_UNICORN and data\GetRace() ~= PPM2.RACE_ALICORN
		if not hornGlowStatus[@]
			hornGlowStatus[@] = {
				frame: FrameNumberL()
				prevStatus: data\GetHornGlow()
				:data, :isEnabled, :hitPos, :target, :bone
				tpos: @GetPos()
				attach: @LookupAttachment('eyes')
				nextSmokeParticle: 0
				nextGrabParticle: 0
			}

			with hornGlowStatus[@]
				if HORN_PARTICLES\GetBool()
					.emmiter = ParticleEmitter(EyePos())
					.emmiterProp = ParticleEmitter(EyePos())

				.color = data\GetHornMagicColor()
				.haloSeed = math.rad(math.random(-1000, 1000))

				if not data\GetSeparateMagicColor()
					if not data\GetSeparateEyes()
						.color = data\GetEyeIrisTop()\Lerp(0.5, data\GetEyeIrisBottom())
					else
						lerpLeft = data\GetEyeIrisTopLeft()\Lerp(0.5, data\GetEyeIrisBottomLeft())
						lerpRight = data\GetEyeIrisTopRight()\Lerp(0.5, data\GetEyeIrisBottomRight())
						.color = lerpLeft\Lerp(0.5, lerpRight)
		else
			with hornGlowStatus[@]
				.frame = FrameNumberL()
				.isEnabled = isEnabled
				.target = target
				.bone = bone
				.hitPos = hitPos
				if IsValid(target)
					.tpos = target\GetPos() + hitPos
					center = target\WorldSpaceCenter()
					.center = center
					mins, maxs = target\WorldSpaceAABB()
					.mins = center + (mins - center) * 1.2
					.maxs = center + (maxs - center) * 1.2
		return false if HORN_HIDE_BEAM\GetBool() and IsValid(target)

hook.Add 'PrePlayerDraw', 'PPM2.PlayerDraw', PPM2.PrePlayerDraw, -2
hook.Add 'PostPlayerDraw', 'PPM2.PostPlayerDraw', PPM2.PostPlayerDraw, -2
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.PostDrawOpaqueRenderables', PPM2.PostDrawOpaqueRenderables, -2
hook.Add 'Think', 'PPM2.UpdateRenderTasks', Think, -2
hook.Add 'PreDrawOpaqueRenderables', 'PPM2.PreDrawOpaqueRenderables', PPM2.PreDrawOpaqueRenderables, -2
hook.Add 'PostDrawTranslucentRenderables', 'PPM2.PostDrawTranslucentRenderables', PPM2.PostDrawTranslucentRenderables, -2

--
-- Copyright (C) 2017-2018 DBot

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

import FrameNumberL, RealTimeL, StrongEntity, PPM2 from _G
import ALTERNATIVE_RENDER from PPM2
import GetPonyData, IsDormant, PPMBonesModifier, IsPony from FindMetaTable('Entity')

RENDER_HORN_GLOW = CreateConVar('ppm2_horn_glow', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn glow when player uses physgun')
HORN_PARTICLES = CreateConVar('ppm2_horn_particles', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn particles when player uses physgun')
HORN_FP = CreateConVar('ppm2_horn_firstperson', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn effetcs in first person')
HORN_HIDE_BEAM = CreateConVar('ppm2_horn_nobeam', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Hide physgun beam')
TASK_RENDER_TYPE = CreateConVar('ppm2_task_render_type', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Task rendering type (e.g. pony ragdolls and NPCs). 1 - better render; less conflicts; more FPS. 0 - "old-style" render; possible conflicts;')
DRAW_LEGS_DEPTH = CreateConVar('ppm2_render_legsdepth', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Render legs in depth pass. Useful with Boken DoF enabled')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')
SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_cl_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')
SV_SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_sv_draw_hands', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Should draw hooves as viewmodel')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS
	return unless IsValid(arms)
	return unless ply.__cachedIsPony
	return true unless SV_SHOULD_DRAW_VIEWMODEL\GetBool()
	return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
	return unless ply\Alive()
	arms\SetPos(ply\EyePos() + Vector(0, 0, 100))
	wep = ply\GetActiveWeapon()
	if IsValid(wep) and wep.UseHands == false
		return true -- Dafuck?
	return if arms\GetModel() ~= 'models/cppm/pony_arms.mdl'
	data = ply\GetPonyData()
	return unless data
	status = data\GetRenderController()\PreDrawArms(arms)
	return status if status ~= nil
	arms.__ppm2_draw = true

hook.Add 'PostDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS
	return unless IsValid(arms)
	return unless arms.__ppm2_draw
	data = ply\GetPonyData()
	return unless data
	data\GetRenderController()\PostDrawArms(arms)
	arms.__ppm2_draw = false

mat_dxlevel = GetConVar('mat_dxlevel')

timer.Create 'PPM2.CheckDXLevel', 180, 0, ->
	if mat_dxlevel\GetInt() > 90
		timer.Remove 'PPM2.CheckDXLevel'
		return

	PPM2.Message('Direct3D Level is LESS THAN 9.1! This will not work!')

IN_DRAW = false
MARKED_FOR_DRAW = {}

PPM2.PreDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
	return if IN_DRAW or PPM2.__RENDERING_REFLECTIONS

	MARKED_FOR_DRAW = {}

	if not ALTERNATIVE_RENDER\GetBool()
		for _, ply in ipairs player.GetAll()
			if not IsDormant(ply)
				p = IsPony(ply)
				ply.__cachedIsPony = p
				if p
					data = GetPonyData(ply)
					if data
						renderController = data\GetRenderController()
						if renderController
							renderController\PreDraw()
							table.insert(MARKED_FOR_DRAW, renderController)

	if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegsDepth()
					IN_DRAW = false

	return if bDrawingDepth or bDrawingSkybox

	if not LEGS_RENDER_TYPE\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegs()
					IN_DRAW = false

PPM2.PostDrawTranslucentRenderables = (bDrawingDepth, bDrawingSkybox) ->
	if not ALTERNATIVE_RENDER\GetBool() and not bDrawingDepth and not bDrawingSkybox
		for _, draw in ipairs MARKED_FOR_DRAW
			draw\PostDraw()

PPM2.PostDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
	return if IN_DRAW or PPM2.__RENDERING_REFLECTIONS

	if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegsDepth()
					IN_DRAW = false

	return if bDrawingDepth or bDrawingSkybox

	if not TASK_RENDER_TYPE\GetBool()
		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			ent = task.ent
			if IsValid(ent)
				if ent.__cachedIsPony
					ent\SetNoDraw(true)
					ent.__ppm2_task_hit = true
					renderController = task\GetRenderController()
					renderController\PreDraw(ent)
					IN_DRAW = true
					ent\DrawModel()
					IN_DRAW = false
					renderController\PostDraw(ent)
				else
					if ent.__ppm2_task_hit
						ent.__ppm2_task_hit = false
						ent\SetNoDraw(false)
						task\Reset()

	if not ENABLE_NEW_RAGDOLLS\GetBool()
		for _, ply in ipairs player.GetAll()
			alive = ply\Alive()
			ply.__ppm2_last_dead = RealTimeL() + 2 if not alive
			if ply.__cachedIsPony
				if ply\GetPonyData() and not alive
					data = ply\GetPonyData()
					rag = ply\GetRagdollEntity()
					if IsValid(rag)
						renderController = data\GetRenderController()
						data\DoRagdollMerge()
						if renderController
							renderController\PreDraw(rag)
							IN_DRAW = true
							rag\DrawModel()
							IN_DRAW = false
							renderController\PostDraw(rag)

	if LEGS_RENDER_TYPE\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegs()
					IN_DRAW = false

Think = ->
	if TASK_RENDER_TYPE\GetBool()
		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			ent = task.ent
			if IsValid(ent) and ent.__cachedIsPony
				if ent.__ppm2_task_hit
					ent.__ppm2_task_hit = false
					ent\SetNoDraw(false)

				if not ent.__ppm2RenderOverride
					ent = ent\GetEntity()
					ent.__ppm2_oldRenderOverride = ent.RenderOverride
					ent.__ppm2RenderOverride = ->
						renderController = task\GetRenderController()
						renderController\PreDraw(ent, true)
						ent\DrawModel()
						renderController\PostDraw(ent, true)
						ent.__ppm2_oldRenderOverride(ent) if ent.__ppm2_oldRenderOverride
					ent.RenderOverride = ent.__ppm2RenderOverride

PPM2.PrePlayerDraw = =>
	return if PPM2.__RENDERING_REFLECTIONS
	if ALTERNATIVE_RENDER\GetBool()
		with data = GetPonyData(@)
			return if not data
			@__cachedIsPony = IsPony(@)
			return if not @__cachedIsPony
			f = FrameNumberL()
			return if @__ppm2_last_draw == f
			@__ppm2_last_draw = f
			@__ppm2_last_dead = @__ppm2_last_dead or 0
			return if @__ppm2_last_dead > RealTimeL()
			bones = PPMBonesModifier(@)
			if data and bones\CanThink()
				@ResetBoneManipCache()
				bones\ResetBones()
				hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
				bones\Think()
				@ApplyBoneManipulations()
				@_ppmBonesModified = true
			renderController = data\GetRenderController()
			status = renderController\PreDraw() if renderController
	else
		with data = GetPonyData(@)
			return if not data
			@__cachedIsPony = IsPony(@)
			return if not @__cachedIsPony
			f = FrameNumberL()
			return if @__ppm2_last_draw == f
			@__ppm2_last_draw = f
			bones = PPMBonesModifier(@)
			if data and bones\CanThink()
				@ResetBoneManipCache()
				bones\ResetBones()
				hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
				bones\Think()
				@ApplyBoneManipulations()
				@_ppmBonesModified = true

PPM2.PostPlayerDraw = =>
	return if not ALTERNATIVE_RENDER\GetBool() or PPM2.__RENDERING_REFLECTIONS
	with data = GetPonyData(@)
		return if not data or not @__cachedIsPony
		renderController = data\GetRenderController()
		renderController\PostDraw() if renderController

do
	hornGlowStatus = {}
	smokeMaterial = 'ppm2/hornsmoke'
	fireMat = 'particle/fire'
	hornShift = Vector(1, 0.15, 14.5)

	hook.Add 'PreDrawHalos', 'PPM2.HornEffects', =>
		return if not HORN_HIDE_BEAM\GetBool()
		frame = FrameNumberL()
		cTime = (RealTimeL() % 20) * 4
		for ent, status in pairs hornGlowStatus
			if IsValid(ent) and status.frame == frame and IsValid(status.target)
				additional = math.sin(cTime / 2 + status.haloSeed * 3) * 40
				newCol = DLib.AddColor(status.color, Color(additional, additional, additional))
				halo.Add({status.target}, newCol, math.sin(cTime + status.haloSeed) * 4 + 8, math.cos(cTime + status.haloSeed) * 4 + 8, 2)

	hook.Add 'Think', 'PPM2.HornEffects', =>
		frame = FrameNumberL()
		for ent, status in pairs hornGlowStatus
			if not IsValid(ent)
				status.emmiter\Finish() if IsValid(status.emmiter)
				status.emmiterProp\Finish() if IsValid(status.emmiterProp)
				hornGlowStatus[ent] = nil
			elseif status.frame ~= frame
				status.data\SetHornGlow(status.prevStatus)
				status.emmiter\Finish() if IsValid(status.emmiter)
				status.emmiterProp\Finish() if IsValid(status.emmiterProp)
				hornGlowStatus[ent] = nil
			else
				if not status.prevStatus and RENDER_HORN_GLOW\GetBool() and status.data\GetHornGlow() ~= status.isEnabled
					status.data\SetHornGlow(status.isEnabled)
				if status.attach and IsValid(status.target)
					grabHornPos = Vector(hornShift) * status.data\GetPonySize()
					{:Pos, :Ang} = ent\GetAttachment(status.attach)
					grabHornPos\Rotate(Ang)
					if status.isEnabled and IsValid(status.emmiter) and status.nextSmokeParticle < RealTimeL()
						status.nextSmokeParticle = RealTimeL() + math.Rand(0.1, 0.2)
						for i = 1, math.random(1, 4)
							vec = VectorRand()
							calcPos = Pos + grabHornPos + vec
							with particle = status.emmiter\Add(smokeMaterial, calcPos)
								\SetRollDelta(math.rad(math.random(0, 360)))
								\SetPos(calcPos)
								life = math.Rand(0.6, 0.9)
								\SetStartAlpha(math.random(80, 170))
								\SetDieTime(life)
								\SetColor(status.color.r, status.color.g, status.color.b)
								\SetEndAlpha(0)
								size = math.Rand(2, 3)
								\SetEndSize(math.Rand(2, size))
								\SetStartSize(size)
								\SetGravity(Vector())
								\SetAirResistance(10)
								vecRand = VectorRand()
								vecRand.z *= 2
								\SetVelocity(ent\GetVelocity() + vecRand * status.data\GetPonySize() * 2)
								\SetCollide(false)
					if status.isEnabled and IsValid(status.emmiterProp) and status.nextGrabParticle < RealTimeL() and status.mins and status.maxs
						status.nextGrabParticle = RealTimeL() + math.Rand(0.05, 0.3)
						status.emmiterProp\SetPos(status.tpos)
						for i = 1, math.random(2, 6)
							calcPos = Vector(math.Rand(status.mins.x, status.maxs.x), math.Rand(status.mins.y, status.maxs.y), math.Rand(status.mins.z, status.maxs.z))
							with particle = status.emmiterProp\Add(fireMat, calcPos)
								\SetRollDelta(math.rad(math.random(0, 360)))
								\SetPos(calcPos)
								life = math.Rand(0.5, 0.9)
								\SetStartAlpha(math.random(130, 230))
								\SetDieTime(life)
								\SetColor(status.color.r, status.color.g, status.color.b)
								\SetEndAlpha(0)
								\SetEndSize(0)
								\SetStartSize(math.Rand(2, 6))
								\SetGravity(Vector())
								\SetAirResistance(15)
								\SetVelocity(VectorRand() * 6)
								\SetCollide(false)

	hook.Add 'DrawPhysgunBeam', 'PPM2.HornEffects', (physgun = NULL, isEnabled = false, target = NULL, bone = 0, hitPos = Vector()) =>
		return if not @IsPony() or not HORN_FP\GetBool() and @ == LocalPlayer() and not @ShouldDrawLocalPlayer()
		data = @GetPonyData()
		return if not data
		return if data\GetRace() ~= PPM2.RACE_UNICORN and data\GetRace() ~= PPM2.RACE_ALICORN
		if not hornGlowStatus[@]
			hornGlowStatus[@] = {
				frame: FrameNumberL()
				prevStatus: data\GetHornGlow()
				:data, :isEnabled, :hitPos, :target, :bone
				tpos: @GetPos()
				attach: @LookupAttachment('eyes')
				nextSmokeParticle: 0
				nextGrabParticle: 0
			}

			with hornGlowStatus[@]
				if HORN_PARTICLES\GetBool()
					.emmiter = ParticleEmitter(EyePos())
					.emmiterProp = ParticleEmitter(EyePos())

				.color = data\GetHornMagicColor()
				.haloSeed = math.rad(math.random(-1000, 1000))

				if not data\GetSeparateMagicColor()
					if not data\GetSeparateEyes()
						.color = DLib.LerpColor(0.5, data\GetEyeIrisTop(), data\GetEyeIrisBottom())
					else
						lerpLeft = DLib.LerpColor(0.5, data\GetEyeIrisTopLeft(), data\GetEyeIrisBottomLeft())
						lerpRight = DLib.LerpColor(0.5, data\GetEyeIrisTopRight(), data\GetEyeIrisBottomRight())
						.color = DLib.LerpColor(0.5, lerpLeft, lerpRight)
		else
			with hornGlowStatus[@]
				.frame = FrameNumberL()
				.isEnabled = isEnabled
				.target = target
				.bone = bone
				.hitPos = hitPos
				if IsValid(target)
					.tpos = target\GetPos() + hitPos
					center = target\WorldSpaceCenter()
					.center = center
					mins, maxs = target\WorldSpaceAABB()
					.mins = center + (mins - center) * 1.2
					.maxs = center + (maxs - center) * 1.2
		return false if HORN_HIDE_BEAM\GetBool() and IsValid(target)

hook.Add 'PrePlayerDraw', 'PPM2.PlayerDraw', PPM2.PrePlayerDraw, -2
hook.Add 'PostPlayerDraw', 'PPM2.PostPlayerDraw', PPM2.PostPlayerDraw, -2
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.PostDrawOpaqueRenderables', PPM2.PostDrawOpaqueRenderables, -2
hook.Add 'Think', 'PPM2.UpdateRenderTasks', Think, -2
hook.Add 'PreDrawOpaqueRenderables', 'PPM2.PreDrawOpaqueRenderables', PPM2.PreDrawOpaqueRenderables, -2
hook.Add 'PostDrawTranslucentRenderables', 'PPM2.PostDrawTranslucentRenderables', PPM2.PostDrawTranslucentRenderables, -2


Got wander working.

Got wander working.

System = require('vendor/secs/lib/system')
Vector = require('lib/geo/vector')

wanderTheta = math.pi

class NpcSteeringSystem extends System

	@criteria: System.Criteria({ 'isNpc', 'position', 'maxSpeed', 'maxForce', 'target' })

	update: (dt) =>
		for entity in *@entities
			velocity = @wander(entity, dt)
			entity\set('velocity', velocity)

	-- Direct path to target
	naive: (entity, dt, targetPosition) =>
		{ :position, :maxSpeed, :target } = entity\get()
		if not targetPosition then targetPosition = target.position
		return Vector(targetPosition - position, maxSpeed * dt)

	-- Steers toward target
	seek: (entity, dt, targetPosition) =>
		{ :position, :velocity, :maxSpeed, :target, :maxForce } = entity\get()
		if not velocity then velocity = Vector.ZERO
		if not targetPosition then targetPosition = target.position

		desiredVelocity = @naive(entity, dt, targetPosition)
		steering = desiredVelocity - velocity
		steering = steering\truncate(maxForce)
		velocity = (velocity + steering)\truncate(maxSpeed)
		return velocity

	-- Steers away from target
	flee: (entity, dt, targetPosition) =>
		{ :position, :velocity, :maxSpeed, :target, :maxForce } = entity\get()
		if not velocity then velocity = Vector.ZERO
		if not targetPosition then targetPosition = target.position

		desiredVelocity = -@naive(entity, dt, targetPosition)
		steering = desiredVelocity - velocity
		steering = steering\truncate(maxForce)
		velocity = (velocity + steering)\truncate(maxSpeed)
		return velocity

	-- Arrives at target
	arrive: (entity, dt, targetPosition) =>
		{ :position, :velocity, :maxSpeed, :target, :maxForce, :slowingRadius } = entity\get()
		if not velocity then velocity = Vector.ZERO
		if not targetPosition then targetPosition = target.position

		desiredVelocity = targetPosition - position
		distance = desiredVelocity\getLength()

		maxSpeed *= dt
		if distance < slowingRadius
			desiredVelocity = Vector(desiredVelocity, maxSpeed * distance / slowingRadius * dt)
		else
			desiredVelocity = Vector(desiredVelocity, maxSpeed * dt)

		steering = desiredVelocity - velocity
		velocity = (velocity + steering)\truncate(maxSpeed * dt)
		return velocity

	wander: (entity, dt, targetPosition) =>
		{ :position, :velocity, :maxSpeed, :maxForce, :slowingRadius } = entity\get()
		if not velocity then velocity = Vector.ZERO

		wanderR = 25
		wanderD = 80
		change = 0.3
		wanderTheta += math.random(-change, change)

		circlePos = with velocity\clone()
			\normalize()
			\multiply(wanderD)
			\add(position)

		h = velocity\getHeading()

		circleOffset = Vector(wanderR * math.cos(wanderTheta + h), wanderR * math.sin(wanderTheta + h))
		targetPosition = circlePos + circleOffset

		return @seek(entity, dt, targetPosition)

System = require('vendor/secs/lib/system')
Vector = require('lib/geo/vector')

CIRCLE_DISTANCE = 1
CIRCLE_RADIUS = 100
ANGLE_CHANGE = math.pi
wanderAngle = 0

class NpcSteeringSystem extends System

	@criteria: System.Criteria({ 'isNpc', 'position', 'maxSpeed', 'maxForce', 'target' })

	update: (dt) =>
		for entity in *@entities
			velocity = @seek(entity, dt)
			entity\set('velocity', velocity)

	-- Direct path to target
	naive: (entity, dt) =>
		{ :position, :maxSpeed, :target } = entity\get()
		return Vector(target.position - position, maxSpeed * dt)

	-- Steers toward target
	seek: (entity, dt) =>
		{ :position, :velocity, :maxSpeed, :target, :maxForce } = entity\get()
		desiredVelocity = @naive(entity, dt)
		if not velocity then velocity = Vector.ZERO
		steering = desiredVelocity - velocity
		steering = steering\truncate(maxForce)
		velocity = (velocity + steering)\truncate(maxSpeed)
		return velocity

	-- Steers away from target
	flee: (entity, dt) =>
		{ :position, :velocity, :maxSpeed, :target, :maxForce } = entity\get()
		desiredVelocity = -@naive(entity, dt)
		if not velocity then velocity = Vector.ZERO
		steering = desiredVelocity - velocity
		steering = steering\truncate(maxForce)
		velocity = (velocity + steering)\truncate(maxSpeed)
		return velocity

	-- Arrives at target
	arrive: (entity, dt) =>
		{ :position, :velocity, :maxSpeed, :target, :maxForce, :slowingRadius } = entity\get()
		desiredVelocity = target.position - position
		distance = desiredVelocity\getLength()

		maxSpeed *= dt
		if distance < slowingRadius
			desiredVelocity = Vector(desiredVelocity, maxSpeed * distance / slowingRadius * dt)
		else
			desiredVelocity = Vector(desiredVelocity, maxSpeed * dt)

		if not velocity then velocity = Vector.ZERO
		steering = desiredVelocity - velocity
		velocity = (velocity + steering)\truncate(maxSpeed * dt)
		return velocity

	wander: (entity, dt) =>
		{ :position, :velocity, :maxSpeed, :target, :maxForce, :slowingRadius } = entity\get()

		if not velocity then velocity = Vector.ZERO
		circleCenter = velocity\clone()
		with circleCenter
			\normalize()
			\scale(CIRCLE_DISTANCE)

		displacement = Vector(0, -1)
		with displacement
			\scale(CIRCLE_RADIUS)
			\setAngle(wanderAngle)

		wanderAngle += math.random() * ANGLE_CHANGE - ANGLE_CHANGE * 0.5
		wanderForce = circleCenter + displacement

		steering = wanderForce
		steering = steering\truncate(maxForce)
		velocity = (velocity + steering)\truncate(maxSpeed)
		return velocity


Add IsValid check in UpdateEyeReflections


Fixed minimal bits

Fixed minimal bits

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

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
rSEnt = net.ReadStrongEntity
wSEnt = net.WriteStrongEntity
rBool = net.ReadBool
wBool = net.WriteBool
rColor = net.ReadColor
wColor = net.WriteColor
rString = net.ReadString
wString = net.WriteString

COLOR_FIXER = (r = 255, g = 255, b = 255, a = 255) ->
	func = (arg = Color(r, g, b, a)) ->
		if type(arg) ~= 'table'
			return Color(255, 255, 255)
		else
			{:r, :g, :b, :a} = arg
			if r and g and b and a
				return Color(r, g, b, a)
			else
				return Color(255, 255, 255)
	return func

URL_FIXER = (arg = '') ->
	arg = tostring(arg)
	if arg\find('^https?://')
		return arg
	else
		return ''

rURL = -> URL_FIXER(rString())

FLOAT_FIXER = (def = 1, min = 0, max = 1) ->
	defFunc = -> def if type(def) ~= 'function'
	defFunc = def if type(def) == 'function'
	return (arg = defFunc()) -> math.Clamp(tonumber(arg) or defFunc(), min, max)

INT_FIXER = (def = 1, min = 0, max = 1) ->
	defFunc = -> def if type(def) ~= 'function'
	defFunc = def if type(def) == 'function'
	return (arg = defFunc()) -> math.floor(math.Clamp(tonumber(arg) or defFunc(), min, max))

PPM2.PonyDataRegistry = {
	'age': {
		default: -> PPM2.AGE_ADULT
		getFunc: 'Age'
		enum: {'FILLY', 'ADULT', 'MATURE'}
	}

	'race': {
		default: -> PPM2.RACE_EARTH
		getFunc: 'Race'
		enum: [arg for arg in *PPM2.RACE_ENUMS]
	}

	'wings_type': {
		default: -> 0
		getFunc: 'WingsType'
		enum: [arg for arg in *PPM2.AvaliablePonyWings]
	}

	'gender': {
		default: -> PPM2.GENDER_FEMALE
		getFunc: 'Gender'
		enum: [arg for arg in *PPM2.AGE_ENUMS]
	}

	'weight': {
		default: -> 1
		getFunc: 'Weight'
		min: PPM2.MIN_WEIGHT
		max: PPM2.MAX_WEIGHT
		type: 'FLOAT'
	}

	'ponysize': {
		default: -> 1
		getFunc: 'PonySize'
		min: PPM2.MIN_SCALE
		max: PPM2.MAX_SCALE
		type: 'FLOAT'
	}

	'necksize': {
		default: -> 1
		getFunc: 'NeckSize'
		min: PPM2.MIN_NECK
		max: PPM2.MAX_NECK
		type: 'FLOAT'
	}

	'legssize': {
		default: -> 1
		getFunc: 'LegsSize'
		min: PPM2.MIN_LEGS
		max: PPM2.MAX_LEGS
		type: 'FLOAT'
	}

	'spinesize': {
		default: -> 1
		getFunc: 'BackSize'
		min: PPM2.MIN_SPINE
		max: PPM2.MAX_SPINE
		type: 'FLOAT'
	}

	'male_buff': {
		default: -> PPM2.DEFAULT_MALE_BUFF
		getFunc: 'MaleBuff'
		min: PPM2.MIN_MALE_BUFF
		max: PPM2.MAX_MALE_BUFF
		type: 'FLOAT'
	}

	'eyelash': {
		default: -> 0
		getFunc: 'EyelashType'
		enum: [arg for arg in *PPM2.EyelashTypes]
	}

	'tail': {
		default: -> 0
		getFunc: 'TailType'
		enum: [arg for arg in *PPM2.AvaliableTails]
	}

	'tail_new': {
		default: -> 0
		getFunc: 'TailTypeNew'
		enum: [arg for arg in *PPM2.AvaliableTailsNew]
	}

	'mane': {
		default: -> 0
		getFunc: 'ManeType'
		enum: [arg for arg in *PPM2.AvaliableUpperManes]
	}

	'mane_new': {
		default: -> 0
		getFunc: 'ManeTypeNew'
		enum: [arg for arg in *PPM2.AvaliableUpperManesNew]
	}

	'manelower': {
		default: -> 0
		getFunc: 'ManeTypeLower'
		enum: [arg for arg in *PPM2.AvaliableLowerManes]
	}

	'manelower_new': {
		default: -> 0
		getFunc: 'ManeTypeLowerNew'
		enum: [arg for arg in *PPM2.AvaliableLowerManesNew]
	}

	'socks_texture': {
		default: -> 0
		getFunc: 'SocksTexture'
		enum: [arg for arg in *PPM2.SocksTypes]
	}

	'socks_texture_url': {
		default: -> ''
		getFunc: 'SocksTextureURL'
		type: 'URL'
	}

	'tailsize': {
		default: -> 1
		getFunc: 'TailSize'
		min: PPM2.MIN_TAIL_SIZE
		max: PPM2.MAX_TAIL_SIZE
		type: 'FLOAT'
	}

	'cmark': {
		default: -> true
		getFunc: 'CMark'
		type: 'BOOLEAN'
	}

	'cmark_size': {
		default: -> 1
		getFunc: 'CMarkSize'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'cmark_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'CMarkColor'
		type: 'COLOR'
	}

	'eyelash_color': {
		default: -> Color(0, 0, 0)
		getFunc: 'EyelashesColor'
		type: 'COLOR'
	}

	'eyelashes_phong_separate': {
		default: -> false
		getFunc: 'SeparateEyelashesPhong'
		type: 'BOOLEAN'
	}

	'fangs': {
		default: -> false
		getFunc: 'Fangs'
		type: 'BOOLEAN'
	}

	'bat_pony_ears': {
		default: -> false
		getFunc: 'BatPonyEars'
		type: 'BOOLEAN'
	}

	'claw_teeth': {
		default: -> false
		getFunc: 'ClawTeeth'
		type: 'BOOLEAN'
	}

	'cmark_type': {
		default: -> 4
		getFunc: 'CMarkType'
		enum: [arg for arg in *PPM2.DefaultCutiemarks]
	}

	'cmark_url': {
		default: -> ''
		getFunc: 'CMarkURL'
		type: 'URL'
	}

	'body': {
		default: -> Color(255, 255, 255)
		getFunc: 'BodyColor'
		type: 'COLOR'
	}

	'eyebrows': {
		default: -> Color(0, 0, 0)
		getFunc: 'EyebrowsColor'
		type: 'COLOR'
	}

	'eyebrows_glow': {
		default: -> false
		getFunc: 'GlowingEyebrows'
		type: 'BOOLEAN'
	}

	'eyebrows_glow_strength': {
		default: -> 1
		getFunc: 'EyebrowsGlowStrength'
		type: 'FLOAT'
		min: 0
		max: 1
	}

	'hide_manes': {
		default: -> true
		getFunc: 'HideManes'
		type: 'BOOLEAN'
	}

	'hide_manes_socks': {
		default: -> true
		getFunc: 'HideManesSocks'
		type: 'BOOLEAN'
	}

	'hide_manes_mane': {
		default: -> true
		getFunc: 'HideManesMane'
		type: 'BOOLEAN'
	}

	'hide_manes_tail': {
		default: -> true
		getFunc: 'HideManesTail'
		type: 'BOOLEAN'
	}

	'horn_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'HornColor'
		type: 'COLOR'
	}

	'wings_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'WingsColor'
		type: 'COLOR'
	}

	'separate_wings': {
		default: -> false
		getFunc: 'SeparateWings'
		type: 'BOOLEAN'
	}

	'separate_horn': {
		default: -> false
		getFunc: 'SeparateHorn'
		type: 'BOOLEAN'
	}

	'separate_magic_color': {
		default: -> false
		getFunc: 'SeparateMagicColor'
		type: 'BOOLEAN'
	}

	'horn_magic_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'HornMagicColor'
		type: 'COLOR'
	}

	'horn_glow': {
		default: -> false
		getFunc: 'HornGlow'
		type: 'BOOLEAN'
	}

	'horn_glow_strength': {
		default: -> 1
		getFunc: 'HornGlowSrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'horn_detail_color': {
		default: -> Color(90, 90, 90)
		getFunc: 'HornDetailColor'
		type: 'COLOR'
	}

	'separate_eyes': {
		default: -> false
		getFunc: 'SeparateEyes'
		type: 'BOOLEAN'
	}

	'separate_mane': {
		default: -> false
		getFunc: 'SeparateMane'
		type: 'BOOLEAN'
	}

	'socks': {
		default: -> false
		getFunc: 'Socks'
		type: 'BOOLEAN'
	}

	'new_male_muzzle': {
		default: -> true
		getFunc: 'NewMuzzle'
		type: 'BOOLEAN'
	}

	'noflex': {
		default: -> false
		getFunc: 'NoFlex'
		type: 'BOOLEAN'
	}

	'socks_model': {
		default: -> false
		getFunc: 'SocksAsModel'
		type: 'BOOLEAN'
	}

	'socks_model_new': {
		default: -> false
		getFunc: 'SocksAsNewModel'
		type: 'BOOLEAN'
	}

	'socks_model_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'SocksColor'
		type: 'COLOR'
	}

	'socks_new_model_color1': {
		default: -> Color(255, 255, 255)
		getFunc: 'NewSocksColor1'
		type: 'COLOR'
	}

	'socks_new_model_color2': {
		default: -> Color(0, 0, 0)
		getFunc: 'NewSocksColor2'
		type: 'COLOR'
	}

	'socks_new_model_color3': {
		default: -> Color(0, 0, 0)
		getFunc: 'NewSocksColor3'
		type: 'COLOR'
	}

	'socks_new_texture_url': {
		default: -> ''
		getFunc: 'NewSocksTextureURL'
		type: 'URL'
	}

	'suit': {
		default: -> 0
		getFunc: 'Bodysuit'
		enum: [arg for arg in *PPM2.AvaliablePonySuits]
	}

	'left_wing_size': {
		default: -> 1
		getFunc: 'LWingSize'
		min: PPM2.MIN_WING
		max: PPM2.MAX_WING
		type: 'FLOAT'
	}

	'left_wing_x': {
		default: -> 0
		getFunc: 'LWingX'
		min: PPM2.MIN_WINGX
		max: PPM2.MAX_WINGX
		type: 'FLOAT'
	}

	'left_wing_y': {
		default: -> 0
		getFunc: 'LWingY'
		min: PPM2.MIN_WINGY
		max: PPM2.MAX_WINGY
		type: 'FLOAT'
	}

	'left_wing_z': {
		default: -> 0
		getFunc: 'LWingZ'
		min: PPM2.MIN_WINGZ
		max: PPM2.MAX_WINGZ
		type: 'FLOAT'
	}

	'right_wing_size': {
		default: -> 1
		getFunc: 'RWingSize'
		min: PPM2.MIN_WING
		max: PPM2.MAX_WING
		type: 'FLOAT'
	}

	'right_wing_x': {
		default: -> 0
		getFunc: 'RWingX'
		min: PPM2.MIN_WINGX
		max: PPM2.MAX_WINGX
		type: 'FLOAT'
	}

	'right_wing_y': {
		default: -> 0
		getFunc: 'RWingY'
		min: PPM2.MIN_WINGY
		max: PPM2.MAX_WINGY
		type: 'FLOAT'
	}

	'right_wing_z': {
		default: -> 0
		getFunc: 'RWingZ'
		min: PPM2.MIN_WINGZ
		max: PPM2.MAX_WINGZ
		type: 'FLOAT'
	}

	'teeth_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'TeethColor'
		type: 'COLOR'
	}

	'mouth_color': {
		default: -> Color(219, 65, 155)
		getFunc: 'MouthColor'
		type: 'COLOR'
	}

	'tongue_color': {
		default: -> Color(235, 131, 59)
		getFunc: 'TongueColor'
		type: 'COLOR'
	}

	'bat_wing_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'BatWingColor'
		type: 'COLOR'
	}

	'bat_wing_skin_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'BatWingSkinColor'
		type: 'COLOR'
	}

	'separate_horn_phong': {
		default: -> false
		getFunc: 'SeparateHornPhong'
		type: 'BOOLEAN'
	}

	'separate_wings_phong': {
		default: -> false
		getFunc: 'SeparateWingsPhong'
		type: 'BOOLEAN'
	}

	'separate_mane_phong': {
		default: -> false
		getFunc: 'SeparateManePhong'
		type: 'BOOLEAN'
	}

	'separate_tail_phong': {
		default: -> false
		getFunc: 'SeparateTailPhong'
		type: 'BOOLEAN'
	}

	'alternative_fangs': {
		default: -> false
		getFunc: 'AlternativeFangs'
		type: 'BOOLEAN'
	}

	'hoof_fluffers': {
		default: -> false
		getFunc: 'HoofFluffers'
		type: 'BOOLEAN'
	}

	'hoof_fluffers_strength': {
		default: -> 1
		getFunc: 'HoofFluffersStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'ears_size': {
		default: -> 1
		getFunc: 'EarsSize'
		min: 0.1
		max: 2
		type: 'FLOAT'
	}

	'bat_pony_ears_strength': {
		default: -> 1
		getFunc: 'BatPonyEarsStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'fangs_strength': {
		default: -> 1
		getFunc: 'FangsStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'clawteeth_strength': {
		default: -> 1
		getFunc: 'ClawTeethStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'lips_color_inherit': {
		default: -> true
		getFunc: 'LipsColorInherit'
		type: 'BOOLEAN'
	}

	'nose_color_inherit': {
		default: -> true
		getFunc: 'NoseColorInherit'
		type: 'BOOLEAN'
	}

	'lips_color': {
		default: -> Color(172, 92, 92)
		getFunc: 'LipsColor'
		type: 'COLOR'
	}

	'nose_color': {
		default: -> Color(77, 84, 83)
		getFunc: 'NoseColor'
		type: 'COLOR'
	}

	'weapon_hide': {
		default: -> true
		getFunc: 'HideWeapons'
		type: 'BOOLEAN'
	}
}

for {internal, publicName} in *{{'_left', 'Left'}, {'_right', 'Right'}, {'', ''}}
	PPM2.PonyDataRegistry["eye_url#{internal}"] = {
		default: -> ''
		getFunc: "EyeURL#{publicName}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["eye_bg#{internal}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "EyeBackground#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_hole#{internal}"] = {
		default: -> Color(0, 0, 0)
		getFunc: "EyeHole#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_iris1#{internal}"] = {
		default: -> Color(200, 200, 200)
		getFunc: "EyeIrisTop#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_iris2#{internal}"] = {
		default: -> Color(200, 200, 200)
		getFunc: "EyeIrisBottom#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_irisline1#{internal}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "EyeIrisLine1#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_irisline_direction#{internal}"] = {
		default: -> false
		getFunc: "EyeLineDirection#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_irisline2#{internal}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "EyeIrisLine2#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_reflection#{internal}"] = {
		default: -> Color(255, 255, 255, 127)
		getFunc: "EyeReflection#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_effect#{internal}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "EyeEffect#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_lines#{internal}"] = {
		default: -> true
		getFunc: "EyeLines#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_iris_size#{internal}"] = {
		default: -> 1
		getFunc: "IrisSize#{publicName}"
		min: PPM2.MIN_IRIS
		max: PPM2.MAX_IRIS
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_derp#{internal}"] = {
		default: -> false
		getFunc: "DerpEyes#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_use_refract#{internal}"] = {
		default: -> false
		getFunc: "EyeRefract#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_cornera#{internal}"] = {
		default: -> false
		getFunc: "EyeCornerA#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_derp_strength#{internal}"] = {
		default: -> 1
		getFunc: "DerpEyesStrength#{publicName}"
		min: PPM2.MIN_DERP_STRENGTH
		max: PPM2.MAX_DERP_STRENGTH
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_type#{internal}"] = {
		default: -> 0
		getFunc: "EyeType#{publicName}"
		enum: [arg for arg in *PPM2.AvaliableEyeTypes]
	}

	PPM2.PonyDataRegistry["hole_width#{internal}"] = {
		default: -> 1
		getFunc: "HoleWidth#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["hole_height#{internal}"] = {
		default: -> 1
		getFunc: "HoleHeight#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["iris_width#{internal}"] = {
		default: -> 1
		getFunc: "IrisWidth#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["iris_height#{internal}"] = {
		default: -> 1
		getFunc: "IrisHeight#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_glossy_reflection#{internal}"] = {
		default: -> 0.16
		getFunc: "EyeGlossyStrength#{publicName}"
		min: 0
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["hole_shiftx#{internal}"] = {
		default: -> 0
		getFunc: "HoleShiftX#{publicName}"
		min: PPM2.MIN_HOLE_SHIFT
		max: PPM2.MAX_HOLE_SHIFT
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["hole_shifty#{internal}"] = {
		default: -> 0
		getFunc: "HoleShiftY#{publicName}"
		min: PPM2.MIN_HOLE_SHIFT
		max: PPM2.MAX_HOLE_SHIFT
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_hole_size#{internal}"] = {
		default: -> .8
		getFunc: "HoleSize#{publicName}"
		min: PPM2.MIN_HOLE
		max: PPM2.MAX_HOLE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_rotation#{internal}"] = {
		default: -> 0
		getFunc: "EyeRotation#{publicName}"
		min: PPM2.MIN_EYE_ROTATION
		max: PPM2.MAX_EYE_ROTATION
		type: 'INT'
	}

for i = 1, 3
	PPM2.PonyDataRegistry["horn_url_#{i}"] = {
		default: -> ''
		getFunc: "HornURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["bat_wing_url_#{i}"] = {
		default: -> ''
		getFunc: "BatWingURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["bat_wing_skin_url_#{i}"] = {
		default: -> ''
		getFunc: "BatWingSkinURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["wings_url_#{i}"] = {
		default: -> ''
		getFunc: "WingsURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["horn_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "HornURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["bat_wing_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "BatWingURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["bat_wing_skin_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "BatWingSkinURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["wings_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "WingsURLColor#{i}"
		type: 'COLOR'
	}

for i = 1, 6
	PPM2.PonyDataRegistry["socks_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "SocksDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "ManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "ManeDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "ManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_url_#{i}"] = {
		default: -> ''
		getFunc: "ManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["tail_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "TailDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["tail_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "TailURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["tail_url_#{i}"] = {
		default: -> ''
		getFunc: "TailURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["tail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "TailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "LowerManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "LowerManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_url_#{i}"] = {
		default: -> ''
		getFunc: "LowerManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["upper_mane_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "UpperManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "UpperManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_url_#{i}"] = {
		default: -> ''
		getFunc: "UpperManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["lower_mane_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "LowerManeDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "UpperManeDetailColor#{i}"
		type: 'COLOR'
	}

for i = 1, PPM2.MAX_BODY_DETAILS
	PPM2.PonyDataRegistry["body_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "BodyDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["body_detail_#{i}"] = {
		default: -> 0
		getFunc: "BodyDetail#{i}"
		enum: [arg for arg in *PPM2.BodyDetailsEnum]
	}

	PPM2.PonyDataRegistry["body_detail_url_#{i}"] = {
		default: -> ''
		getFunc: "BodyDetailURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["body_detail_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "BodyDetailURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["body_detail_glow_#{i}"] = {
		default: -> false
		getFunc: "BodyDetailGlow#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["body_detail_glow_strength_#{i}"] = {
		default: -> 1
		getFunc: "BodyDetailGlowStrength#{i}"
		type: 'FLOAT'
		min: 0
		max: 1
	}

for i = 1, PPM2.MAX_TATTOOS
	PPM2.PonyDataRegistry["tattoo_type_#{i}"] = {
		default: -> 0
		getFunc: "TattooType#{i}"
		enum: [arg for arg in *PPM2.TATTOOS_REGISTRY]
	}

	PPM2.PonyDataRegistry["tattoo_posx_#{i}"] = {
		default: -> 0
		getFunc: "TattooPosX#{i}"
		min: -100
		max: 100
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_posy_#{i}"] = {
		default: -> 0
		getFunc: "TattooPosY#{i}"
		fix: (arg = 0) -> math.Clamp(tonumber(arg) or 0, -100, 100)
		min: -100
		max: 100
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_rotate_#{i}"] = {
		default: -> 0
		getFunc: "TattooRotate#{i}"
		min: -180
		max: 180
		type: 'INT'
	}

	PPM2.PonyDataRegistry["tattoo_scalex_#{i}"] = {
		default: -> 1
		getFunc: "TattooScaleX#{i}"
		min: 0
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_glow_strength_#{i}"] = {
		default: -> 1
		getFunc: "TattooGlowStrength#{i}"
		min: 0
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_scaley_#{i}"] = {
		default: -> 1
		getFunc: "TattooScaleY#{i}"
		min: 0
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_glow_#{i}"] = {
		default: -> false
		getFunc: "TattooGlow#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["tattoo_over_detail_#{i}"] = {
		default: -> false
		getFunc: "TattooOverDetail#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["tattoo_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "TattooColor#{i}"
		type: 'COLOR'
	}

for ttype in *{'Body', 'Horn', 'Wings', 'BatWingsSkin', 'Socks', 'Mane', 'Tail', 'UpperMane', 'LowerMane', 'LEye', 'REye', 'BEyes', 'Eyelashes'}
	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_exponent'] = {
		default: -> 3
		getFunc: ttype .. 'PhongExponent'
		min: 0.04
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_boost'] = {
		default: -> 0.09
		getFunc: ttype .. 'PhongBoost'
		min: 0.01
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_front'] = {
		default: -> 1
		getFunc: ttype .. 'PhongFront'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_middle'] = {
		default: -> 5
		getFunc: ttype .. 'PhongMiddle'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_sliding'] = {
		default: -> 10
		getFunc: ttype .. 'PhongSliding'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_tint'] = {
		default: -> Color(255, 200, 200)
		getFunc: ttype .. 'PhongTint'
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_lightwarp_texture'] = {
		default: -> 0
		getFunc: ttype .. 'Lightwarp'
		enum: [arg for arg in *PPM2.AvaliableLightwarps]
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_lightwarp_texture_url'] = {
		default: -> ''
		getFunc: ttype .. 'LightwarpURL'
		type: 'URL'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_bumpmap_texture_url'] = {
		default: -> ''
		getFunc: ttype .. 'BumpmapURL'
		type: 'URL'
	}

for {:flex, :active} in *PPM2.PonyFlexController.FLEX_LIST
	continue if not active
	PPM2.PonyDataRegistry["flex_disable_#{flex\lower()}"] = {
		default: -> false
		getFunc: "DisableFlex#{flex}"
		type: 'BOOLEAN'
	}

testMinimalBits = 0

for key, value in pairs PPM2.PonyDataRegistry
	if value.enum
		value.min = 0
		value.max = #value.enum - 1
		value.type = 'INT'

	switch value.type
		when 'INT'
			error("Variable #{key} has invalid minimal value (#{type(value.min)})") if type(value.min) ~= 'number'
			error("Variable #{max} has invalid maximal value (#{type(value.max)})") if type(value.max) ~= 'number'
			value.fix = INT_FIXER(value.default, value.min, value.max)
			if value.min >= 0
				selectBits = net.ChooseOptimalBits(value.max - value.min)
				testMinimalBits += selectBits
				value.read = rUInt(selectBits, value.min, value.max)
				value.write = wUInt(value.default(), selectBits)
			else
				selectBits = net.ChooseOptimalBits(math.max(math.abs(value.max), math.abs(value.min)))
				testMinimalBits += selectBits
				value.read = rInt(selectBits, value.min, value.max)
				value.write = wInt(value.default(), selectBits)
		when 'FLOAT'
			error("Variable #{key} has invalid minimal value (#{type(value.min)})") if type(value.min) ~= 'number'
			error("Variable #{max} has invalid maximal value (#{type(value.max)})") if type(value.max) ~= 'number'
			value.fix = FLOAT_FIXER(value.default, value.min, value.max)
			value.read = rFloat(value.min, value.max)
			value.write = (arg = value.default()) -> wFloat(arg)
			testMinimalBits += 32
		when 'URL'
			value.fix = URL_FIXER
			value.read = rURL
			value.write = wString
			testMinimalBits += 8
		when 'BOOLEAN'
			value.fix = (arg = value.default()) -> tobool(arg)
			value.read = rBool
			value.write = wBool
			testMinimalBits += 1
		when 'COLOR'
			{:r, :g, :b, :a} = value.default()
			value.fix = COLOR_FIXER(r, g, b, a)
			value.read = rColor
			value.write = wColor
			testMinimalBits += 32
		else
			error("Unknown variable type - #{value.type} for #{key}")

-- print('Minimal required bits - ' .. testMinimalBits)
PPM2.testMinimalBits = testMinimalBits

for key, value in pairs PPM2.PonyDataRegistry
	error("Data has no fix function: #{key}") if not value.fix

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

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
rSEnt = net.ReadStrongEntity
wSEnt = net.WriteStrongEntity
rBool = net.ReadBool
wBool = net.WriteBool
rColor = net.ReadColor
wColor = net.WriteColor
rString = net.ReadString
wString = net.WriteString

COLOR_FIXER = (r = 255, g = 255, b = 255, a = 255) ->
	func = (arg = Color(r, g, b, a)) ->
		if type(arg) ~= 'table'
			return Color(255, 255, 255)
		else
			{:r, :g, :b, :a} = arg
			if r and g and b and a
				return Color(r, g, b, a)
			else
				return Color(255, 255, 255)
	return func

URL_FIXER = (arg = '') ->
	arg = tostring(arg)
	if arg\find('^https?://')
		return arg
	else
		return ''

rURL = -> URL_FIXER(rString())

FLOAT_FIXER = (def = 1, min = 0, max = 1) ->
	defFunc = -> def if type(def) ~= 'function'
	defFunc = def if type(def) == 'function'
	return (arg = defFunc()) -> math.Clamp(tonumber(arg) or defFunc(), min, max)

INT_FIXER = (def = 1, min = 0, max = 1) ->
	defFunc = -> def if type(def) ~= 'function'
	defFunc = def if type(def) == 'function'
	return (arg = defFunc()) -> math.floor(math.Clamp(tonumber(arg) or defFunc(), min, max))

PPM2.PonyDataRegistry = {
	'age': {
		default: -> PPM2.AGE_ADULT
		getFunc: 'Age'
		enum: {'FILLY', 'ADULT', 'MATURE'}
	}

	'race': {
		default: -> PPM2.RACE_EARTH
		getFunc: 'Race'
		enum: [arg for arg in *PPM2.RACE_ENUMS]
	}

	'wings_type': {
		default: -> 0
		getFunc: 'WingsType'
		enum: [arg for arg in *PPM2.AvaliablePonyWings]
	}

	'gender': {
		default: -> PPM2.GENDER_FEMALE
		getFunc: 'Gender'
		enum: [arg for arg in *PPM2.AGE_ENUMS]
	}

	'weight': {
		default: -> 1
		getFunc: 'Weight'
		min: PPM2.MIN_WEIGHT
		max: PPM2.MAX_WEIGHT
		type: 'FLOAT'
	}

	'ponysize': {
		default: -> 1
		getFunc: 'PonySize'
		min: PPM2.MIN_SCALE
		max: PPM2.MAX_SCALE
		type: 'FLOAT'
	}

	'necksize': {
		default: -> 1
		getFunc: 'NeckSize'
		min: PPM2.MIN_NECK
		max: PPM2.MAX_NECK
		type: 'FLOAT'
	}

	'legssize': {
		default: -> 1
		getFunc: 'LegsSize'
		min: PPM2.MIN_LEGS
		max: PPM2.MAX_LEGS
		type: 'FLOAT'
	}

	'spinesize': {
		default: -> 1
		getFunc: 'BackSize'
		min: PPM2.MIN_SPINE
		max: PPM2.MAX_SPINE
		type: 'FLOAT'
	}

	'male_buff': {
		default: -> PPM2.DEFAULT_MALE_BUFF
		getFunc: 'MaleBuff'
		min: PPM2.MIN_MALE_BUFF
		max: PPM2.MAX_MALE_BUFF
		type: 'FLOAT'
	}

	'eyelash': {
		default: -> 0
		getFunc: 'EyelashType'
		enum: [arg for arg in *PPM2.EyelashTypes]
	}

	'tail': {
		default: -> 0
		getFunc: 'TailType'
		enum: [arg for arg in *PPM2.AvaliableTails]
	}

	'tail_new': {
		default: -> 0
		getFunc: 'TailTypeNew'
		enum: [arg for arg in *PPM2.AvaliableTailsNew]
	}

	'mane': {
		default: -> 0
		getFunc: 'ManeType'
		enum: [arg for arg in *PPM2.AvaliableUpperManes]
	}

	'mane_new': {
		default: -> 0
		getFunc: 'ManeTypeNew'
		enum: [arg for arg in *PPM2.AvaliableUpperManesNew]
	}

	'manelower': {
		default: -> 0
		getFunc: 'ManeTypeLower'
		enum: [arg for arg in *PPM2.AvaliableLowerManes]
	}

	'manelower_new': {
		default: -> 0
		getFunc: 'ManeTypeLowerNew'
		enum: [arg for arg in *PPM2.AvaliableLowerManesNew]
	}

	'socks_texture': {
		default: -> 0
		getFunc: 'SocksTexture'
		enum: [arg for arg in *PPM2.SocksTypes]
	}

	'socks_texture_url': {
		default: -> ''
		getFunc: 'SocksTextureURL'
		type: 'URL'
	}

	'tailsize': {
		default: -> 1
		getFunc: 'TailSize'
		min: PPM2.MIN_TAIL_SIZE
		max: PPM2.MAX_TAIL_SIZE
		type: 'FLOAT'
	}

	'cmark': {
		default: -> true
		getFunc: 'CMark'
		type: 'BOOLEAN'
	}

	'cmark_size': {
		default: -> 1
		getFunc: 'CMarkSize'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'cmark_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'CMarkColor'
		type: 'COLOR'
	}

	'eyelash_color': {
		default: -> Color(0, 0, 0)
		getFunc: 'EyelashesColor'
		type: 'COLOR'
	}

	'eyelashes_phong_separate': {
		default: -> false
		getFunc: 'SeparateEyelashesPhong'
		type: 'BOOLEAN'
	}

	'fangs': {
		default: -> false
		getFunc: 'Fangs'
		type: 'BOOLEAN'
	}

	'bat_pony_ears': {
		default: -> false
		getFunc: 'BatPonyEars'
		type: 'BOOLEAN'
	}

	'claw_teeth': {
		default: -> false
		getFunc: 'ClawTeeth'
		type: 'BOOLEAN'
	}

	'cmark_type': {
		default: -> 4
		getFunc: 'CMarkType'
		enum: [arg for arg in *PPM2.DefaultCutiemarks]
	}

	'cmark_url': {
		default: -> ''
		getFunc: 'CMarkURL'
		type: 'URL'
	}

	'body': {
		default: -> Color(255, 255, 255)
		getFunc: 'BodyColor'
		type: 'COLOR'
	}

	'eyebrows': {
		default: -> Color(0, 0, 0)
		getFunc: 'EyebrowsColor'
		type: 'COLOR'
	}

	'eyebrows_glow': {
		default: -> false
		getFunc: 'GlowingEyebrows'
		type: 'BOOLEAN'
	}

	'eyebrows_glow_strength': {
		default: -> 1
		getFunc: 'EyebrowsGlowStrength'
		type: 'FLOAT'
		min: 0
		max: 1
	}

	'hide_manes': {
		default: -> true
		getFunc: 'HideManes'
		type: 'BOOLEAN'
	}

	'hide_manes_socks': {
		default: -> true
		getFunc: 'HideManesSocks'
		type: 'BOOLEAN'
	}

	'hide_manes_mane': {
		default: -> true
		getFunc: 'HideManesMane'
		type: 'BOOLEAN'
	}

	'hide_manes_tail': {
		default: -> true
		getFunc: 'HideManesTail'
		type: 'BOOLEAN'
	}

	'horn_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'HornColor'
		type: 'COLOR'
	}

	'wings_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'WingsColor'
		type: 'COLOR'
	}

	'separate_wings': {
		default: -> false
		getFunc: 'SeparateWings'
		type: 'BOOLEAN'
	}

	'separate_horn': {
		default: -> false
		getFunc: 'SeparateHorn'
		type: 'BOOLEAN'
	}

	'separate_magic_color': {
		default: -> false
		getFunc: 'SeparateMagicColor'
		type: 'BOOLEAN'
	}

	'horn_magic_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'HornMagicColor'
		type: 'COLOR'
	}

	'horn_glow': {
		default: -> false
		getFunc: 'HornGlow'
		type: 'BOOLEAN'
	}

	'horn_glow_strength': {
		default: -> 1
		getFunc: 'HornGlowSrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'horn_detail_color': {
		default: -> Color(90, 90, 90)
		getFunc: 'HornDetailColor'
		type: 'COLOR'
	}

	'separate_eyes': {
		default: -> false
		getFunc: 'SeparateEyes'
		type: 'BOOLEAN'
	}

	'separate_mane': {
		default: -> false
		getFunc: 'SeparateMane'
		type: 'BOOLEAN'
	}

	'socks': {
		default: -> false
		getFunc: 'Socks'
		type: 'BOOLEAN'
	}

	'new_male_muzzle': {
		default: -> true
		getFunc: 'NewMuzzle'
		type: 'BOOLEAN'
	}

	'noflex': {
		default: -> false
		getFunc: 'NoFlex'
		type: 'BOOLEAN'
	}

	'socks_model': {
		default: -> false
		getFunc: 'SocksAsModel'
		type: 'BOOLEAN'
	}

	'socks_model_new': {
		default: -> false
		getFunc: 'SocksAsNewModel'
		type: 'BOOLEAN'
	}

	'socks_model_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'SocksColor'
		type: 'COLOR'
	}

	'socks_new_model_color1': {
		default: -> Color(255, 255, 255)
		getFunc: 'NewSocksColor1'
		type: 'COLOR'
	}

	'socks_new_model_color2': {
		default: -> Color(0, 0, 0)
		getFunc: 'NewSocksColor2'
		type: 'COLOR'
	}

	'socks_new_model_color3': {
		default: -> Color(0, 0, 0)
		getFunc: 'NewSocksColor3'
		type: 'COLOR'
	}

	'socks_new_texture_url': {
		default: -> ''
		getFunc: 'NewSocksTextureURL'
		type: 'URL'
	}

	'suit': {
		default: -> 0
		getFunc: 'Bodysuit'
		enum: [arg for arg in *PPM2.AvaliablePonySuits]
	}

	'left_wing_size': {
		default: -> 1
		getFunc: 'LWingSize'
		min: PPM2.MIN_WING
		max: PPM2.MAX_WING
		type: 'FLOAT'
	}

	'left_wing_x': {
		default: -> 0
		getFunc: 'LWingX'
		min: PPM2.MIN_WINGX
		max: PPM2.MAX_WINGX
		type: 'FLOAT'
	}

	'left_wing_y': {
		default: -> 0
		getFunc: 'LWingY'
		min: PPM2.MIN_WINGY
		max: PPM2.MAX_WINGY
		type: 'FLOAT'
	}

	'left_wing_z': {
		default: -> 0
		getFunc: 'LWingZ'
		min: PPM2.MIN_WINGZ
		max: PPM2.MAX_WINGZ
		type: 'FLOAT'
	}

	'right_wing_size': {
		default: -> 1
		getFunc: 'RWingSize'
		min: PPM2.MIN_WING
		max: PPM2.MAX_WING
		type: 'FLOAT'
	}

	'right_wing_x': {
		default: -> 0
		getFunc: 'RWingX'
		min: PPM2.MIN_WINGX
		max: PPM2.MAX_WINGX
		type: 'FLOAT'
	}

	'right_wing_y': {
		default: -> 0
		getFunc: 'RWingY'
		min: PPM2.MIN_WINGY
		max: PPM2.MAX_WINGY
		type: 'FLOAT'
	}

	'right_wing_z': {
		default: -> 0
		getFunc: 'RWingZ'
		min: PPM2.MIN_WINGZ
		max: PPM2.MAX_WINGZ
		type: 'FLOAT'
	}

	'teeth_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'TeethColor'
		type: 'COLOR'
	}

	'mouth_color': {
		default: -> Color(219, 65, 155)
		getFunc: 'MouthColor'
		type: 'COLOR'
	}

	'tongue_color': {
		default: -> Color(235, 131, 59)
		getFunc: 'TongueColor'
		type: 'COLOR'
	}

	'bat_wing_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'BatWingColor'
		type: 'COLOR'
	}

	'bat_wing_skin_color': {
		default: -> Color(255, 255, 255)
		getFunc: 'BatWingSkinColor'
		type: 'COLOR'
	}

	'separate_horn_phong': {
		default: -> false
		getFunc: 'SeparateHornPhong'
		type: 'BOOLEAN'
	}

	'separate_wings_phong': {
		default: -> false
		getFunc: 'SeparateWingsPhong'
		type: 'BOOLEAN'
	}

	'separate_mane_phong': {
		default: -> false
		getFunc: 'SeparateManePhong'
		type: 'BOOLEAN'
	}

	'separate_tail_phong': {
		default: -> false
		getFunc: 'SeparateTailPhong'
		type: 'BOOLEAN'
	}

	'alternative_fangs': {
		default: -> false
		getFunc: 'AlternativeFangs'
		type: 'BOOLEAN'
	}

	'hoof_fluffers': {
		default: -> false
		getFunc: 'HoofFluffers'
		type: 'BOOLEAN'
	}

	'hoof_fluffers_strength': {
		default: -> 1
		getFunc: 'HoofFluffersStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'ears_size': {
		default: -> 1
		getFunc: 'EarsSize'
		min: 0.1
		max: 2
		type: 'FLOAT'
	}

	'bat_pony_ears_strength': {
		default: -> 1
		getFunc: 'BatPonyEarsStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'fangs_strength': {
		default: -> 1
		getFunc: 'FangsStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'clawteeth_strength': {
		default: -> 1
		getFunc: 'ClawTeethStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'lips_color_inherit': {
		default: -> true
		getFunc: 'LipsColorInherit'
		type: 'BOOLEAN'
	}

	'nose_color_inherit': {
		default: -> true
		getFunc: 'NoseColorInherit'
		type: 'BOOLEAN'
	}

	'lips_color': {
		default: -> Color(172, 92, 92)
		getFunc: 'LipsColor'
		type: 'COLOR'
	}

	'nose_color': {
		default: -> Color(77, 84, 83)
		getFunc: 'NoseColor'
		type: 'COLOR'
	}

	'weapon_hide': {
		default: -> true
		getFunc: 'HideWeapons'
		type: 'BOOLEAN'
	}
}

for {internal, publicName} in *{{'_left', 'Left'}, {'_right', 'Right'}, {'', ''}}
	PPM2.PonyDataRegistry["eye_url#{internal}"] = {
		default: -> ''
		getFunc: "EyeURL#{publicName}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["eye_bg#{internal}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "EyeBackground#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_hole#{internal}"] = {
		default: -> Color(0, 0, 0)
		getFunc: "EyeHole#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_iris1#{internal}"] = {
		default: -> Color(200, 200, 200)
		getFunc: "EyeIrisTop#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_iris2#{internal}"] = {
		default: -> Color(200, 200, 200)
		getFunc: "EyeIrisBottom#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_irisline1#{internal}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "EyeIrisLine1#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_irisline_direction#{internal}"] = {
		default: -> false
		getFunc: "EyeLineDirection#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_irisline2#{internal}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "EyeIrisLine2#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_reflection#{internal}"] = {
		default: -> Color(255, 255, 255, 127)
		getFunc: "EyeReflection#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_effect#{internal}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "EyeEffect#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_lines#{internal}"] = {
		default: -> true
		getFunc: "EyeLines#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_iris_size#{internal}"] = {
		default: -> 1
		getFunc: "IrisSize#{publicName}"
		min: PPM2.MIN_IRIS
		max: PPM2.MAX_IRIS
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_derp#{internal}"] = {
		default: -> false
		getFunc: "DerpEyes#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_use_refract#{internal}"] = {
		default: -> false
		getFunc: "EyeRefract#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_cornera#{internal}"] = {
		default: -> false
		getFunc: "EyeCornerA#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_derp_strength#{internal}"] = {
		default: -> 1
		getFunc: "DerpEyesStrength#{publicName}"
		min: PPM2.MIN_DERP_STRENGTH
		max: PPM2.MAX_DERP_STRENGTH
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_type#{internal}"] = {
		default: -> 0
		getFunc: "EyeType#{publicName}"
		enum: [arg for arg in *PPM2.AvaliableEyeTypes]
	}

	PPM2.PonyDataRegistry["hole_width#{internal}"] = {
		default: -> 1
		getFunc: "HoleWidth#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["hole_height#{internal}"] = {
		default: -> 1
		getFunc: "HoleHeight#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["iris_width#{internal}"] = {
		default: -> 1
		getFunc: "IrisWidth#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["iris_height#{internal}"] = {
		default: -> 1
		getFunc: "IrisHeight#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_glossy_reflection#{internal}"] = {
		default: -> 0.16
		getFunc: "EyeGlossyStrength#{publicName}"
		min: 0
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["hole_shiftx#{internal}"] = {
		default: -> 0
		getFunc: "HoleShiftX#{publicName}"
		min: PPM2.MIN_HOLE_SHIFT
		max: PPM2.MAX_HOLE_SHIFT
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["hole_shifty#{internal}"] = {
		default: -> 0
		getFunc: "HoleShiftY#{publicName}"
		min: PPM2.MIN_HOLE_SHIFT
		max: PPM2.MAX_HOLE_SHIFT
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_hole_size#{internal}"] = {
		default: -> .8
		getFunc: "HoleSize#{publicName}"
		min: PPM2.MIN_HOLE
		max: PPM2.MAX_HOLE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_rotation#{internal}"] = {
		default: -> 0
		getFunc: "EyeRotation#{publicName}"
		min: PPM2.MIN_EYE_ROTATION
		max: PPM2.MAX_EYE_ROTATION
		type: 'INT'
	}

for i = 1, 3
	PPM2.PonyDataRegistry["horn_url_#{i}"] = {
		default: -> ''
		getFunc: "HornURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["bat_wing_url_#{i}"] = {
		default: -> ''
		getFunc: "BatWingURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["bat_wing_skin_url_#{i}"] = {
		default: -> ''
		getFunc: "BatWingSkinURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["wings_url_#{i}"] = {
		default: -> ''
		getFunc: "WingsURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["horn_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "HornURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["bat_wing_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "BatWingURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["bat_wing_skin_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "BatWingSkinURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["wings_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "WingsURLColor#{i}"
		type: 'COLOR'
	}

for i = 1, 6
	PPM2.PonyDataRegistry["socks_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "SocksDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "ManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "ManeDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "ManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_url_#{i}"] = {
		default: -> ''
		getFunc: "ManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["tail_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "TailDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["tail_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "TailURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["tail_url_#{i}"] = {
		default: -> ''
		getFunc: "TailURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["tail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "TailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "LowerManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "LowerManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_url_#{i}"] = {
		default: -> ''
		getFunc: "LowerManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["upper_mane_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "UpperManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "UpperManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_url_#{i}"] = {
		default: -> ''
		getFunc: "UpperManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["lower_mane_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "LowerManeDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "UpperManeDetailColor#{i}"
		type: 'COLOR'
	}

for i = 1, PPM2.MAX_BODY_DETAILS
	PPM2.PonyDataRegistry["body_detail_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "BodyDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["body_detail_#{i}"] = {
		default: -> 0
		getFunc: "BodyDetail#{i}"
		enum: [arg for arg in *PPM2.BodyDetailsEnum]
	}

	PPM2.PonyDataRegistry["body_detail_url_#{i}"] = {
		default: -> ''
		getFunc: "BodyDetailURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["body_detail_url_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "BodyDetailURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["body_detail_glow_#{i}"] = {
		default: -> false
		getFunc: "BodyDetailGlow#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["body_detail_glow_strength_#{i}"] = {
		default: -> 1
		getFunc: "BodyDetailGlowStrength#{i}"
		type: 'FLOAT'
		min: 0
		max: 1
	}

for i = 1, PPM2.MAX_TATTOOS
	PPM2.PonyDataRegistry["tattoo_type_#{i}"] = {
		default: -> 0
		getFunc: "TattooType#{i}"
		enum: [arg for arg in *PPM2.TATTOOS_REGISTRY]
	}

	PPM2.PonyDataRegistry["tattoo_posx_#{i}"] = {
		default: -> 0
		getFunc: "TattooPosX#{i}"
		min: -100
		max: 100
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_posy_#{i}"] = {
		default: -> 0
		getFunc: "TattooPosY#{i}"
		fix: (arg = 0) -> math.Clamp(tonumber(arg) or 0, -100, 100)
		min: -100
		max: 100
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_rotate_#{i}"] = {
		default: -> 0
		getFunc: "TattooRotate#{i}"
		min: -180
		max: 180
		type: 'INT'
	}

	PPM2.PonyDataRegistry["tattoo_scalex_#{i}"] = {
		default: -> 1
		getFunc: "TattooScaleX#{i}"
		min: 0
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_glow_strength_#{i}"] = {
		default: -> 1
		getFunc: "TattooGlowStrength#{i}"
		min: 0
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_scaley_#{i}"] = {
		default: -> 1
		getFunc: "TattooScaleY#{i}"
		min: 0
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_glow_#{i}"] = {
		default: -> false
		getFunc: "TattooGlow#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["tattoo_over_detail_#{i}"] = {
		default: -> false
		getFunc: "TattooOverDetail#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["tattoo_color_#{i}"] = {
		default: -> Color(255, 255, 255)
		getFunc: "TattooColor#{i}"
		type: 'COLOR'
	}

for ttype in *{'Body', 'Horn', 'Wings', 'BatWingsSkin', 'Socks', 'Mane', 'Tail', 'UpperMane', 'LowerMane', 'LEye', 'REye', 'BEyes', 'Eyelashes'}
	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_exponent'] = {
		default: -> 3
		getFunc: ttype .. 'PhongExponent'
		min: 0.04
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_boost'] = {
		default: -> 0.09
		getFunc: ttype .. 'PhongBoost'
		min: 0.01
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_front'] = {
		default: -> 1
		getFunc: ttype .. 'PhongFront'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_middle'] = {
		default: -> 5
		getFunc: ttype .. 'PhongMiddle'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_sliding'] = {
		default: -> 10
		getFunc: ttype .. 'PhongSliding'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_tint'] = {
		default: -> Color(255, 200, 200)
		getFunc: ttype .. 'PhongTint'
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_lightwarp_texture'] = {
		default: -> 0
		getFunc: ttype .. 'Lightwarp'
		enum: [arg for arg in *PPM2.AvaliableLightwarps]
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_lightwarp_texture_url'] = {
		default: -> ''
		getFunc: ttype .. 'LightwarpURL'
		type: 'URL'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_bumpmap_texture_url'] = {
		default: -> ''
		getFunc: ttype .. 'BumpmapURL'
		type: 'URL'
	}

for {:flex, :active} in *PPM2.PonyFlexController.FLEX_LIST
	continue if not active
	PPM2.PonyDataRegistry["flex_disable_#{flex\lower()}"] = {
		default: -> false
		getFunc: "DisableFlex#{flex}"
		type: 'BOOLEAN'
	}

for key, value in pairs PPM2.PonyDataRegistry
	if value.enum
		value.min = 0
		value.max = #value.enum - 1
		value.type = 'INT'

	switch value.type
		when 'INT'
			error("Variable #{key} has invalid minimal value (#{type(value.min)})") if type(value.min) ~= 'number'
			error("Variable #{max} has invalid maximal value (#{type(value.max)})") if type(value.max) ~= 'number'
			value.fix = INT_FIXER(value.default, value.min, value.max)
			if value.min >= 0
				selectBits = 16
				while 2 ^ (selectBits - 1) > value.max
					selectBits -= 1
				value.read = rUInt(selectBits, value.min, value.max)
				value.write = wUInt(value.default(), selectBits)
			else
				delta = math.max(value.max, -value.min)
				selectBits = 16
				while 2 ^ (selectBits - 1) > delta
					selectBits -= 1
				value.read = rInt(selectBits, value.min, value.max)
				value.write = wInt(value.default(), selectBits)
		when 'FLOAT'
			error("Variable #{key} has invalid minimal value (#{type(value.min)})") if type(value.min) ~= 'number'
			error("Variable #{max} has invalid maximal value (#{type(value.max)})") if type(value.max) ~= 'number'
			value.fix = FLOAT_FIXER(value.default, value.min, value.max)
			value.read = rFloat(value.min, value.max)
			value.write = (arg = value.default()) -> wFloat(arg)
		when 'URL'
			value.fix = URL_FIXER
			value.read = rURL
			value.write = wString
		when 'BOOLEAN'
			value.fix = (arg = value.default()) -> tobool(arg)
			value.read = rBool
			value.write = wBool
		when 'COLOR'
			{:r, :g, :b, :a} = value.default()
			value.fix = COLOR_FIXER(r, g, b, a)
			value.read = rColor
			value.write = wColor
		else
			error("Unknown variable type - #{value.type} for #{key}")

for key, value in pairs PPM2.PonyDataRegistry
	error("Data has no fix function: #{key}") if not value.fix


logger: add seen function

logger: add seen function

Former-commit-id: 45170cda2b58809a76ab1ba0ea5ac30eb90a2a24 [formerly c9fc594139a5d03d911d1ede7c95a9b08b64149d]
Former-commit-id: 89f6c5173f8a796bbd58c58d51af805fd34ad8e1

dbi = require 'DBI'
require'logging.console'
iconv = require'iconv'
iso2utf = iconv.new('UTF-8'.."//TRANSLIT", 'ISO-8859-1')
log = logging.console()

conn = false

schemas = {[[
  CREATE TABLE IF NOT EXISTS log (
    time timestamp DEFAULT now(),
    nick text,
    channel text,
    message text,
    type text
  );]],
  [[CREATE INDEX idx_time ON log(time);]],
  [[CREATE INDEX idx_channel ON log(channel);]]
}

toutf = (s) ->
  iso2utf\iconv(s)

connect = ->
  conn, err = DBI.Connect('PostgreSQL', ivar2.config.dbname, ivar2.config.dbuser, ivar2.config.dbpass, ivar2.config.dbhost, ivar2.config.dbport)
  unless conn
    log\error "Unable to connect to DB: #{err}"
    return

  conn\autocommit(true)

  --for s in *schemas
  --  a,b = DBI.Do(conn, s)

dbh = ->
  connect! unless conn

  -- Check if connection is alive
  alive = conn\ping!
  connect! unless alive

  success, err = DBI.Do(conn, 'SELECT NOW()')
  unless success
    log\error "SQL Connection :#{err}"
    connect!

  return conn

dblog = (type, source, destination, arg) =>
  nick = source.nick

  if type == 'PRIVMSG'
    -- action
    if arg\sub(1,1) == '\001' and arg\sub(-1) == '\001'
      arg = arg\sub(9, -2)
      type = 'ACTION'

  unless arg
    arg = ''

  insert = ->
    ins = dbh!\prepare('INSERT INTO log(nick,channel,message,type) values(?,?,?,?)')
    ins\execute(nick, destination, arg, type)

  -- First try to insert statement directly
  -- if that doesn't work; convert it from iso to utf and try again
  stmt, err = insert!
  unless stmt
    arg, err = toutf(arg)
    stmt, err = insert!
    unless stmt
      log\error err

history = (source, destination, nr) ->
  nr = tonumber(nr) or 1
  -- TODO ignore messages that are commands
  stmt = dbh!\prepare [[
    SELECT
      *
      FROM log
      WHERE channel=?
      AND (type = 'PRIVMSG' OR type = 'ACTION')
      ORDER BY time
      DESC LIMIT ?
    ]]
  stmt\execute destination, nr
  out = {}
  for row in stmt\rows(true) -- true for column names
    out[#out+1] = row

  if #out == 1
    return out[1].message
  return out

lastlog = (source, destination, arg) ->
  nr = tonumber(nr) or 1
  arg = '%'..arg..'%'
  stmt = dbh!\prepare [[
    SELECT
      *
      FROM log
      WHERE channel=?
      AND (type = 'PRIVMSG' OR type = 'ACTION')
      AND message LIKE ?
      ORDER BY time DESC
      LIMIT 20
    ]]
  stmt\execute destination, arg
  out = {}
  for row in stmt\rows(true) -- true for column names
    out[#out+1] = row

  if #out == 1
    return out[1].message
  return out

seen = (source, destination, nick) =>
  nick = ivar2.util.trim(nick)
  stmt, err = dbh!\prepare [[
    SELECT
        *,
        date_trunc('second', time) as sectime,
        date_trunc('second', age(now(), date_trunc('second', time))) as ago
      FROM log
      WHERE nick = ?
      ORDER BY time DESC
      LIMIT 1
    ]]
  unless stmt
    print(err)
  stmt\execute nick
  out = {}
  for row in stmt\rows(true) -- true for column names
    out[#out+1] = row

  actions =
    'ACTION': 'talking in third person'
    'TOPIC': 'changing topic'
    'PRIVMSG': 'jabbering'
    'NICK': 'changing nick'
    'QUIT': 'quitting'
    'PART': 'leaving'
    'NOTICE': 'sending a notice'
    'MODE': 'changing a mode'
    'JOIN': 'joining'

  if #out == 1
    r = out[1]
    message = ''
    if #r.message
      message == " with message #{r.message}"
    say "#{ivar2.util.bold r.nick} was last observed #{ivar2.util.italic actions[r.type]}#{message} #{r.ago} ago (#{r.sectime} UTC)"
  else
    say "#{ivar2.util.bold nick} was not seen in my lifetime."

return {
  PRIVMSG: {
    '%pseen (.+)$': seen
    '%plast$': (source, destination) =>
      say history(source,destination,1)
    '%plastlog (.+)$': (source, destination, arg) =>
      out = {}
      for k,v in *lastlog(source, destination, arg)
        -- Skip commands
        unless k.message\match('^%p')
          table.insert out, string.format('<%s> %s', k.nick, k.message)
      say table.concat(out, ' ')
    (s, d, a) =>
      dblog @, 'PRIVMSG', s, d, a
  }
  NOTICE: {
    (s, d, a) =>
      dblog @, 'NOTICE', s, d, a
  }
  JOIN: {
    (s, d, a) =>
      dblog @, 'JOIN', s, d, a
  }
  PART: {
    (s, d, a) =>
      dblog @, 'PART', s, d, a
  }
  KICK: {
    (s, d, a) =>
      dblog @, 'KICK', s, d, a
  }
  MODE: {
    (s, d, a) =>
      dblog @, 'MODE', s, d, a
  }
  TOPIC: {
    (s, d, a) =>
      dblog @, 'TOPIC', s, d, a
  }
  NICK: {
    (s, d, a) =>
      dblog @, 'NICK', s, d, a
  }
  QUIT: {
    (s, d, a) =>
      dblog @, 'QUIT', s, d, a
  }
}

dbi = require 'DBI'
require'logging.console'
iconv = require'iconv'
iso2utf = iconv.new('UTF-8'.."//TRANSLIT", 'ISO-8859-1')
log = logging.console()

conn = false

schemas = {[[
  CREATE TABLE IF NOT EXISTS log (
    time timestamp DEFAULT now(),
    nick text,
    channel text,
    message text,
    type text
  );]],
  [[CREATE INDEX idx_time ON log(time);]],
  [[CREATE INDEX idx_channel ON log(channel);]]
}

toutf = (s) ->
  iso2utf\iconv(s)

connect = ->
  conn, err = DBI.Connect('PostgreSQL', ivar2.config.dbname, ivar2.config.dbuser, ivar2.config.dbpass, ivar2.config.dbhost, ivar2.config.dbport)
  unless conn
    log\error "Unable to connect to DB: #{err}"
    return

  conn\autocommit(true)

  --for s in *schemas
  --  a,b = DBI.Do(conn, s)

dbh = ->
  connect! unless conn

  -- Check if connection is alive
  alive = conn\ping!
  connect! unless alive

  success, err = DBI.Do(conn, 'SELECT NOW()')
  unless success
    log\error "SQL Connection :#{err}"
    connect!

  return conn

dblog = (type, source, destination, arg) =>
  nick = source.nick

  if type == 'PRIVMSG'
    -- action
    if arg\sub(1,1) == '\001' and arg\sub(-1) == '\001'
      arg = arg\sub(9, -2)
      type = 'ACTION'

  unless arg
    arg = ''

  insert = ->
    ins = dbh!\prepare('INSERT INTO log(nick,channel,message,type) values(?,?,?,?)')
    ins\execute(nick, destination, arg, type)

  -- First try to insert statement directly
  -- if that doesn't work; convert it from iso to utf and try again
  stmt, err = insert!
  unless stmt
    arg, err = toutf(arg)
    stmt, err = insert!
    unless stmt
      log\error err

history = (source, destination, nr) ->
  nr = tonumber(nr) or 1
  -- TODO ignore messages that are commands
  stmt = dbh!\prepare [[
    SELECT
      *
      FROM log
      WHERE channel=?
      AND (type = 'PRIVMSG' OR type = 'ACTION')
      ORDER BY time
      DESC LIMIT ?
    ]]
  stmt\execute destination, nr
  out = {}
  for row in stmt\rows(true) -- true for column names
    out[#out+1] = row

  if #out == 1
    return out[1].message
  return out

lastlog = (source, destination, arg) ->
  nr = tonumber(nr) or 1
  arg = '%'..arg..'%'
  stmt = dbh!\prepare [[
    SELECT
      *
      FROM log
      WHERE channel=?
      AND (type = 'PRIVMSG' OR type = 'ACTION')
      AND message LIKE ?
      ORDER BY time DESC
      LIMIT 20
    ]]
  stmt\execute destination, arg
  out = {}
  for row in stmt\rows(true) -- true for column names
    out[#out+1] = row

  if #out == 1
    return out[1].message
  return out

return {
  PRIVMSG: {
    '%plast$': (source, destination) =>
      say history(source,destination,1)
    '%plastlog (.+)$': (source, destination, arg) =>
      out = {}
      for k,v in *lastlog(source, destination, arg)
        -- Skip commands
        unless k.message\match('^%p')
          table.insert out, string.format('<%s> %s', k.nick, k.message)
      say table.concat(out, ' ')
    (s, d, a) =>
      dblog @, 'PRIVMSG', s, d, a
  }
  NOTICE: {
    (s, d, a) =>
      dblog @, 'NOTICE', s, d, a
  }
  JOIN: {
    (s, d, a) =>
      dblog @, 'JOIN', s, d, a
  }
  PART: {
    (s, d, a) =>
      dblog @, 'PART', s, d, a
  }
  KICK: {
    (s, d, a) =>
      dblog @, 'KICK', s, d, a
  }
  MODE: {
    (s, d, a) =>
      dblog @, 'MODE', s, d, a
  }
  TOPIC: {
    (s, d, a) =>
      dblog @, 'TOPIC', s, d, a
  }
  NICK: {
    (s, d, a) =>
      dblog @, 'NICK', s, d, a
  }
  QUIT: {
    (s, d, a) =>
      dblog @, 'QUIT', s, d, a
  }
}