Fix color instantiation

Fix color instantiation

import java, Font, Image, Quad from sauce3

Constants = require "sauce3/wrappers"
Color     = java.require "com.badlogic.gdx.graphics.Color"
Gdx       = java.require "com.badlogic.gdx.Gdx"
GL20      = java.require "com.badlogic.gdx.graphics.GL20"
Matrix4   = java.require "com.badlogic.gdx.math.Matrix4"
Sauce3VM   = java.require "sauce3.Sauce3VM"

OrthographicCamera = java.require "com.badlogic.gdx.graphics.OrthographicCamera"
ShapeRender        = java.require "com.badlogic.gdx.graphics.glutils.ShapeRenderer"
SpriteBatch        = java.require "com.badlogic.gdx.graphics.g2d.SpriteBatch"

shader = SpriteBatch\createDefaultShader!

batch  = java.new SpriteBatch, 1000, shader
shapes = java.new ShapeRender

font = Font!

shapes\setAutoShapeType true

matrix = java.new Matrix4
color  = java.new Color, 1, 1, 1, 1

background = java.new Color, 0, 0, 0, 1
blending   = "alpha"

batch\setColor color
shapes\setColor color
font.font\setColor color

camera = java.new OrthographicCamera
camera\setToOrtho true

batch\setProjectionMatrix camera.combined
shapes\setProjectionMatrix camera.combined

matrix_dirty = false

check = (texture_based) ->
  if texture_based
    if matrix_dirty
      batch\setTransformMatrix matrix
      matrix_dirty = false

    if shapes\isDrawing!
      Sauce3VM.util\endShapes shapes
    unless batch\isDrawing!
      batch\begin!
  else
    if matrix_dirty
      shapes\setTransformMatrix matrix
      matrix_dirty = false

    if batch\isDrawing!
      Sauce3VM.util\endBatch batch
    unless shapes\isDrawing!
      shapes\begin!

arc = (mode, x, y, radius, a1, a2) ->
  check false

  shapes\set Constants.shapes[mode]
  shapes\arc x, y, radius, (math.deg a1), math.deg a2

circle = (mode, x, y, radius) ->
  check false

  shapes\set Constants.shapes[mode]
  shapes\circle x, y, radius

clear = ->
  Gdx.gl\glClearColor background.r, background.g, background.b
  Gdx.gl\glClear GL20.GL_COLOR_BUFFER_BIT

draw = (image, x = 0, y = 0, r = 0, sx = 1, sy = 1, ox = 0, oy = 0) ->
  check true

  w = image\getWidth!
  h = image\getHeight!

  src_x = 0
  src_y = 0

  src_w = w
  src_h = h

  x -= ox
  y -= oy

  batch\draw image.texture, x, y, ox, oy, w, h, sx, sy, r, srx_x, srx_y, src_w, src_h, false, true

draw_q = (image, quad, x = 0, y = 0, r = 0, sx = 1, sy = 1, ox = 0, oy = 0) ->
  check true

  w = quad.width
  h = quad.height

  src_x = quad.x
  src_y = quad.y

  src_w = quad.sw
  src_h = quad.sh

  x -= ox
  y -= oy

  batch\draw image.texture, x, y, ox, oy, w, h, sx, sy, r, src_x, src_y, src_w, src_h, false, true

ellipse = (mode, x, y, width, height) ->
  check false

  shapes\set Constants.shapes[mode]
  shapes\ellipse x, y, width, height

line = (x1, y1, x2, y2) ->
  check false
  shapes\line x1, y1, x2, y2

origin = ->
  matrix\idt!
  matrix_dirty = true

point = (x, y) ->
  check false
  shapes\point x, y, 0

polygon = (mode, ...) ->
  check false
  shapes\set Constants.shapes[mode]

  args = table.pack ...

  if type args[1] == "table"
    shapes\polygon args[1]
  else
    shapes\polygon args

present = ->
  if shapes\isDrawing!
    Sauce3VM.util\endShapes shapes
  if batch\isDrawing!
    Sauce3VM.util\endBatch batch

print = (text, x = 0, y = 0, r = 0, sx = 1, sy = 1, ox = 0, oy = 0) ->
  check true

  local tmp

  unless r == 0
    tmp = batch\getTransformMatrix!
    translate x, y
    rotate r
    translate -x, -y

    batch\setTransformMatrix matrix
    matrix_dirty = false

  font.font\getData!\setScale sx, sy
  font.font\draw batch, text, x - ox * sx, y - oy * sy

  unless r == 0
    translate x, y
    rotate -r
    translate -x, -y

    batch\setTransformMatrix tmp
    matrix_dirty = false

prin_f = (text, width = 0, align = "left", x = 0, y = 0, r = 0, sx = 1, sy = 1, ox = 0, oy = 0) ->
  check true

  tmp = nil

  unless r == 0
    tmp = batch\getTransformMatrix!
    translate x, y
    rotate r
    translate -x, -y

    batch\setTransformMatrix matrix
    matrix_dirty = false

  font.font\getData!\setScale sx, -sy
  font.font\draw batch, text, x - ox * sx, y - oy * sy, width, Constants.aligns[align], true

  unless r == 0
    translate x, y
    rotate -r
    translate -x, -y

    batch\setTransformMatrix tmp
    matrix_dirty = false

rectangle = (mode, x, y, width, height) ->
  check false

  shapes\set Constants.shapes[mode]
  shapes\rect x, y, width, height

reset = ->
  shader = SpriteBatch\createDefaultShader!
  batch\setShader shader

  background\set 0.4, 0.3, 0.4, 1
  color\set 1, 1, 1, 1

  batch\setColor color
  shapes\setColor color
  font.font\setColor color

  matrix\idt!
  matrix_dirty = true

translate = (x, y) ->
  matrix\translate x, y
  matrix_dirty = true

rotate = (radians) ->
  matrix\rotate 0, 0, 1, math.deg radians
  matrix_dirty = true

scale = (sx, sy) ->
  matrix\scale sx, sy, 1
  matrix_dirty = true

get_background_color = ->
  background.r * 255, background.g * 255, background.b * 255

get_blend_mode = ->
  blending

get_color = ->
  color.r * 255, color.g * 255, color.b * 255, color.a * 255

get_font = ->
  font

set_background_color = (r, g, b, a = 255) ->
  background\set r / 255, g / 255, b / 255, a / 255

set_blend_mode = (mode) ->
  blending = mode

  blend_mode = Constants[blending]
  batch\setBlendFunction blend_mode[1], blend_mode[2]

set_font = (v) ->
  font = v

set_new_font = (path, size, f_type) ->
  font = new_font path, size, f_type

new_font = (path, size, f_type) ->
  (Font path, size, f_type)

new_image = (path, format, f_type) ->
  (Image path, format, f_type)

new_quad = (x, y, width, height, sx, sy) ->
  (Quad x, y, width, height, sx, sy)

{
  :arc
  :circle
  :clear
  :draw
  :draw_q
  :ellipse
  :get_background_color
  :get_blendMode
  :get_color
  :get_font
  :line
  :origin
  :point
  :polygon
  :present
  :print
  :print_f
  :rectangle
  :reset
  :translate
  :rotate
  :scale
  :set_background_color
  :set_blend_mode
  :set_color
  :set_font
  :set_new_font
  :new_font
  :new_image
  :new_quad
}

import java, Font, Image, Quad from sauce3

Constants = require "sauce3/wrappers"
Color     = java.require "com.badlogic.gdx.graphics.Color"
Gdx       = java.require "com.badlogic.gdx.Gdx"
GL20      = java.require "com.badlogic.gdx.graphics.GL20"
Matrix4   = java.require "com.badlogic.gdx.math.Matrix4"
Sauce3VM   = java.require "sauce3.Sauce3VM"

OrthographicCamera = java.require "com.badlogic.gdx.graphics.OrthographicCamera"
ShapeRender        = java.require "com.badlogic.gdx.graphics.glutils.ShapeRenderer"
SpriteBatch        = java.require "com.badlogic.gdx.graphics.g2d.SpriteBatch"

shader = SpriteBatch\createDefaultShader!

batch  = java.new SpriteBatch, 1000, shader
shapes = java.new ShapeRender

font = Font!

shapes\setAutoShapeType true

matrix = java.new Matrix4
color  = java.new Color, 1, 1, 1, 1

background = java.new Color 0, 0, 0, 1
blending   = "alpha"

batch\setColor color
shapes\setColor color
font.font\setColor color

camera = java.new OrthographicCamera
camera\setToOrtho true

batch\setProjectionMatrix camera.combined
shapes\setProjectionMatrix camera.combined

matrix_dirty = false

check = (texture_based) ->
  if texture_based
    if matrix_dirty
      batch\setTransformMatrix matrix
      matrix_dirty = false

    if shapes\isDrawing!
      Sauce3VM.util\endShapes shapes
    unless batch\isDrawing!
      batch\begin!
  else
    if matrix_dirty
      shapes\setTransformMatrix matrix
      matrix_dirty = false

    if batch\isDrawing!
      Sauce3VM.util\endBatch batch
    unless shapes\isDrawing!
      shapes\begin!

arc = (mode, x, y, radius, a1, a2) ->
  check false

  shapes\set Constants.shapes[mode]
  shapes\arc x, y, radius, (math.deg a1), math.deg a2

circle = (mode, x, y, radius) ->
  check false

  shapes\set Constants.shapes[mode]
  shapes\circle x, y, radius

clear = ->
  Gdx.gl\glClearColor background.r, background.g, background.b
  Gdx.gl\glClear GL20.GL_COLOR_BUFFER_BIT

draw = (image, x = 0, y = 0, r = 0, sx = 1, sy = 1, ox = 0, oy = 0) ->
  check true

  w = image\getWidth!
  h = image\getHeight!

  src_x = 0
  src_y = 0

  src_w = w
  src_h = h

  x -= ox
  y -= oy

  batch\draw image.texture, x, y, ox, oy, w, h, sx, sy, r, srx_x, srx_y, src_w, src_h, false, true

draw_q = (image, quad, x = 0, y = 0, r = 0, sx = 1, sy = 1, ox = 0, oy = 0) ->
  check true

  w = quad.width
  h = quad.height

  src_x = quad.x
  src_y = quad.y

  src_w = quad.sw
  src_h = quad.sh

  x -= ox
  y -= oy

  batch\draw image.texture, x, y, ox, oy, w, h, sx, sy, r, src_x, src_y, src_w, src_h, false, true

ellipse = (mode, x, y, width, height) ->
  check false

  shapes\set Constants.shapes[mode]
  shapes\ellipse x, y, width, height

line = (x1, y1, x2, y2) ->
  check false
  shapes\line x1, y1, x2, y2

origin = ->
  matrix\idt!
  matrix_dirty = true

point = (x, y) ->
  check false
  shapes\point x, y, 0

polygon = (mode, ...) ->
  check false
  shapes\set Constants.shapes[mode]

  args = table.pack ...

  if type args[1] == "table"
    shapes\polygon args[1]
  else
    shapes\polygon args

present = ->
  if shapes\isDrawing!
    Sauce3VM.util\endShapes shapes
  if batch\isDrawing!
    Sauce3VM.util\endBatch batch

print = (text, x = 0, y = 0, r = 0, sx = 1, sy = 1, ox = 0, oy = 0) ->
  check true

  local tmp

  unless r == 0
    tmp = batch\getTransformMatrix!
    translate x, y
    rotate r
    translate -x, -y

    batch\setTransformMatrix matrix
    matrix_dirty = false

  font.font\getData!\setScale sx, sy
  font.font\draw batch, text, x - ox * sx, y - oy * sy

  unless r == 0
    translate x, y
    rotate -r
    translate -x, -y

    batch\setTransformMatrix tmp
    matrix_dirty = false

prin_f = (text, width = 0, align = "left", x = 0, y = 0, r = 0, sx = 1, sy = 1, ox = 0, oy = 0) ->
  check true

  tmp = nil

  unless r == 0
    tmp = batch\getTransformMatrix!
    translate x, y
    rotate r
    translate -x, -y

    batch\setTransformMatrix matrix
    matrix_dirty = false

  font.font\getData!\setScale sx, -sy
  font.font\draw batch, text, x - ox * sx, y - oy * sy, width, Constants.aligns[align], true

  unless r == 0
    translate x, y
    rotate -r
    translate -x, -y

    batch\setTransformMatrix tmp
    matrix_dirty = false

rectangle = (mode, x, y, width, height) ->
  check false

  shapes\set Constants.shapes[mode]
  shapes\rect x, y, width, height

reset = ->
  shader = SpriteBatch\createDefaultShader!
  batch\setShader shader

  background\set 0.4, 0.3, 0.4, 1
  color\set 1, 1, 1, 1

  batch\setColor color
  shapes\setColor color
  font.font\setColor color

  matrix\idt!
  matrix_dirty = true

translate = (x, y) ->
  matrix\translate x, y
  matrix_dirty = true

rotate = (radians) ->
  matrix\rotate 0, 0, 1, math.deg radians
  matrix_dirty = true

scale = (sx, sy) ->
  matrix\scale sx, sy, 1
  matrix_dirty = true

get_background_color = ->
  background.r * 255, background.g * 255, background.b * 255

get_blend_mode = ->
  blending

get_color = ->
  color.r * 255, color.g * 255, color.b * 255, color.a * 255

get_font = ->
  font

set_background_color = (r, g, b, a = 255) ->
  background\set r / 255, g / 255, b / 255, a / 255

set_blend_mode = (mode) ->
  blending = mode

  blend_mode = Constants[blending]
  batch\setBlendFunction blend_mode[1], blend_mode[2]

set_font = (v) ->
  font = v

set_new_font = (path, size, f_type) ->
  font = new_font path, size, f_type

new_font = (path, size, f_type) ->
  (Font path, size, f_type)

new_image = (path, format, f_type) ->
  (Image path, format, f_type)

new_quad = (x, y, width, height, sx, sy) ->
  (Quad x, y, width, height, sx, sy)

{
  :arc
  :circle
  :clear
  :draw
  :draw_q
  :ellipse
  :get_background_color
  :get_blendMode
  :get_color
  :get_font
  :line
  :origin
  :point
  :polygon
  :present
  :print
  :print_f
  :rectangle
  :reset
  :translate
  :rotate
  :scale
  :set_background_color
  :set_blend_mode
  :set_color
  :set_font
  :set_new_font
  :new_font
  :new_image
  :new_quad
}


Crosshairs

Crosshairs

-- Copyright (C) 2017-2018 DBot

-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at

--     http://www.apache.org/licenses/LICENSE-2.0

-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

ENABLE_FULLBRIGHT = CreateConVar('ppm2_editor_fullbright', '1', {FCVAR_ARCHIVE}, 'Disable lighting in editor')

local EDIT_TREE

inRadius = (val, min, max) -> val >= min and val <= max
inBox = (pointX, pointY, x, y, w, h) -> inRadius(pointX, x - w, x + w) and inRadius(pointY, y - h, y + h)

-- loal
surface.CreateFont('PPM2BackButton', {
	font: 'Roboto'
	size: ScreenScale(24)\floor()
	weight: 600
})

import HUDCommons from DLib
drawCrosshair = (x, y, radius = ScreenScale(10), arcColor = Color(255, 255, 255), boxesColor = Color(200, 200, 200)) ->
	x -= radius / 2
	y -= radius / 2

	HUDCommons.DrawCircleHollow(x, y, radius, radius * 2, radius * 0.2, arcColor)
	h = radius * 0.1
	w = radius * 0.6
	surface.SetDrawColor(boxesColor)
	surface.DrawRect(x - w / 2, y + radius / 2 - h / 2, w, h)
	surface.DrawRect(x + radius / 2 + w / 2, y + radius / 2 - h / 2, w, h)

	surface.DrawRect(x + radius / 2 - h / 2, y - w / 2, h, w)
	surface.DrawRect(x + radius / 2 - h / 2, y + radius / 2 + w / 3, h, w)

MODEL_BOX_PANEL = {
	SEQUENCE_STAND: 22
	PONY_VEC_Z: 64 * .7

	SEQUENCES: {
		'Standing':    22
		'Moving':      316
		'Walking':     232
		'Sit':         202
		'Swim':        370
		'Run':         328
		'Crouch walk': 286
		'Crouch':      76
		'Jump':        160
	}

	Init: =>
		@animRate = 1
		@seq = @SEQUENCE_STAND
		@hold = false
		@holdOnPoint = false
		@holdRightClick = false
		@canHold = true
		@lastTick = RealTimeL()

		@holdLast = 0
		@mouseX, @mouseY = 0, 0

		@crosshairCircleInactive = Color(150, 150, 150)
		@crosshairBoxInactive = Color(100, 100, 100)

		@crosshairCircleHovered = Color(137, 195, 196)
		@crosshairBoxHovered = Color(200, 200, 200)

		@crosshairCircleSelected = Color(0, 0, 0, 0)
		@crosshairBoxSelected = Color(211, 255, 192)

		@angle = Angle(0, 0, 0)
		@distToPony = 90
		@trackBone = -1
		@trackBoneName = 'LrigSpine2'
		@trackAttach = -1
		@trackAttachName = ''
		@shouldAutoTrack = true
		@autoTrackPos = Vector(0, 0, 0)
		@fixedDistanceToPony = 100

		@vectorPos = Vector(@fixedDistanceToPony, 0, 0)
		@targetPos = Vector(0, 0, @PONY_VEC_Z * .7)

		@SetCursor('none')
		@SetMouseInputEnabled(true)

		@buildingModel = ClientsideModel('models/ppm/ppm2_stage.mdl', RENDERGROUP_OTHER)
		@buildingModel\SetNoDraw(true)
		@buildingModel\SetModelScale(0.9)

		with @seqButton = vgui.Create('DComboBox', @)
			\SetSize(120, 20)
			\SetValue('Standing')
			\AddChoice(choice, num) for choice, num in pairs @SEQUENCES
			.OnSelect = (pnl = box, index = 1, value = '', data = value) -> @SetSequence(data)

		with @emotesPanel = PPM2.CreateEmotesPanel(@, @model, false)
			\SetPos(10, 40)
			\SetMouseInputEnabled(true)
			\SetVisible(true)

	UpdateAttachsIDs: =>
		@trackBone = @model\LookupBone(@trackBoneName) or -1 if @trackBoneName ~= ''
		@trackAttach = @model\LookupAttachment(@trackAttachName) or -1 if @trackAttachName ~= ''
	GetTrackedPosition: =>
		return @autoTrackPos if @shouldAutoTrack
		return @targetPos

	PerformLayout: (w = 0, h = 0) =>
		@seqButton\SetPos(10, 10)
		@emotesPanel\SetPos(10, 40)

	OnMousePressed: (code = MOUSE_LEFT) =>
		if code == MOUSE_LEFT and @canHold
			if not @selectPoint
				@hold = true
				@SetCursor('sizeall')
				@holdLast = RealTimeL() + .1
				@mouseX, @mouseY = gui.MousePos()
			else
				@holdOnPoint = true
				@SetCursor('crosshair')
		elseif code == MOUSE_RIGHT
			@holdRightClick = true

	OnMouseReleased: (code = MOUSE_LEFT) =>
		if code == MOUSE_LEFT and @canHold
			@holdOnPoint = false
			@SetCursor('none')

			if not @selectPoint
				@hold = false
			else
				@PushMenu(@selectPoint.linkTable)

		elseif code == MOUSE_RIGHT
			@holdRightClick = false

	SetController: (val) => @controller = val

	OnMouseWheeled: (wheelDelta = 0) =>
		if @canHold
			@distToPony = math.clamp(@distToPony - wheelDelta * 10, 20, 150)

	GetModel: => @model
	GetSequence: => @seq
	GetAnimRate: => @animRate
	SetAnimRate: (val = 1) => @animRate = val
	SetSequence: (val = @SEQUENCE_STAND) =>
		@seq = val
		@model\SetSequence(@seq) if IsValid(@model)

	ResetSequence: => @SetSequence(@SEQUENCE_STAND)
	ResetSeq: => @SetSequence(@SEQUENCE_STAND)

	GetParentTarget: => @parentTarget
	SetParentTarget: (val) => @parentTarget = val

	PushMenu: (menu) =>
		table.insert(@stack, menu)
		if not IsValid(@backButton)
			with @backButton = vgui.Create('DButton', @)
				x, y = @GetPos()
				w, h = @GetSize()
				\SetText('↩')
				\SetFont('PPM2BackButton')
				\SizeToContents()
				W, H = \GetSize()
				W += ScreenScale(8)
				\SetSize(W, H)
				\SetPos(w - ScreenScale(6) - W, ScreenScale(4))
				.DoClick = -> @PopMenu()
		if @InMenu()
			x, y = @GetPos()
			frame = @GetParentTarget() or @GetParent() or @
			W, H = @GetSize()
			width = ScreenScale(120)
			with @settingsPanel = vgui.Create('PPM2SettingsBase', frame)
				\SetPos(x, y)
				\SetSize(width, H)
				menu.populate(@settingsPanel)
		return @

	PopMenu: =>
		assert(#@stack > 1, 'invalid stack size to pop from')
		@settingsPanel\Remove() if @InMenu() and IsValid(@settingsPanel)
		table.remove(@stack)
		@backButton\Remove() if #@stack == 1 and IsValid(@backButton)
		return @

	CurrentMenu: => @stack[#@stack]
	InRoot: => #@stack == 1
	InSelection: => @CurrentMenu().type == 'level'
	InMenu: => @CurrentMenu().type == 'menu'

	ResetModel: (ponydata, model = 'models/ppm/player_default_base_new.mdl') =>
		@model\Remove() if IsValid(@model)

		with @model = ClientsideModel(model)
			\SetNoDraw(true)
			.__PPM2_PonyData = ponydata
			\SetSequence(@seq)
			\FrameAdvance(0)
			\SetPos(Vector())
			\InvalidateBoneCache()

		@UpdateAttachsIDs()
		return @model

	Think: =>
		rtime = RealTimeL()
		delta = rtime - @lastTick
		@lastTick = rtime

		if IsValid(@model)
			@model\FrameAdvance(delta * @animRate)
			@model\SetPlaybackRate(1)
			@model\SetPoseParameter('move_x', 1)

			if @shouldAutoTrack
				if @trackAttach ~= -1
					{:Ang, :Pos} = @model\GetAttachment(@trackAttach)
					@autoTrackPos = Pos or Vector()
				elseif @trackBone ~= -1
					@autoTrackPos = @model\GetBonePosition(@trackBone) or Vector()
				else
					@shouldAutoTrack = false

		@hold = @IsHovered() if @hold

		if @hold
			x, y = gui.MousePos()
			deltaX, deltaY = x - @mouseX, y - @mouseY
			@mouseX, @mouseY = x, y
			{:pitch, :yaw, :roll} = @angle
			yaw -= deltaX * .5
			pitch = math.clamp(pitch - deltaY * .5, -45, 45)
			@angle = Angle(pitch, yaw % 360, roll)

		@vectorPos = Vector(@fixedDistanceToPony, 0, 0)
		@vectorPos\Rotate(@angle)
		@drawAngle = Angle(-@angle.p, @angle.y - 180)

	FLOOR_VECTOR: Vector(0, 0, -30)
	FLOOR_ANGLE: Vector(0, 0, 1)

	DRAW_WALLS: {
		{Vector(-4000, 0, 900), Vector(1, 0, 0), 8000, 2000}
		{Vector(4000, 0, 900), Vector(-1, 0, 0), 8000, 2000}
		{Vector(0, -4000, 900), Vector(0, 1, 0), 8000, 2000}
		{Vector(0, 4000, 900), Vector(0, -1, 0), 8000, 2000}
		{Vector(0, 0, 900), Vector(0, 0, -1), 8000, 8000}
	}

	WALL_COLOR: Color() - 255
	FLOOR_COLOR: Color() - 255
	EMPTY_VECTOR: Vector()

	Paint: (w = 0, h = 0) =>
		surface.SetDrawColor(0, 0, 0)
		surface.DrawRect(0, 0, w, h)
		return if not IsValid(@model)
		x, y = @LocalToScreen(0, 0)
		drawpos = @vectorPos + @GetTrackedPosition()
		cam.Start3D(drawpos, @drawAngle, @distToPony, x, y, w, h)

		if @holdRightClick
			@model\SetEyeTarget(drawpos)
			turnpitch, turnyaw = DLib.combat.turnAngle(@EMPTY_VECTOR, drawpos, Angle())

			if not inRadius(turnyaw, -20, 20)
				if turnyaw < 0
					@model\SetPoseParameter('head_yaw', turnyaw + 20)
				else
					@model\SetPoseParameter('head_yaw', turnyaw - 20)
			else
				@model\SetPoseParameter('head_yaw', 0)

			turnpitch = turnpitch + 20
			if not inRadius(turnpitch, -10, 0)
				if turnpitch < 0
					@model\SetPoseParameter('head_pitch', turnpitch + 10)
				else
					@model\SetPoseParameter('head_pitch', turnpitch)
			else
				@model\SetPoseParameter('head_pitch', 0)
		else
			@model\SetEyeTarget(@EMPTY_VECTOR)
			@model\SetPoseParameter('head_yaw', 0)
			@model\SetPoseParameter('head_pitch', 0)

		render.DrawQuadEasy(@FLOOR_VECTOR, @FLOOR_ANGLE, 7000, 7000, @FLOOR_COLOR)

		for {pos, ang, w, h} in *@DRAW_WALLS
			render.DrawQuadEasy(pos, ang, w, h, @WALL_COLOR)

		if ENABLE_FULLBRIGHT\GetBool()
			render.SuppressEngineLighting(true)
			render.ResetModelLighting(1, 1, 1)
			render.SetColorModulation(1, 1, 1)

		@buildingModel\DrawModel()
		ctrl = @controller\GetRenderController()

		if bg = @controller\GetBodygroupController()
			bg\ApplyBodygroups()

		with @model\PPMBonesModifier()
			\ResetBones()
			hook.Call('PPM2.SetupBones', nil, @model, @controller)
			\Think(true)

		with ctrl
			\DrawModels()
			\HideModels(true)
			\PreDraw(@model)

		@model\DrawModel()
		ctrl\PostDraw(@model)

		render.SuppressEngineLighting(false) if ENABLE_FULLBRIGHT\GetBool()

		menu = @CurrentMenu()
		@drawPoints = false
		lx, ly = x, y

		if type(menu.points) == 'table'
			@drawPoints = true
			@pointsData = for point in *menu.points
				vecpos = point.getpos(@model)
				position = vecpos\ToScreen()
				{position, point, vecpos\Distance(drawpos)}
		elseif @InMenu() and menu.getpos
			{:x, :y} = menu.getpos(@model)\ToScreen(drawpos)
			x, y = x - lx, y - ly

		cam.End3D()

		if @drawPoints
			mx, my = gui.MousePos()
			mx, my = mx - lx, my - ly
			radius = ScreenScale(20)
			local drawnSelected
			min = 9999

			for pointdata in *@pointsData
				{:x, :y} = pointdata[1]
				x, y = x - lx, y - ly
				pointdata[1].x, pointdata[1].y = x, y

				if inBox(mx, my, x, y, radius, radius)
					if min > pointdata[3]
						drawnSelected = pointdata
						min = pointdata[3]

			@selectPoint = drawnSelected and drawnSelected[2] or false

			for pointdata in *@pointsData
				{:x, :y} = pointdata[1]

				if pointdata == drawnSelected
					drawCrosshair(x, y, radius, @crosshairCircleHovered, @crosshairBoxHovered)
				else
					drawCrosshair(x, y, radius, @crosshairCircleInactive, @crosshairBoxInactive)

			if not @hold and not @holdOnPoint
				if drawnSelected
					@SetCursor('hand')
				else
					@SetCursor('none')
		else
			@selectPoint = false
			if @InMenu() and menu.getpos
				radius = ScreenScale(20)
				drawCrosshair(x, y, radius, @crosshairCircleSelected, @crosshairBoxSelected)

	OnRemove: =>
		@model\Remove() if IsValid(@model)
		@buildingModel\Remove() if IsValid(@buildingModel)
}

vgui.Register('PPM2Model2Panel', MODEL_BOX_PANEL, 'EditablePanel')

EDIT_TREE = {
	type: 'level'
	name: 'Pony overview'

	points: {
		{
			type: 'bone'
			target: 'LrigScull'
			link: 'head_submenu'
		}

		{
			type: 'bone'
			target: 'LrigSpine1'
			link: 'spine_length'
		}

		{
			type: 'bone'
			target: 'Tail01'
			link: 'tail'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_FL_Metacarpus'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_FR_Metacarpus'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_BR_LargeCannon'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_BL_LargeCannon'
			link: 'legs_submenu'
		}
	}

	children: {
		head_submenu: {
			type: 'level'
			name: 'Head anatomy'

			points: {
				{
					type: 'attach'
					target: 'eyes'
					link: 'eyes'
				}
			}

			children: {
				eyes: {
					type: 'menu'
					populate: =>
				}
			}
		}

		legs_submenu: {
			type: 'level'
			name: 'Hooves anatomy'

			points: {

			}

			children: {

			}
		}
	}
}

patchSubtree = (node) ->
	if type(node.children) == 'table'
		for childID, child in pairs node.children
			child.id = childID
			patchSubtree(child)

	if type(node.points) == 'table'
		for point in *node.points
			point.addvector = point.addvector or Vector()

			switch point.type
				when 'point'
					point.getpos = => Vector(point.target)
				when 'bone'
					point.getpos = =>
						if not point.targetID or point.targetID == -1
							point.targetID = @LookupBone(point.target) or -1

						if point.targetID == -1
							return Vector(point.addvector)
						else
							return @GetBonePosition(point.targetID) + point.addvector
				when 'attach'
					point.getpos = =>
						if not point.targetID or point.targetID == -1
							point.targetID = @LookupAttachment(point.target) or -1

						if point.targetID == -1
							return Vector(point.addvector)
						else
							{:Pos, :Ang} = @GetAttachment(point.targetID)
							return Pos and (Pos + point.addvector) or Vector(point.addvector)

			if type(node.children) == 'table'
				point.linkTable = node.children[point.link]
				if type(point.linkTable) == 'table' and point.linkTable.type == 'menu'
					point.linkTable.getpos = point.getpos

EDIT_TREE.id = 'root'
patchSubtree(EDIT_TREE)

ppm2_editor3 = ->
	if IsValid(PPM2.EDITOR3)
		PPM2.EDITOR3\SetVisible(true)
		PPM2.EDITOR3\MakePopup()
		return PPM2.EDITOR3
	PPM2.EDITOR3 = vgui.Create('DLib_Window')
	self = PPM2.EDITOR3
	@SetSize(ScrWL(), ScrHL())
	@SetPos(0, 0)
	@MakePopup()
	@SetTitle('PPM/2 Editor/3')
	@SetDraggable(false)
	@RemoveResize()

	with @modelPanel = vgui.Create('PPM2Model2Panel', @)
		\Dock(FILL)
		\DockMargin(3, 3, 3, 3)

	copy = PPM2.GetMainData()\Copy()
	ply = LocalPlayer()
	ent = @modelPanel\ResetModel()

	controller = copy\CreateCustomController(ent)
	controller\SetFlexLerpMultiplier(1.3)
	copy\SetController(controller)

	@modelPanel\SetController(controller)
	controller\SetupEntity(ent)
	controller\SetDisableTask(true)

	@modelPanel.stack = {EDIT_TREE}
	@modelPanel\SetParentTarget(@)

concommand.Add 'ppm2_editor3', ppm2_editor3

-- Copyright (C) 2017-2018 DBot

-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at

--     http://www.apache.org/licenses/LICENSE-2.0

-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

ENABLE_FULLBRIGHT = CreateConVar('ppm2_editor_fullbright', '1', {FCVAR_ARCHIVE}, 'Disable lighting in editor')

local EDIT_TREE

inRadius = (val, min, max) -> val >= min and val <= max
inBox = (pointX, pointY, x, y, w, h) -> inRadius(pointX, x - w, x + w) and inRadius(pointY, y - h, y + h)

-- loal
surface.CreateFont('PPM2BackButton', {
	font: 'Roboto'
	size: ScreenScale(24)\floor()
	weight: 600
})

MODEL_BOX_PANEL = {
	SEQUENCE_STAND: 22
	PONY_VEC_Z: 64 * .7

	SEQUENCES: {
		'Standing':    22
		'Moving':      316
		'Walking':     232
		'Sit':         202
		'Swim':        370
		'Run':         328
		'Crouch walk': 286
		'Crouch':      76
		'Jump':        160
	}

	Init: =>
		@animRate = 1
		@seq = @SEQUENCE_STAND
		@hold = false
		@holdOnPoint = false
		@holdRightClick = false
		@canHold = true
		@lastTick = RealTimeL()

		@holdLast = 0
		@mouseX, @mouseY = 0, 0

		@angle = Angle(0, 0, 0)
		@distToPony = 90
		@trackBone = -1
		@trackBoneName = 'LrigSpine2'
		@trackAttach = -1
		@trackAttachName = ''
		@shouldAutoTrack = true
		@autoTrackPos = Vector(0, 0, 0)
		@fixedDistanceToPony = 100

		@vectorPos = Vector(@fixedDistanceToPony, 0, 0)
		@targetPos = Vector(0, 0, @PONY_VEC_Z * .7)

		@SetCursor('none')
		@SetMouseInputEnabled(true)

		@buildingModel = ClientsideModel('models/ppm/ppm2_stage.mdl', RENDERGROUP_OTHER)
		@buildingModel\SetNoDraw(true)
		@buildingModel\SetModelScale(0.9)

		with @seqButton = vgui.Create('DComboBox', @)
			\SetSize(120, 20)
			\SetValue('Standing')
			\AddChoice(choice, num) for choice, num in pairs @SEQUENCES
			.OnSelect = (pnl = box, index = 1, value = '', data = value) -> @SetSequence(data)

		with @emotesPanel = PPM2.CreateEmotesPanel(@, @model, false)
			\SetPos(10, 40)
			\SetMouseInputEnabled(true)
			\SetVisible(true)

	UpdateAttachsIDs: =>
		@trackBone = @model\LookupBone(@trackBoneName) or -1 if @trackBoneName ~= ''
		@trackAttach = @model\LookupAttachment(@trackAttachName) or -1 if @trackAttachName ~= ''
	GetTrackedPosition: =>
		return @autoTrackPos if @shouldAutoTrack
		return @targetPos

	PerformLayout: (w = 0, h = 0) =>
		@seqButton\SetPos(10, 10)
		@emotesPanel\SetPos(10, 40)

	OnMousePressed: (code = MOUSE_LEFT) =>
		if code == MOUSE_LEFT and @canHold
			if not @selectPoint
				@hold = true
				@SetCursor('sizeall')
				@holdLast = RealTimeL() + .1
				@mouseX, @mouseY = gui.MousePos()
			else
				@holdOnPoint = true
				@SetCursor('crosshair')
		elseif code == MOUSE_RIGHT
			@holdRightClick = true

	OnMouseReleased: (code = MOUSE_LEFT) =>
		if code == MOUSE_LEFT and @canHold
			@holdOnPoint = false
			@SetCursor('none')

			if not @selectPoint
				@hold = false
			else
				@PushMenu(@selectPoint.linkTable)

		elseif code == MOUSE_RIGHT
			@holdRightClick = false

	SetController: (val) => @controller = val

	OnMouseWheeled: (wheelDelta = 0) =>
		if @canHold
			@distToPony = math.clamp(@distToPony - wheelDelta * 10, 20, 150)

	GetModel: => @model
	GetSequence: => @seq
	GetAnimRate: => @animRate
	SetAnimRate: (val = 1) => @animRate = val
	SetSequence: (val = @SEQUENCE_STAND) =>
		@seq = val
		@model\SetSequence(@seq) if IsValid(@model)

	ResetSequence: => @SetSequence(@SEQUENCE_STAND)
	ResetSeq: => @SetSequence(@SEQUENCE_STAND)

	GetParentTarget: => @parentTarget
	SetParentTarget: (val) => @parentTarget = val

	PushMenu: (menu) =>
		table.insert(@stack, menu)
		if not IsValid(@backButton)
			with @backButton = vgui.Create('DButton', @)
				x, y = @GetPos()
				w, h = @GetSize()
				\SetText('↩')
				\SetFont('PPM2BackButton')
				\SizeToContents()
				W, H = \GetSize()
				W += ScreenScale(8)
				\SetSize(W, H)
				\SetPos(w - ScreenScale(6) - W, ScreenScale(4))
				.DoClick = -> @PopMenu()
		if @InMenu()
			x, y = @GetPos()
			frame = @GetParentTarget() or @GetParent() or @
			W, H = @GetSize()
			width = ScreenScale(120)
			with @settingsPanel = vgui.Create('PPM2SettingsBase', frame)
				\SetPos(x, y)
				\SetSize(width, H)
				menu.populate(@settingsPanel)
		return @

	PopMenu: =>
		assert(#@stack > 1, 'invalid stack size to pop from')
		@settingsPanel\Remove() if @InMenu() and IsValid(@settingsPanel)
		table.remove(@stack)
		@backButton\Remove() if #@stack == 1 and IsValid(@backButton)
		return @

	CurrentMenu: => @stack[#@stack]
	InRoot: => #@stack == 1
	InSelection: => @CurrentMenu().type == 'level'
	InMenu: => @CurrentMenu().type == 'menu'

	ResetModel: (ponydata, model = 'models/ppm/player_default_base_new.mdl') =>
		@model\Remove() if IsValid(@model)

		with @model = ClientsideModel(model)
			\SetNoDraw(true)
			.__PPM2_PonyData = ponydata
			\SetSequence(@seq)
			\FrameAdvance(0)
			\SetPos(Vector())
			\InvalidateBoneCache()

		@UpdateAttachsIDs()
		return @model

	Think: =>
		rtime = RealTimeL()
		delta = rtime - @lastTick
		@lastTick = rtime

		if IsValid(@model)
			@model\FrameAdvance(delta * @animRate)
			@model\SetPlaybackRate(1)
			@model\SetPoseParameter('move_x', 1)

			if @shouldAutoTrack
				if @trackAttach ~= -1
					{:Ang, :Pos} = @model\GetAttachment(@trackAttach)
					@autoTrackPos = Pos or Vector()
				elseif @trackBone ~= -1
					@autoTrackPos = @model\GetBonePosition(@trackBone) or Vector()
				else
					@shouldAutoTrack = false

		@hold = @IsHovered() if @hold

		if @hold
			x, y = gui.MousePos()
			deltaX, deltaY = x - @mouseX, y - @mouseY
			@mouseX, @mouseY = x, y
			{:pitch, :yaw, :roll} = @angle
			yaw -= deltaX * .5
			pitch = math.clamp(pitch - deltaY * .5, -45, 45)
			@angle = Angle(pitch, yaw % 360, roll)

		@vectorPos = Vector(@fixedDistanceToPony, 0, 0)
		@vectorPos\Rotate(@angle)
		@drawAngle = Angle(-@angle.p, @angle.y - 180)

	FLOOR_VECTOR: Vector(0, 0, -30)
	FLOOR_ANGLE: Vector(0, 0, 1)

	DRAW_WALLS: {
		{Vector(-4000, 0, 900), Vector(1, 0, 0), 8000, 2000}
		{Vector(4000, 0, 900), Vector(-1, 0, 0), 8000, 2000}
		{Vector(0, -4000, 900), Vector(0, 1, 0), 8000, 2000}
		{Vector(0, 4000, 900), Vector(0, -1, 0), 8000, 2000}
		{Vector(0, 0, 900), Vector(0, 0, -1), 8000, 8000}
	}

	WALL_COLOR: Color() - 255
	FLOOR_COLOR: Color() - 255
	EMPTY_VECTOR: Vector()

	Paint: (w = 0, h = 0) =>
		surface.SetDrawColor(0, 0, 0)
		surface.DrawRect(0, 0, w, h)
		return if not IsValid(@model)
		x, y = @LocalToScreen(0, 0)
		drawpos = @vectorPos + @GetTrackedPosition()
		cam.Start3D(drawpos, @drawAngle, @distToPony, x, y, w, h)

		if @holdRightClick
			@model\SetEyeTarget(drawpos)
			turnpitch, turnyaw = DLib.combat.turnAngle(@EMPTY_VECTOR, drawpos, Angle())

			if not inRadius(turnyaw, -20, 20)
				if turnyaw < 0
					@model\SetPoseParameter('head_yaw', turnyaw + 20)
				else
					@model\SetPoseParameter('head_yaw', turnyaw - 20)
			else
				@model\SetPoseParameter('head_yaw', 0)

			turnpitch = turnpitch + 20
			if not inRadius(turnpitch, -10, 0)
				if turnpitch < 0
					@model\SetPoseParameter('head_pitch', turnpitch + 10)
				else
					@model\SetPoseParameter('head_pitch', turnpitch)
			else
				@model\SetPoseParameter('head_pitch', 0)
		else
			@model\SetEyeTarget(@EMPTY_VECTOR)
			@model\SetPoseParameter('head_yaw', 0)
			@model\SetPoseParameter('head_pitch', 0)

		render.DrawQuadEasy(@FLOOR_VECTOR, @FLOOR_ANGLE, 7000, 7000, @FLOOR_COLOR)

		for {pos, ang, w, h} in *@DRAW_WALLS
			render.DrawQuadEasy(pos, ang, w, h, @WALL_COLOR)

		if ENABLE_FULLBRIGHT\GetBool()
			render.SuppressEngineLighting(true)
			render.ResetModelLighting(1, 1, 1)
			render.SetColorModulation(1, 1, 1)

		@buildingModel\DrawModel()
		ctrl = @controller\GetRenderController()

		if bg = @controller\GetBodygroupController()
			bg\ApplyBodygroups()

		with @model\PPMBonesModifier()
			\ResetBones()
			hook.Call('PPM2.SetupBones', nil, @model, @controller)
			\Think(true)

		with ctrl
			\DrawModels()
			\HideModels(true)
			\PreDraw(@model)

		@model\DrawModel()
		ctrl\PostDraw(@model)

		render.SuppressEngineLighting(false) if ENABLE_FULLBRIGHT\GetBool()

		menu = @CurrentMenu()
		@drawPoints = false
		lx, ly = x, y

		if type(menu.points) == 'table'
			@drawPoints = true
			@pointsData = for point in *menu.points
				vecpos = point.getpos(@model)
				position = vecpos\ToScreen()
				{position, point, vecpos\Distance(drawpos)}
		elseif @InMenu() and menu.getpos
			{:x, :y} = menu.getpos(@model)\ToScreen(drawpos)
			x, y = x - lx, y - ly

		cam.End3D()

		if @drawPoints
			mx, my = gui.MousePos()
			mx, my = mx - lx, my - ly
			radius = ScreenScale(10)
			local drawnSelected
			min = 9999

			for pointdata in *@pointsData
				{:x, :y} = pointdata[1]
				x, y = x - lx, y - ly
				pointdata[1].x, pointdata[1].y = x, y

				if inBox(mx, my, x, y, radius, radius)
					if min > pointdata[3]
						drawnSelected = pointdata
						min = pointdata[3]

			@selectPoint = drawnSelected and drawnSelected[2] or false

			for pointdata in *@pointsData
				{:x, :y} = pointdata[1]

				if pointdata == drawnSelected
					surface.SetDrawColor(255, 255, 255)
				else
					surface.SetDrawColor(100, 100, 100)

				surface.DrawLine(x - radius, y - radius, x + radius, y + radius)
				surface.DrawLine(x + radius, y - radius, x - radius, y + radius)

			if not @hold and not @holdOnPoint
				if drawnSelected
					@SetCursor('hand')
				else
					@SetCursor('none')
		else
			@selectPoint = false
			if @InMenu() and menu.getpos
				radius = ScreenScale(10)
				surface.SetDrawColor(255, 255, 255)
				surface.DrawLine(x - radius, y - radius, x + radius, y + radius)
				surface.DrawLine(x + radius, y - radius, x - radius, y + radius)

	OnRemove: =>
		@model\Remove() if IsValid(@model)
		@buildingModel\Remove() if IsValid(@buildingModel)
}

vgui.Register('PPM2Model2Panel', MODEL_BOX_PANEL, 'EditablePanel')

EDIT_TREE = {
	type: 'level'
	name: 'Pony overview'

	points: {
		{
			type: 'bone'
			target: 'LrigScull'
			link: 'head_submenu'
		}

		{
			type: 'bone'
			target: 'LrigSpine1'
			link: 'spine_length'
		}

		{
			type: 'bone'
			target: 'Tail01'
			link: 'tail'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_FL_Metacarpus'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_FR_Metacarpus'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_BR_LargeCannon'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_BL_LargeCannon'
			link: 'legs_submenu'
		}
	}

	children: {
		head_submenu: {
			type: 'level'
			name: 'Head anatomy'

			points: {
				{
					type: 'attach'
					target: 'eyes'
					link: 'eyes'
				}
			}

			children: {
				eyes: {
					type: 'menu'
					populate: =>
				}
			}
		}

		legs_submenu: {
			type: 'level'
			name: 'Hooves anatomy'

			points: {

			}

			children: {

			}
		}
	}
}

patchSubtree = (node) ->
	if type(node.children) == 'table'
		for childID, child in pairs node.children
			child.id = childID
			patchSubtree(child)

	if type(node.points) == 'table'
		for point in *node.points
			point.addvector = point.addvector or Vector()

			switch point.type
				when 'point'
					point.getpos = => Vector(point.target)
				when 'bone'
					point.getpos = =>
						if not point.targetID or point.targetID == -1
							point.targetID = @LookupBone(point.target) or -1

						if point.targetID == -1
							return Vector(point.addvector)
						else
							return @GetBonePosition(point.targetID) + point.addvector
				when 'attach'
					point.getpos = =>
						if not point.targetID or point.targetID == -1
							point.targetID = @LookupAttachment(point.target) or -1

						if point.targetID == -1
							return Vector(point.addvector)
						else
							{:Pos, :Ang} = @GetAttachment(point.targetID)
							return Pos and (Pos + point.addvector) or Vector(point.addvector)

			if type(node.children) == 'table'
				point.linkTable = node.children[point.link]
				if type(point.linkTable) == 'table' and point.linkTable.type == 'menu'
					point.linkTable.getpos = point.getpos

EDIT_TREE.id = 'root'
patchSubtree(EDIT_TREE)

ppm2_editor3 = ->
	if IsValid(PPM2.EDITOR3)
		PPM2.EDITOR3\SetVisible(true)
		PPM2.EDITOR3\MakePopup()
		return PPM2.EDITOR3
	PPM2.EDITOR3 = vgui.Create('DLib_Window')
	self = PPM2.EDITOR3
	@SetSize(ScrWL(), ScrHL())
	@SetPos(0, 0)
	@MakePopup()
	@SetTitle('PPM/2 Editor/3')
	@SetDraggable(false)
	@RemoveResize()

	with @modelPanel = vgui.Create('PPM2Model2Panel', @)
		\Dock(FILL)
		\DockMargin(3, 3, 3, 3)

	copy = PPM2.GetMainData()\Copy()
	ply = LocalPlayer()
	ent = @modelPanel\ResetModel()

	controller = copy\CreateCustomController(ent)
	controller\SetFlexLerpMultiplier(1.3)
	copy\SetController(controller)

	@modelPanel\SetController(controller)
	controller\SetupEntity(ent)
	controller\SetDisableTask(true)

	@modelPanel.stack = {EDIT_TREE}
	@modelPanel\SetParentTarget(@)

concommand.Add 'ppm2_editor3', ppm2_editor3


Update status on Think

Update status on Think

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

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

net.Receive 'PPM2.KillAnimation', ->
    ent = net.ReadEntity()
    return if not IsValid(ent) or not ent\GetPonyData()
    hook.Call('PPM2_KillAnimation', nil, ent)

net.Receive 'PPM2.AngerAnimation', ->
    ent = net.ReadEntity()
    return if not IsValid(ent) or not ent\GetPonyData()
    hook.Call('PPM2_AngerAnimation', nil, ent)

net.Receive 'PPM2.PlayEmote', ->
    emoteID = net.ReadUInt(8)
    ply = net.ReadEntity()
    isEndless = net.ReadBool()
    shouldStop = net.ReadBool()
    return if not IsValid(ply) or not ply\GetPonyData()
    return if not PPM2.AVALIABLE_EMOTES[emoteID]
    hook.Call('PPM2_EmoteAnimation', nil, ply, PPM2.AVALIABLE_EMOTES[emoteID].sequence, PPM2.AVALIABLE_EMOTES[emoteID].time, isEndless, shouldStop)

PPM2.EmotesPanelContext\Remove() if IsValid(PPM2.EmotesPanelContext)
PPM2.EmotesPanel\Remove() if IsValid(PPM2.EmotesPanel)

CONSOLE_EMOTES_COMMAND = (ply = LocalPlayer(), cmd = '', args = {}) ->
    args[1] = args[1] or ''
    emoteID = tonumber(args[1])
    isEndless = tobool(args[2])
    shouldStop = tobool(args[3])

    if emoteID
        if not PPM2.AVALIABLE_EMOTES[emoteID]
            PPM2.Message('No such emotion with ID: ', emoteID)
            return
        net.Start('PPM2.PlayEmote')
        net.WriteUInt(emoteID, 8)
        net.WriteBool(isEndless)
        net.WriteBool(shouldStop)
        net.SendToServer()
        hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), PPM2.AVALIABLE_EMOTES[emoteID].sequence, PPM2.AVALIABLE_EMOTES[emoteID].time)
    else
        emoteID = args[1]\lower()
        if not PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID]
            PPM2.Message('No such emotion with ID: ', emoteID)
            return
        net.Start('PPM2.PlayEmote')
        net.WriteUInt(PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID].id, 8)
        net.WriteBool(isEndless)
        net.WriteBool(shouldStop)
        net.SendToServer()
        hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), emoteID, PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID].time)

CONSOLE_DEF_LIST = ['ppm2_emote "' .. sequence .. '"' for {:sequence} in *PPM2.AVALIABLE_EMOTES]
CONSOLE_EMOTES_AUTOCOMPLETE = (cmd = '', args = '') ->
    args = args\Trim()
    return CONSOLE_DEF_LIST if args == ''
    output = {}
    for {:sequence} in *PPM2.AVALIABLE_EMOTES
        if string.find(sequence, '^' .. args)
            table.insert(output, 'ppm2_emote "' .. sequence .. '"')
    return output

concommand.Add 'ppm2_emote', CONSOLE_EMOTES_COMMAND, CONSOLE_EMOTES_AUTOCOMPLETE

BUTTON_DRAW_FUNC = (w = 0, h = 0) =>
    @hoverDelta = math.Clamp(@hoverDelta + (@IsHovered() and FrameTime() or -FrameTime()) * 5, 0, 1)
    col = @hoverDelta * 150
    col = 200 if @IsDown()
    surface.SetDrawColor(col, col, col, 150)
    surface.DrawRect(0, 0, w, h)

BUTTON_CLICK_FUNC = (isEndless = false, shouldStop = false) =>
    if @sendToServer
        net.Start('PPM2.PlayEmote')
        net.WriteUInt(@id, 8)
        net.WriteBool(isEndless)
        net.WriteBool(shouldStop)
        net.SendToServer()
        hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), @sequence, @time, isEndless, shouldStop)
    else
        hook.Call('PPM2_EmoteAnimation', nil, @target, @sequence, @time, isEndless, shouldStop)

BUTTON_TEXT_COLOR = Color(255, 255, 255)

IMAGE_PANEL_THINK = =>
    @lastThink = RealTime() + .4
    if @IsHovered()
        if not @oldHover
            @oldHover = true
            @hoverPnl\SetVisible(true)
            x, y = @LocalToScreen(0, 0)
            @hoverPnl\SetPos(x - 256, y - 224)
    else
        if @oldHover
            @oldHover = false
            @hoverPnl\SetVisible(false)

HOVERED_IMAGE_PANEL_THINK = =>
    if not @parent\IsValid()
        @Remove()
        return
    if @parent.lastThink < RealTime()
        @SetVisible(false)

PPM2.CreateEmotesPanel = (parent, target = LocalPlayer(), sendToServer = true) ->
    self = vgui.Create('DPanel', parent)
    @SetSize(200, 300)
    @Paint = (w = 0, h = 0) =>
        surface.SetDrawColor(0, 0, 0, 150)
        surface.DrawRect(0, 0, w, h)
    @scroll = vgui.Create('DScrollPanel', @)
    with @scroll
        \Dock(FILL)
        \SetSize(200, 300)
        .Paint = ->
        \SetMouseInputEnabled(true)
    checkboxes = {}
    @buttons = for {:name, :id, :sequence, :time, :fexists, :filecrop} in *PPM2.AVALIABLE_EMOTES
        with btn = vgui.Create('DButton', @scroll)
            \SetTextColor(BUTTON_TEXT_COLOR)
            .Paint = BUTTON_DRAW_FUNC
            .id = id
            .time = time
            .sequence = sequence
            .hoverDelta = 0
            .sendToServer = sendToServer
            .target = target
            .DoClick = BUTTON_CLICK_FUNC
            \SetSize(200, 32)
            \SetText(name)
            \SetFont('HudHintTextLarge')
            \Dock(TOP)
            with .checkbox = \Add('DCheckBox')
                \Dock(RIGHT)
                \DockMargin(2, 8, 2, 8)
                \SetSize(16, 16)
                \SetChecked(false)
                .Think = ->
                    if IsValid(target)
                        if ponyData = target\GetPonyData()
                            if renderController = ponyData\GetRenderController()
                                if flexController = renderController\GetFlexController()
                                    \SetChecked(flexController\HasSequence(sequence .. '_endless'))
                .OnChange = (checkbox3, newVal) ->
                    return if .suppress
                    BUTTON_CLICK_FUNC(btn, true) if newVal
                    BUTTON_CLICK_FUNC(btn, false, true) if not newVal
            table.insert(checkboxes, .checkbox)
            if fexists
                image = vgui.Create('DImage', btn)
                with image
                    \Dock(LEFT)
                    \SetSize(32, 32)
                    \SetImage(filecrop)
                    \SetMouseInputEnabled(true)
                    .hoverPnl = vgui.Create('DImage')
                    .Think = IMAGE_PANEL_THINK
                    .oldHover = false
                    with .hoverPnl
                        \SetMouseInputEnabled(false)
                        \SetVisible(false)
                        \SetImage(filecrop)
                        \SetSize(256, 256)
                        .Think = HOVERED_IMAGE_PANEL_THINK
                        .parent = image
                    .OnRemove = -> .hoverPnl\Remove() if IsValid(.hoverPnl)
        btn
    @scroll\AddItem(btn) for btn in *@buttons
    @SetVisible(false)
    @SetMouseInputEnabled(false)
    return @

hook.Add 'ContextMenuCreated', 'PPM2.Emotes', =>
    return if not IsValid(@)
    PPM2.EmotesPanelContext\Remove() if IsValid(PPM2.EmotesPanelContext)
    PPM2.EmotesPanelContext = PPM2.CreateEmotesPanel(@)
    PPM2.EmotesPanelContext\SetPos(ScrW() / 2 - 100, ScrH() - 300)
    PPM2.EmotesPanelContext\SetVisible(true)
    PPM2.EmotesPanelContext\SetMouseInputEnabled(true)
    timer.Create 'PPM2.ContextMenuEmotesUpdate', 1, 0, ->
        if not IsValid(PPM2.EmotesPanelContext)
            timer.Remove 'PPM2.ContextMenuEmotesUpdate'
            return
        return if not IsValid(LocalPlayer())
        status = LocalPlayer()\IsPony()
        PPM2.EmotesPanelContext\SetVisible(status)
        PPM2.EmotesPanelContext\SetMouseInputEnabled(status)

hook.Add 'StartChat', 'PPM2.Emotes', ->
    if not IsValid(PPM2.EmotesPanel)
        PPM2.EmotesPanel = PPM2.CreateEmotesPanel()
        PPM2.EmotesPanel\SetPos(ScrW() - 500, ScrH() - 300)

    if IsValid(PPM2.EmotesPanel)
        if LocalPlayer()\IsPony()
            PPM2.EmotesPanel\SetVisible(true)
            PPM2.EmotesPanel\SetMouseInputEnabled(true)
            PPM2.EmotesPanel\RequestFocus()
        else
            PPM2.EmotesPanel\SetVisible(false)
            PPM2.EmotesPanel\SetMouseInputEnabled(false)
            PPM2.EmotesPanel\KillFocus()

hook.Add 'FinishChat', 'PPM2.Emotes', ->
    if IsValid(PPM2.EmotesPanel)
        PPM2.EmotesPanel\KillFocus()
        PPM2.EmotesPanel\SetVisible(false)
        PPM2.EmotesPanel\SetMouseInputEnabled(false)

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

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

net.Receive 'PPM2.KillAnimation', ->
    ent = net.ReadEntity()
    return if not IsValid(ent) or not ent\GetPonyData()
    hook.Call('PPM2_KillAnimation', nil, ent)

net.Receive 'PPM2.AngerAnimation', ->
    ent = net.ReadEntity()
    return if not IsValid(ent) or not ent\GetPonyData()
    hook.Call('PPM2_AngerAnimation', nil, ent)

net.Receive 'PPM2.PlayEmote', ->
    emoteID = net.ReadUInt(8)
    ply = net.ReadEntity()
    isEndless = net.ReadBool()
    shouldStop = net.ReadBool()
    return if not IsValid(ply) or not ply\GetPonyData()
    return if not PPM2.AVALIABLE_EMOTES[emoteID]
    hook.Call('PPM2_EmoteAnimation', nil, ply, PPM2.AVALIABLE_EMOTES[emoteID].sequence, PPM2.AVALIABLE_EMOTES[emoteID].time, isEndless, shouldStop)

PPM2.EmotesPanelContext\Remove() if IsValid(PPM2.EmotesPanelContext)
PPM2.EmotesPanel\Remove() if IsValid(PPM2.EmotesPanel)

CONSOLE_EMOTES_COMMAND = (ply = LocalPlayer(), cmd = '', args = {}) ->
    args[1] = args[1] or ''
    emoteID = tonumber(args[1])
    isEndless = tobool(args[2])
    shouldStop = tobool(args[3])

    if emoteID
        if not PPM2.AVALIABLE_EMOTES[emoteID]
            PPM2.Message('No such emotion with ID: ', emoteID)
            return
        net.Start('PPM2.PlayEmote')
        net.WriteUInt(emoteID, 8)
        net.WriteBool(isEndless)
        net.WriteBool(shouldStop)
        net.SendToServer()
        hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), PPM2.AVALIABLE_EMOTES[emoteID].sequence, PPM2.AVALIABLE_EMOTES[emoteID].time)
    else
        emoteID = args[1]\lower()
        if not PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID]
            PPM2.Message('No such emotion with ID: ', emoteID)
            return
        net.Start('PPM2.PlayEmote')
        net.WriteUInt(PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID].id, 8)
        net.WriteBool(isEndless)
        net.WriteBool(shouldStop)
        net.SendToServer()
        hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), emoteID, PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID].time)

CONSOLE_DEF_LIST = ['ppm2_emote "' .. sequence .. '"' for {:sequence} in *PPM2.AVALIABLE_EMOTES]
CONSOLE_EMOTES_AUTOCOMPLETE = (cmd = '', args = '') ->
    args = args\Trim()
    return CONSOLE_DEF_LIST if args == ''
    output = {}
    for {:sequence} in *PPM2.AVALIABLE_EMOTES
        if string.find(sequence, '^' .. args)
            table.insert(output, 'ppm2_emote "' .. sequence .. '"')
    return output

concommand.Add 'ppm2_emote', CONSOLE_EMOTES_COMMAND, CONSOLE_EMOTES_AUTOCOMPLETE

BUTTON_DRAW_FUNC = (w = 0, h = 0) =>
    @hoverDelta = math.Clamp(@hoverDelta + (@IsHovered() and FrameTime() or -FrameTime()) * 5, 0, 1)
    col = @hoverDelta * 150
    col = 200 if @IsDown()
    surface.SetDrawColor(col, col, col, 150)
    surface.DrawRect(0, 0, w, h)

BUTTON_CLICK_FUNC = (isEndless = false, shouldStop = false) =>
    if @sendToServer
        net.Start('PPM2.PlayEmote')
        net.WriteUInt(@id, 8)
        net.WriteBool(isEndless)
        net.WriteBool(shouldStop)
        net.SendToServer()
        hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), @sequence, @time, isEndless, shouldStop)
    else
        hook.Call('PPM2_EmoteAnimation', nil, @target, @sequence, @time, isEndless, shouldStop)

BUTTON_TEXT_COLOR = Color(255, 255, 255)

IMAGE_PANEL_THINK = =>
    @lastThink = RealTime() + .4
    if @IsHovered()
        if not @oldHover
            @oldHover = true
            @hoverPnl\SetVisible(true)
            x, y = @LocalToScreen(0, 0)
            @hoverPnl\SetPos(x - 256, y - 224)
    else
        if @oldHover
            @oldHover = false
            @hoverPnl\SetVisible(false)

HOVERED_IMAGE_PANEL_THINK = =>
    if not @parent\IsValid()
        @Remove()
        return
    if @parent.lastThink < RealTime()
        @SetVisible(false)

PPM2.CreateEmotesPanel = (parent, target = LocalPlayer(), sendToServer = true) ->
    self = vgui.Create('DPanel', parent)
    @SetSize(200, 300)
    @Paint = (w = 0, h = 0) =>
        surface.SetDrawColor(0, 0, 0, 150)
        surface.DrawRect(0, 0, w, h)
    @scroll = vgui.Create('DScrollPanel', @)
    with @scroll
        \Dock(FILL)
        \SetSize(200, 300)
        .Paint = ->
        \SetMouseInputEnabled(true)
    checkboxes = {}
    @buttons = for {:name, :id, :sequence, :time, :fexists, :filecrop} in *PPM2.AVALIABLE_EMOTES
        with btn = vgui.Create('DButton', @scroll)
            \SetTextColor(BUTTON_TEXT_COLOR)
            .Paint = BUTTON_DRAW_FUNC
            .id = id
            .time = time
            .sequence = sequence
            .hoverDelta = 0
            .sendToServer = sendToServer
            .target = target
            .DoClick = ->
                BUTTON_CLICK_FUNC(btn)
                checkbox2\SetChecked(false) for checkbox2 in *checkboxes when IsValid(checkbox2)
            \SetSize(200, 32)
            \SetText(name)
            \SetFont('HudHintTextLarge')
            \Dock(TOP)
            with .checkbox = \Add('DCheckBox')
                \Dock(RIGHT)
                \DockMargin(2, 8, 2, 8)
                \SetSize(16, 16)
                \SetChecked(false)
                if ponyData = target\GetPonyData()
                    if renderController = ponyData\GetRenderController()
                        if flexController = renderController\GetFlexController()
                            \SetChecked(flexController\HasSequence(sequence .. '_endless'))
                .OnChange = (checkbox3, newVal) ->
                    return if .suppress
                    checkbox2\SetChecked(false) for checkbox2 in *checkboxes when IsValid(checkbox2) and checkbox2 ~= checkbox3
                    BUTTON_CLICK_FUNC(btn, true) if newVal
                    BUTTON_CLICK_FUNC(btn, false, true) if not newVal
            table.insert(checkboxes, .checkbox)
            if fexists
                image = vgui.Create('DImage', btn)
                with image
                    \Dock(LEFT)
                    \SetSize(32, 32)
                    \SetImage(filecrop)
                    \SetMouseInputEnabled(true)
                    .hoverPnl = vgui.Create('DImage')
                    .Think = IMAGE_PANEL_THINK
                    .oldHover = false
                    with .hoverPnl
                        \SetMouseInputEnabled(false)
                        \SetVisible(false)
                        \SetImage(filecrop)
                        \SetSize(256, 256)
                        .Think = HOVERED_IMAGE_PANEL_THINK
                        .parent = image
                    .OnRemove = -> .hoverPnl\Remove() if IsValid(.hoverPnl)
        btn
    @scroll\AddItem(btn) for btn in *@buttons
    @SetVisible(false)
    @SetMouseInputEnabled(false)
    return @

hook.Add 'ContextMenuCreated', 'PPM2.Emotes', =>
    return if not IsValid(@)
    PPM2.EmotesPanelContext\Remove() if IsValid(PPM2.EmotesPanelContext)
    PPM2.EmotesPanelContext = PPM2.CreateEmotesPanel(@)
    PPM2.EmotesPanelContext\SetPos(ScrW() / 2 - 100, ScrH() - 300)
    PPM2.EmotesPanelContext\SetVisible(true)
    PPM2.EmotesPanelContext\SetMouseInputEnabled(true)
    timer.Create 'PPM2.ContextMenuEmotesUpdate', 1, 0, ->
        if not IsValid(PPM2.EmotesPanelContext)
            timer.Remove 'PPM2.ContextMenuEmotesUpdate'
            return
        return if not IsValid(LocalPlayer())
        status = LocalPlayer()\IsPony()
        PPM2.EmotesPanelContext\SetVisible(status)
        PPM2.EmotesPanelContext\SetMouseInputEnabled(status)

hook.Add 'StartChat', 'PPM2.Emotes', ->
    if not IsValid(PPM2.EmotesPanel)
        PPM2.EmotesPanel = PPM2.CreateEmotesPanel()
        PPM2.EmotesPanel\SetPos(ScrW() - 500, ScrH() - 300)

    if IsValid(PPM2.EmotesPanel)
        if LocalPlayer()\IsPony()
            PPM2.EmotesPanel\SetVisible(true)
            PPM2.EmotesPanel\SetMouseInputEnabled(true)
            PPM2.EmotesPanel\RequestFocus()
        else
            PPM2.EmotesPanel\SetVisible(false)
            PPM2.EmotesPanel\SetMouseInputEnabled(false)
            PPM2.EmotesPanel\KillFocus()

hook.Add 'FinishChat', 'PPM2.Emotes', ->
    if IsValid(PPM2.EmotesPanel)
        PPM2.EmotesPanel\KillFocus()
        PPM2.EmotesPanel\SetVisible(false)
        PPM2.EmotesPanel\SetMouseInputEnabled(false)


Move some things around in Engine

Move some things around in Engine

* Use EventListener to dispatch events like resize
* Add comments for functions
* Add some more HaxePunk specific functions and put them in HXP order
* Initial go at Engine\resize

require "LovePunk.Entity"
require "LovePunk.RenderMode"
require "LovePunk.Screen"
require "LovePunk.geometry.Rectangle"
require "LovePunk.utils.Draw"
require "LovePunk.utils.Event"
require "LovePunk.utils.EventListener"

export ^

-- The main class which manages the game loop
class Engine
	-- Constructor. Defines startup information about your game.
	-- @param width      The width of your game.
	-- @param height     The height of your game.
	-- @param frameRate  The game framerate, in frames per second.
	-- @param fixed      If a fixed-framerate should be used.
	-- @param renderMode Overrides the default render mode for this target
	new: (width = 0, height = 0, frameRate = 0, fixed = false, renderMode) =>
		LP.bounds = Rectangle(0, 0, width, height)
		LP.assignedFrameRate = frameRate
		LP.fixed = fixed

		LP.engine = @
		LP.screen = Screen!
		LP.width = width
		LP.height = height

		LP.randomizeSeed! unless LP.randomSeed

		LP.entity = Entity!
		LP.time = love.timer.getTime!

		-- Cap on the elapsed time (default at 30 FPS). Raise this to allow for lower framerates (eg. 1 / 10).
		@maxElapsed = 0.0333
		-- The max amount of frames that can be skipped in fixed framerate mode.
		@maxFrameSkip = 0
		-- If the game should stop updating/rendering.
		@paused = false
		-- The amount of milliseconds between ticks in fixed framerate mode.
		@tickRate = 4

		@__frameList = {}
		@__frameLast = 0
		@__frameListSum = 0

		-- Do something with the Löve functions...
		love.draw = @\render
		love.update = @\update
		love.focus = (f) ->
			if f
				DispatchEvent Event Event.ACTIVATE!
-- 				@focusGained!
			else
				DispatchEvent Event Event.DEACTIVATE!
-- 				@focusLost!
		if love._version_major >= 0 and love._version_minor >= 9
			love.resize = -> DispatchEvent Event Event.RESIZE

		-- call some more setup from here
		@setGraphicsProperties!

	-- Override this, called after Engine has been added to the stage.
	init: =>

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

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

	-- Updates the game, updating the Scene and Entities.
	update: =>
		LP.scene\updateLists!
		checkScene! if LP\sceneIsNull!
		LP.tweener\updateTweens! if LP.tweener.active and LP.tweener\hasTween!

		if LP.scene.active
			LP.scene\updateTweens! if LP.scene\hasTween!
			LP.scene\update!

		LP.scene\updateLists false

	-- Renders the game, rendering the Scene and Entities.
	render: =>
		LP.resize LP.windowWidth, LP.windowHeight if LP.screen.needsResize

		-- timing stuff
		t = love.timer.getDelta!

		@__frameLast = t if @__frameLast == 0

		-- render loop
		if LP.renderMode == RenderMode.Buffer
			LP.screen\swap!
			LP.screen\refresh!

		Draw.resetTarget!

		LP.scene\render! if LP.scene.visible

		LP.screen\redraw! if LP.renderMode == RenderMode.Buffer

		-- more timing stuff
		t = love.timer.getTime!
		@__frameList[#@__frameList] = t - @__frameLast
		@__frameListSum += @__frameList[#@__frameList]
		if #@__frameList > 10
			@__frameListSum -= @__frameList[#@__frameList]
			@__frameList = [frame for frame in *@__frameList[2,]]

		LP.frameRate = 1000 / (@__frameListSum / #@__frameList)
		@__frameLast = t

	-- Sets the game's graphics properties. Override this to set them differently.
	setGraphicsProperties: =>
		if love._version_major >= 0 and love._version_minor >= 9
			-- love.window is Löve >= 0.9
			LP.windowWidth = love.window.getWidth!
			LP.windowHeight = love.window.getHeight!
		else
			LP.windowWidth = love.graphics.getWidth!
			LP.windowHeight = love.graphics.getHeight!

		@resize! -- call resize once to initialize the screen

		-- set resize event
		AddEventListener Event.RESIZE!, (e) -> @resize!

		AddEventListener Event.ACTIVATE!, (e) ->
			LP.focused = true
			@focusGained!
			LP.scene\focusGained!

		AddEventListener Event.DEACTIVATE!, (e) ->
			LP.focused = false
			@focusLost!
			LP.scene\focusLost!

	-- @private Event handler for window resize
	resize: =>
		LP.width = love.graphics.getWidth! if LP.width == 0
		LP.height = love.graphics.getHeight! if LP.height == 0

		-- calculate scale from width/height values
		if love._version_major >= 0 and love._version_minor >= 9
			-- love.window is Löve >= 0.9
			LP.windowWidth = love.window.getWidth!
			LP.windowHeight = love.window.getHeight!
		else
			LP.windowWidth = love.graphics.getWidth!
			LP.windowHeight = love.graphics.getHeight!

		LP.screen\scaleX love.graphics.getWidth! / LP.width
		LP.screen\scaleY love.graphics.getHeight! / LP.height
		LP.resize love.graphics.getWidth!, love.graphics.getHeight!

	-- @private Event handler for stage entry.
	onStage = (e) =>

	-- @private Framerate independent game loop.
	onEnterFrame = (e) =>

	-- @private Switch scenes if they've changed.
	checkScene = =>
		return if not LP\sceneIsNull!

		if LP.scene != nil
			LP.scene\endScene!
			LP.scene\updateLists!
			LP.scene\clearTweens! if LP.scene.autoClear and LP.scene\hasTween!
			-- TODO
			-- removeChild(LP.scene.sprite) if LP.scene.sprite
			LP\swapScene!
			-- TODO
			-- addChild LP.scene.sprite
			LP.camera = LP.scene.camera
			LP.scene\begin!
			LP.scene\updateLists!

require "LovePunk.Entity"
require "LovePunk.RenderMode"
require "LovePunk.Screen"
require "LovePunk.geometry.Rectangle"
require "LovePunk.utils.Draw"

export ^

---
-- The main class which manages the game loop
class Engine
	---
	-- Constructor. Defines startup information about your game.
	-- @param width      The width of your game.
	-- @param height     The height of your game.
	-- @param frameRate  The game framerate, in frames per second.
	-- @param fixed      If a fixed-framerate should be used.
	-- @param renderMode Overrides the default render mode for this target
	new: (width = 0, height = 0, frameRate = 0, fixed = false, renderMode) =>
		LP.bounds = Rectangle(0, 0, width, height)
		LP.assignedFrameRate = frameRate
		LP.fixed = fixed

		LP.engine = @
		LP.screen = Screen!
		LP.width = width
		LP.height = height

		LP.randomizeSeed! unless LP.randomSeed

		LP.entity = Entity!
		LP.time = love.timer.getTime!

		---
		-- Cap on the elapsed time (default at 30 FPS). Raise this to allow for lower framerates (eg. 1 / 10).
		@maxElapsed = 0.0333
		---
		-- The max amount of frames that can be skipped in fixed framerate mode.
		@maxFrameSkip = 0
		---
		-- If the game should stop updating/rendering.
		@paused = false
		---
		-- The amount of milliseconds between ticks in fixed framerate mode.
		@tickRate = 4

		@__frameList = {}
		@__frameLast = 0
		@__frameListSum = 0

		-- Do something with the Löve functions...
		love.draw = @\render
		love.update = @\update
		love.focus = (f) ->
			if f
				@focusGained!
			else
				@focusLost!
		if love._version_major >= 0 and love._version_minor >= 9
			love.resize = @\resize

	focusGained: =>
	focusLost: =>
	init: =>
	render: =>
		LP.resize LP.windowWidth, LP.windowHeight if LP.screen.needsResize

		-- timing stuff
		t = love.timer.getDelta!

		@__frameLast = t if @__frameLast == 0

		-- render loop
		if LP.renderMode == RenderMode.Buffer
			LP.screen\swap!
			LP.screen\refresh!

		Draw.resetTarget!

		LP.scene\render! if LP.scene.visible

		LP.screen\redraw! if LP.renderMode == RenderMode.Buffer

		-- more timing stuff
		t = love.timer.getTime!
		@__frameList[#@__frameList] = t - @__frameLast
		@__frameListSum += @__frameList[#@__frameList]
		if #@__frameList > 10
			@__frameListSum -= @__frameList[#@__frameList]
			@__frameList = [frame for frame in *@__frameList[2,]]

		LP.frameRate = 1000 / (@__frameListSum / #@__frameList)
		@__frameLast = t

	update: =>
		LP.scene\updateLists!
		checkScene! if LP\sceneIsNull!
		LP.tweener\updateTweens! if LP.tweener.active and LP.tweener\hasTween!

		if LP.scene.active
			LP.scene\updateTweens! if LP.scene\hasTween!
			LP.scene\update!

		LP.scene\updateLists false

	resize: =>

	checkScene = =>
		return if not LP\sceneIsNull!

		if LP.scene != nil
			LP.scene\endScene!
			LP.scene\updateLists!
			LP.scene\clearTweens! if LP.scene.autoClear and LP.scene\hasTween!
			-- TODO
			-- removeChild(LP.scene.sprite) if LP.scene.sprite
			LP\swapScene!
			-- TODO
			-- addChild LP.scene.sprite
			LP.camera = LP.scene.camera
			LP.scene\begin!
			LP.scene\updateLists!


Fix imports for schema/migrations

Fix imports for schema/migrations

lapis = require "lapis"
io = require "io"
import capture_errors from require "lapis.application"
import json_params, respond_to, capture_errors, capture_errors_json, assert_error, yield_error from require "lapis.application"
import validate, assert_valid from require "lapis.validate"
import escape_pattern, trim_filter, from_json, to_json from require "lapis.util"
db = require "lapis.db"
import Redis, Users, Albums, Images, Accesstokens, generate_token, imagedatesql from require "photongx.models"
config = require("lapis.config").get!

require_login = (fn) ->
  =>
    if @current_user
      fn @
    else
      redirect_to: @url_for "admin"

persona_verify = (assertion, audience) ->

    options = { method:ngx.HTTP_POST, body:to_json {:assertion, :audience} }
    res, err = ngx.location.capture('/persona/', options)

    if not res then
      return { err: res }

    if res.status >= 200 and res.status < 300 then
      return from_json res.body
    else
      return {
        status:res.status,
        body:res.body
      }

class extends lapis.Application
  layout: require "photongx.views.layout"
  views_prefix: "photongx.views"

  @before_filter =>
    @current_user = Users\read_session @

  [index: "/"]: =>
    if @current_user
      return redirect_to: @url_for "albums"
    render:true

  [albums: "/albums"]: require_login =>
    @albums = Albums\select "where user_id = ? order by id DESC", @current_user.id
    --@albums = Albums\select "left join images on albums.id=images.album_id where albums.user_id = ? order by albums.id DESC, images.views DESC", @current_user.id, fields:"distinct on (albums.id) *"
    --images = Images\get_coverimages @albums
    for i=1, #@albums
      album = @albums[i]
      album.image = (Images\get_coverimage album.id)[1]
      album.url = @url_for("album", token:album.token, title:album.title)
    render: true

  [tokenalbums: "/albums/:slug/"]: capture_errors =>
    @accesstokens = Accesstokens\for_slug @params.slug
    unless #@accesstokens > 0
      return render: "error", status: 404
    album_ids = [a.album_id for a in *@accesstokens]
    @albums = Albums\find_all album_ids

    -- FIXME improve SQL to single statement
    for album in *@albums
      images = Images\get_coverimage album.id
      album.image = images[1]
      -- Override the token with our given slug so the template generates the correct URLs
      album.slug = @params.slug
      album.url = @url_for("tokenalbum", slug:@params.slug, token:album.token, title:album.title)
    render: "albums"

  [album: "/album/:token/:title/"]: =>
    if @current_user
      @album = Albums\find token:@params.token
      unless @album
        return render:"error", status:404
      unless @current_user.id == @album.user_id
        return render:"error", status:403
    unless @album return render:"error", status:403
    @album.views = @album.views + 1
    @album\update "views"
    @images = Images\select "where album_id = ? ORDER BY date, file_name", @album.id, fields: "*, "..imagedatesql
    @albumurl = @url_for('album', token:@params.token, title:@album.title)
    @albumsurl = @url_for('albums')
    render: true

  [tokenalbum: "/album/:slug/:token/:title/"]: =>
    @album = Albums\find token:@params.token
    unless @album
      return render:"error", status:404
    valid_token = Accesstokens\validate_album @params.slug, @album.id
    unless valid_token
      return render:"error", status:410
    @albumurl = @url_for('tokenalbum', slug:@params.slug, token:@album.token, title:@album.title)
    @albumsurl = @url_for('tokenalbums', slug:@params.slug)
    @album.views = @album.views + 1
    @album\update "views"
    @images = Images\select "where album_id = ? ORDER BY date, file_name", @album.id, fields: "*, "..imagedatesql
    render: "album"

--
-- This view gets called from a rewrite_by_lua handler so it exits
-- because: "Note that when calling ngx.exit(ngx.OK) within a rewrite_by_lua handler, the nginx request processing control flow will still continue to the content handler."
--
  [img: "/img/:token/:filename"]: =>

    -- Check if user has permission to see image or return 403
    -- Check if token is valid
    -- Set real url or return 404

    -- Simple auth check for now
    --unless @current_user
    --  ngx.exit(403)

    -- TODO maybe disallow original?

    @image = Images\find token:@params.token
    unless @image
      ngx.exit(404)

    -- Check access tokens and return status 410 if expired
    imguri = '/real' .. @image\file_path! .. '/' ..@params.filename
    ngx.req.set_uri imguri, true

-- FIXME require_login
  [zipalbum: "/archive/album/:token/:title/"]: =>
    if @current_user
      @album = Albums\find token:@params.token
      unless @album
        return render:"error", status:404
      unless @current_user.id == @album.user_id
        return render:"error", status:403
    -- FIXME
    @album = Albums\find token:@params.token
    unless @album return render:"error", status:403

    @images = Images\select "where album_id = ? ORDER BY date, file_name", @album.id, fields: "*, "..imagedatesql

    -- Craft the body request that modzip wants

    reqbody = {}

    sample = "- 428    /foo.txt   My Document1.txt"

    for image in *@images
      size = image\get_file_size!
      url = image\real_file_name!
      name = image.file_name
      table.insert(reqbody, table.concat({'-', size, url, name}, ' '))

    reqbody = table.concat(reqbody, '\n')

    --do
    --  json:{:reqbody}

    ngx.header["X-Archive-Files"] = 'zip'
    ngx.header['Content-Type'] = 'application/force-download'
    ngx.header['Content-Disposition'] = "attachment; filename=#{@album.title}.zip"
    ngx.req.set_header "X-Archive-Files", 'zip'
    -- Capture the zip file from modzip
    options = { method:ngx.HTTP_GET, body:reqbody }
    res, err = ngx.location.capture('/zip', options)

    unless res
      return render:"error", status:500

    --if res.status >= 200 and res.status < 300 then
    --return layout:false, res.body
    return layout:false, reqbody

  [persona_login: "/api/persona/login"]: respond_to {
    POST: capture_errors_json =>
      body = ngx.req.get_body_data!
      if body
        body = from_json body

        verification_data = persona_verify body.assertion, config.site
        if verification_data.status == 'okay'
          Users\write_session @, verification_data
          return json:verification_data
      json: {email:false}
  }
  [persona_status: "/api/persona/status"]: respond_to {
    GET: =>
      render: true

    POST: capture_errors_json =>
      cu = @current_user
      if cu
        return json:{email:cu.email}
      json:{email:false}
  }
  [user_logout: "/api/persona/logout"]: =>
    @session.user = false
    json: {email:false}

-- ALBUMS API view - get albums or add new album
  [apialbums: "/api/albums"]: respond_to {
    GET: capture_errors_json require_login =>
      albums = assert_error Albums\select "where user_id = ?", @current_user.id
      json: {:albums}

    POST: capture_errors_json require_login json_params =>

      assert_valid @params, {
        { "name", exists: true, min_length: 1 }
      }
      -- Get or create
      album = Albums\find user_id: @current_user.id, title:@params.name
      unless album
        album = Albums\create @current_user.id, @params.name
      json: { album: album }
  }

  "/api/albums/:album_id": respond_to {
    PUT: capture_errors_json require_login json_params =>
      album = Albums\find id:@params.album_id, user_id: @current_user.id
      album.title = @params.title
      album\update "title"
      json: {:album}
    DELETE: capture_errors_json require_login =>
      album = Albums\find id:@params.album_id, user_id: @current_user.id
      unless album
        return render:"error", status:404
      album\delete!
      json:album
  }

  "/api/images/:album_id": respond_to {

    GET: capture_errors_json require_login =>
      album = Albums\find id:@params.album_id
      unless album
        return render:"error", status:404
      unless album.user_id == @current_user.id
        return render:"error", status:403
      images = assert_error Images\select "where album_id = ? ORDER BY date", @params.album_id, fields: "*, "..imagedatesql
      --- TODO maybe use raw query to cast hstore to json?
      -- Examples found:https://gist.github.com/WoLpH/2318757
      -- Or use postgresql 9.3 which can cast hstore to JSON
      for image in *images
        if image.metadata
          newstr, n, err = ngx.re.gsub(image.metadata, "=>", ":")
          image.metadata = from_json '{'..newstr..'}'
      json: {:images}
  }

  "/api/accesstokens/:album_id": respond_to {

    GET: capture_errors_json require_login =>
      accesstokens = Accesstokens\select "where user_id = ? and album_id = ?", @current_user.id, @params.album_id
      json: {:accesstokens}

    DELETE: =>
      accesstoken = Accesstokens\find id:@params.album_id, user_id:@current_user.id
      unless accesstoken
        return render:"error", status:404
      accesstoken\delete!
      json:{ :accesstoken }
  }

  [images: "/api/images"]: respond_to {
    GET:capture_errors_json require_login  =>
      images = assert_error Images\select "where user_id = ?", @current_user.id
      json: {:images}

    POST: capture_errors_json require_login =>
        assert_valid @params, {
            {'upload', file_exists: true}
            {'filename', exists: true}
            {'title', exists: true}
            {'token', exists: true}
            {'checksum', exists: true}
        }
        {:upload, :title, :filename, :token, :checksum} = @params
        pattern = '\\.(jpe?g|gif|png|crw|raw)$'
        unless ngx.re.match(filename, pattern, "i")
            return status:403, json:{status:403, result:'Filename must be of image type'}
        file = @params.upload
        album = assert_error Albums\find user_id: @current_user.id, token:token
        success, image = pcall -> Images\create @current_user.id, album.id, filename
        -- Since there is a likely chance our silly token can be duplicate we just try to generate image again if it fails
        while not success
          success, image = pcall -> Images\create @current_user.id, album.id, filename
        content = file.content
        real_file_name = image\real_file_name!
        diskfile = io.open real_file_name, 'w'
        unless diskfile
          -- TODO delete created image from SQL
          return status:403, json:{status:403, error:"Permission denied"}
        diskfile\write file.content
        diskfile\flush!
        diskfile\close
        redis = Redis!
        queue = assert_error redis\queue image.token
        status:200, json:{status:200, result:'Image successfully uploaded'}
  }

  [photostreamimages: "/api/photostreamimages"]: respond_to {
    GET:capture_errors_json require_login  =>
      photostreamimages = assert_error Images\select "WHERE user_id = ? ORDER BY date DESC", @current_user.id, fields: "*, "..imagedatesql
      json: {:photostreamimages}
    }

  "/api/albumttl/:album_id": respond_to {
    POST: capture_errors_json require_login =>
      assert_valid @params, {
        { "name", exists: true}
        { "ttl", exists: true}
      }
      ttl = tonumber @params.ttl
      -- TTL not a number ? Assume forever.
      if ttl == nil
          ttl = 2^32 -- ~150 years

      name = @params.name
      album = Albums\find id:@params.album_id, user_id: @current_user.id
      accesstoken = Accesstokens\create @current_user.id, album.id, name, ttl

      json: {:album,:accesstoken}
    }

  [admin: "/admin/"]: =>
    layout:'admin'

  "/api/tag": =>
    json: {token: generate_token 6}

  "/api/image/:image_id": respond_to {
    DELETE: capture_errors_json require_login =>
      image = Images\find id:@params.image_id, user_id: @current_user.id
      unless image
        return render:"error", status:"404"
      image\delete!
      json:image
  }

  "/api/img/click": =>
    assert_valid @params, {
      { "img", exists: true, min_length: 6 }
    }
    @image = Images\find token:@params.img
    unless @image
      return render: "error", status: 404
    @image.views = @image.views + 1
    @image\update "views"

    json: {views: @image.views}

  [adminapi: "/admin/api/all"]: =>
    ok = "test"
    json: { status: ok}

  "/api/queue": require_login capture_errors_json =>
    redis = Redis!
    queue = assert_error redis\queue_length!
    json: {counter:queue}

  "/db/make": =>
    schema = require "photongx.schema"
    schema.make_schema!
    json: { status: "ok" }

  "/db/destroy": require_login =>
    -- Hard coded to first user for now
    if @current_user.id == 1
      schema = require "photongx.schema"
      schema.destroy_schema!
      return json: { status: "ok" }
    json: status: 403

  "/db/migrate": require_login =>
    -- Hard coded to first user for now
    if @current_user.id == 1
      import run_migrations from require "lapis.db.migrations"
      run_migrations require "photongx.migrations"
      return json: { status: "ok" }
    json: status: 403

  "/debug": =>
    layout:false, "hello"

lapis = require "lapis"
io = require "io"
import capture_errors from require "lapis.application"
import json_params, respond_to, capture_errors, capture_errors_json, assert_error, yield_error from require "lapis.application"
import validate, assert_valid from require "lapis.validate"
import escape_pattern, trim_filter, from_json, to_json from require "lapis.util"
db = require "lapis.db"
import Redis, Users, Albums, Images, Accesstokens, generate_token, imagedatesql from require "photongx.models"
config = require("lapis.config").get!

require_login = (fn) ->
  =>
    if @current_user
      fn @
    else
      redirect_to: @url_for "admin"

persona_verify = (assertion, audience) ->

    options = { method:ngx.HTTP_POST, body:to_json {:assertion, :audience} }
    res, err = ngx.location.capture('/persona/', options)

    if not res then
      return { err: res }

    if res.status >= 200 and res.status < 300 then
      return from_json res.body
    else
      return {
        status:res.status,
        body:res.body
      }

class extends lapis.Application
  layout: require "photongx.views.layout"
  views_prefix: "photongx.views"

  @before_filter =>
    @current_user = Users\read_session @

  [index: "/"]: =>
    if @current_user
      return redirect_to: @url_for "albums"
    render:true

  [albums: "/albums"]: require_login =>
    @albums = Albums\select "where user_id = ? order by id DESC", @current_user.id
    --@albums = Albums\select "left join images on albums.id=images.album_id where albums.user_id = ? order by albums.id DESC, images.views DESC", @current_user.id, fields:"distinct on (albums.id) *"
    --images = Images\get_coverimages @albums
    for i=1, #@albums
      album = @albums[i]
      album.image = (Images\get_coverimage album.id)[1]
      album.url = @url_for("album", token:album.token, title:album.title)
    render: true

  [tokenalbums: "/albums/:slug/"]: capture_errors =>
    @accesstokens = Accesstokens\for_slug @params.slug
    unless #@accesstokens > 0
      return render: "error", status: 404
    album_ids = [a.album_id for a in *@accesstokens]
    @albums = Albums\find_all album_ids

    -- FIXME improve SQL to single statement
    for album in *@albums
      images = Images\get_coverimage album.id
      album.image = images[1]
      -- Override the token with our given slug so the template generates the correct URLs
      album.slug = @params.slug
      album.url = @url_for("tokenalbum", slug:@params.slug, token:album.token, title:album.title)
    render: "albums"

  [album: "/album/:token/:title/"]: =>
    if @current_user
      @album = Albums\find token:@params.token
      unless @album
        return render:"error", status:404
      unless @current_user.id == @album.user_id
        return render:"error", status:403
    unless @album return render:"error", status:403
    @album.views = @album.views + 1
    @album\update "views"
    @images = Images\select "where album_id = ? ORDER BY date, file_name", @album.id, fields: "*, "..imagedatesql
    @albumurl = @url_for('album', token:@params.token, title:@album.title)
    @albumsurl = @url_for('albums')
    render: true

  [tokenalbum: "/album/:slug/:token/:title/"]: =>
    @album = Albums\find token:@params.token
    unless @album
      return render:"error", status:404
    valid_token = Accesstokens\validate_album @params.slug, @album.id
    unless valid_token
      return render:"error", status:410
    @albumurl = @url_for('tokenalbum', slug:@params.slug, token:@album.token, title:@album.title)
    @albumsurl = @url_for('tokenalbums', slug:@params.slug)
    @album.views = @album.views + 1
    @album\update "views"
    @images = Images\select "where album_id = ? ORDER BY date, file_name", @album.id, fields: "*, "..imagedatesql
    render: "album"

--
-- This view gets called from a rewrite_by_lua handler so it exits
-- because: "Note that when calling ngx.exit(ngx.OK) within a rewrite_by_lua handler, the nginx request processing control flow will still continue to the content handler."
--
  [img: "/img/:token/:filename"]: =>

    -- Check if user has permission to see image or return 403
    -- Check if token is valid
    -- Set real url or return 404

    -- Simple auth check for now
    --unless @current_user
    --  ngx.exit(403)

    -- TODO maybe disallow original?

    @image = Images\find token:@params.token
    unless @image
      ngx.exit(404)

    -- Check access tokens and return status 410 if expired
    imguri = '/real' .. @image\file_path! .. '/' ..@params.filename
    ngx.req.set_uri imguri, true

-- FIXME require_login
  [zipalbum: "/archive/album/:token/:title/"]: =>
    if @current_user
      @album = Albums\find token:@params.token
      unless @album
        return render:"error", status:404
      unless @current_user.id == @album.user_id
        return render:"error", status:403
    -- FIXME
    @album = Albums\find token:@params.token
    unless @album return render:"error", status:403

    @images = Images\select "where album_id = ? ORDER BY date, file_name", @album.id, fields: "*, "..imagedatesql

    -- Craft the body request that modzip wants

    reqbody = {}

    sample = "- 428    /foo.txt   My Document1.txt"

    for image in *@images
      size = image\get_file_size!
      url = image\real_file_name!
      name = image.file_name
      table.insert(reqbody, table.concat({'-', size, url, name}, ' '))

    reqbody = table.concat(reqbody, '\n')

    --do
    --  json:{:reqbody}

    ngx.header["X-Archive-Files"] = 'zip'
    ngx.header['Content-Type'] = 'application/force-download'
    ngx.header['Content-Disposition'] = "attachment; filename=#{@album.title}.zip"
    ngx.req.set_header "X-Archive-Files", 'zip'
    -- Capture the zip file from modzip
    options = { method:ngx.HTTP_GET, body:reqbody }
    res, err = ngx.location.capture('/zip', options)

    unless res
      return render:"error", status:500

    --if res.status >= 200 and res.status < 300 then
    --return layout:false, res.body
    return layout:false, reqbody

  [persona_login: "/api/persona/login"]: respond_to {
    POST: capture_errors_json =>
      body = ngx.req.get_body_data!
      if body
        body = from_json body

        verification_data = persona_verify body.assertion, config.site
        if verification_data.status == 'okay'
          Users\write_session @, verification_data
          return json:verification_data
      json: {email:false}
  }
  [persona_status: "/api/persona/status"]: respond_to {
    GET: =>
      render: true

    POST: capture_errors_json =>
      cu = @current_user
      if cu
        return json:{email:cu.email}
      json:{email:false}
  }
  [user_logout: "/api/persona/logout"]: =>
    @session.user = false
    json: {email:false}

-- ALBUMS API view - get albums or add new album
  [apialbums: "/api/albums"]: respond_to {
    GET: capture_errors_json require_login =>
      albums = assert_error Albums\select "where user_id = ?", @current_user.id
      json: {:albums}

    POST: capture_errors_json require_login json_params =>

      assert_valid @params, {
        { "name", exists: true, min_length: 1 }
      }
      -- Get or create
      album = Albums\find user_id: @current_user.id, title:@params.name
      unless album
        album = Albums\create @current_user.id, @params.name
      json: { album: album }
  }

  "/api/albums/:album_id": respond_to {
    PUT: capture_errors_json require_login json_params =>
      album = Albums\find id:@params.album_id, user_id: @current_user.id
      album.title = @params.title
      album\update "title"
      json: {:album}
    DELETE: capture_errors_json require_login =>
      album = Albums\find id:@params.album_id, user_id: @current_user.id
      unless album
        return render:"error", status:404
      album\delete!
      json:album
  }

  "/api/images/:album_id": respond_to {

    GET: capture_errors_json require_login =>
      album = Albums\find id:@params.album_id
      unless album
        return render:"error", status:404
      unless album.user_id == @current_user.id
        return render:"error", status:403
      images = assert_error Images\select "where album_id = ? ORDER BY date", @params.album_id, fields: "*, "..imagedatesql
      --- TODO maybe use raw query to cast hstore to json?
      -- Examples found:https://gist.github.com/WoLpH/2318757
      -- Or use postgresql 9.3 which can cast hstore to JSON
      for image in *images
        if image.metadata
          newstr, n, err = ngx.re.gsub(image.metadata, "=>", ":")
          image.metadata = from_json '{'..newstr..'}'
      json: {:images}
  }

  "/api/accesstokens/:album_id": respond_to {

    GET: capture_errors_json require_login =>
      accesstokens = Accesstokens\select "where user_id = ? and album_id = ?", @current_user.id, @params.album_id
      json: {:accesstokens}

    DELETE: =>
      accesstoken = Accesstokens\find id:@params.album_id, user_id:@current_user.id
      unless accesstoken
        return render:"error", status:404
      accesstoken\delete!
      json:{ :accesstoken }
  }

  [images: "/api/images"]: respond_to {
    GET:capture_errors_json require_login  =>
      images = assert_error Images\select "where user_id = ?", @current_user.id
      json: {:images}

    POST: capture_errors_json require_login =>
        assert_valid @params, {
            {'upload', file_exists: true}
            {'filename', exists: true}
            {'title', exists: true}
            {'token', exists: true}
            {'checksum', exists: true}
        }
        {:upload, :title, :filename, :token, :checksum} = @params
        pattern = '\\.(jpe?g|gif|png|crw|raw)$'
        unless ngx.re.match(filename, pattern, "i")
            return status:403, json:{status:403, result:'Filename must be of image type'}
        file = @params.upload
        album = assert_error Albums\find user_id: @current_user.id, token:token
        success, image = pcall -> Images\create @current_user.id, album.id, filename
        -- Since there is a likely chance our silly token can be duplicate we just try to generate image again if it fails
        while not success
          success, image = pcall -> Images\create @current_user.id, album.id, filename
        content = file.content
        real_file_name = image\real_file_name!
        diskfile = io.open real_file_name, 'w'
        unless diskfile
          -- TODO delete created image from SQL
          return status:403, json:{status:403, error:"Permission denied"}
        diskfile\write file.content
        diskfile\flush!
        diskfile\close
        redis = Redis!
        queue = assert_error redis\queue image.token
        status:200, json:{status:200, result:'Image successfully uploaded'}
  }

  [photostreamimages: "/api/photostreamimages"]: respond_to {
    GET:capture_errors_json require_login  =>
      photostreamimages = assert_error Images\select "WHERE user_id = ? ORDER BY date DESC", @current_user.id, fields: "*, "..imagedatesql
      json: {:photostreamimages}
    }

  "/api/albumttl/:album_id": respond_to {
    POST: capture_errors_json require_login =>
      assert_valid @params, {
        { "name", exists: true}
        { "ttl", exists: true}
      }
      ttl = tonumber @params.ttl
      -- TTL not a number ? Assume forever.
      if ttl == nil
          ttl = 2^32 -- ~150 years

      name = @params.name
      album = Albums\find id:@params.album_id, user_id: @current_user.id
      accesstoken = Accesstokens\create @current_user.id, album.id, name, ttl

      json: {:album,:accesstoken}
    }

  [admin: "/admin/"]: =>
    layout:'admin'

  "/api/tag": =>
    json: {token: generate_token 6}

  "/api/image/:image_id": respond_to {
    DELETE: capture_errors_json require_login =>
      image = Images\find id:@params.image_id, user_id: @current_user.id
      unless image
        return render:"error", status:"404"
      image\delete!
      json:image
  }

  "/api/img/click": =>
    assert_valid @params, {
      { "img", exists: true, min_length: 6 }
    }
    @image = Images\find token:@params.img
    unless @image
      return render: "error", status: 404
    @image.views = @image.views + 1
    @image\update "views"

    json: {views: @image.views}

  [adminapi: "/admin/api/all"]: =>
    ok = "test"
    json: { status: ok}

  "/api/queue": require_login capture_errors_json =>
    redis = Redis!
    queue = assert_error redis\queue_length!
    json: {counter:queue}

  "/db/make": =>
    schema = require "schema"
    schema.make_schema!
    json: { status: "ok" }

  "/db/destroy": require_login =>
    -- Hard coded to first user for now
    if @current_user.id == 1
      schema = require "schema"
      schema.destroy_schema!
      return json: { status: "ok" }
    json: status: 403

  "/db/migrate": require_login =>
    -- Hard coded to first user for now
    if @current_user.id == 1
      import run_migrations from require "lapis.db.migrations"
      run_migrations require "migrations"
      return json: { status: "ok" }
    json: status: 403

  "/debug": =>
    layout:false, "hello"