hotfix: misnamed variable

hotfix: misnamed variable

import escape from require "lapis.util"
http = require "lapis.nginx.http"
import render_html from require "lapis.html"

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

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

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

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

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

  mkNonce: =>
    now=os.time!
    hash=now..encode_base64 hmac_sha1 now..ranval!,ranval!..now
    hash=hash\gsub "%/","_"
    hash\sub 1,20

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

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

import escape from require "lapis.util"
http = require "lapis.nginx.http"
import render_html from require "lapis.html"

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

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

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

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

  tryLogin: (params) =>
    nonce=@mkNonce
    res=http.simple @fillUrl params.otp, nonce
    if res\find "status=OK", 1, true and res\find "nonce="..nonce, 1, true
      entry=YubiCloudModel\find idstr: params.otp\lower!\sub(-33)
      if entry
        return entry\get_user!, res
    if @required
      return nil, res
    else
      return false, res

  mkNonce: =>
    now=os.time!
    hash=now..encode_base64 hmac_sha1 now..ranval!,ranval!..now
    hash=hash\gsub "%/","_"
    hash\sub 1,20

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

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


Placeholder.

Placeholder.

Class = require "class"

Tile = Class "Tile",
	__init: (arg) =>
		-- FIXME: Errors on missing parameters?

		@terrain = arg.terrain
		@structure = arg.structure

Class "Map",
	__init: (arg) =>
		@noises = arg.noises or nil -- FIXME
		@terrainChanges = arg.terrainChanges or {}

	---
	-- @return (Tile)
	get: (x, y, z) =>

	---
	-- @return nil
	set: (x, y, z, arguments) =>

	---
	-- @return (string)
	toMoon: =>

	__class: {
		:Tile

		---
		-- @param str (string)
		fromMoon: (str) =>
	}

Class = require "class"

Tile = Class "Tile",
	__init: (arg) =>
		-- FIXME: Errors on missing parameters?

		@terrain = arg.terrain
		@structure = arg.structure

Class "Map",
	:Tile
	---
	-- @return (Tile)
	get: (x, y, z) =>

	---
	-- @return nil
	set: (x, y, z, arguments) =>

	---
	-- @return (string)
	toMoon: =>

	---
	-- @param str (string)
	fromMoon: (str) =>


Get rid of print for finding shows.

Get rid of print for finding shows.

bktree = require "bk-tree"
rex = require "rex_pcre"
cli = require "cliargs"
xml = require "xml"
lfs = require "lfs"

import insert from table

contains = (t, item) ->
	for k, v in pairs t
		return true if v == item
	false

local dir_list, dir_listing
dir_listing = (path, file) ->
	f = path .. '/' .. file
	attr = lfs.attributes(f).mode
	{file, attr, if attr == 'directory' then dir_list(f)}

dir_list = (path) ->
	[dir_listing(path, file) for file in lfs.dir(path) when not contains({'.', '..'}, file)]

map_listing = (structure, func) ->
	results = {}
	for k, v in pairs structure
		res = func(v, map_listing)
		insert(results, res) if res
	results

print_listing = (value, func) ->
	{file, mode, data} = value
	print file, mode
	func(data, print_listing) if data

file_ext = (str) ->
	"." .. str\match(".([^%.]+)$")

dbg = (b) ->
	"[DBG: #{(b and 'Y') or 'N'}]"

perform_action = (old, new, action) ->
	dbg(debug) .. switch action
		when "copy"
			"[cp]: Not yet implemented."
		when "move"
			"[mv]: Not yet implemented."
		when "mkdir"
			lfs.mkdir(new) if not debug
			"[mkdir]: #{old} to: #{new}"
		when "symlink", "hardlink"
			lfs.link(old, new, action != "hardlink") if not debug
			"#{action} [link]: #{old} to: #{new}"
		else
			"unknown action: #{action}"

prompt_for_alternatives = (names, original) ->

	if not names or #names == 0
		print "-- no results for #{original}, skipping -- "
		return original

	print "----------------------------"
	print "Original String: #{original}"
	for i, word in pairs names
		print "##{i} #{word.str}"

	io.stdout\write("Enter number to pick (blank to keep original): ")
	n = io.stdin\read()

	return if tonumber(n) then names[tonumber(n)].str else original

find_alt_names = (tree, name, max_dist) ->

	io.stdout\write("Skip: #{name}? (Y/N): ")
	if io.stdin\read() == "Y"
		io.stdin\flush()
		return {}
	max_dist = (max_dist > 0 and max_dist) or (max_dist < 0 and #name) or 100

	i = 0
	result = {}
	while #result == 0 and i < max_dist
		result = tree\query_sorted(name, i)
		i = i + 1

	[result[i] for i=1, #result when result[i].distance > 0]

process_xml = (string) ->
	shows = {}
	xtable = xml.load(string)

	if not xtable.xml == "anime-list" then error "incorrect XML"
	for k, t in pairs xtable
		if type(t) == "table"
			if t.xml == "anime" then
				for k2, st in pairs t
					if st.xml == "name"
						shows[#shows+1] = st[1] if type(st[1]) == "string"

	shows

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata

		if rex.gsub(file, regex, '')

			isdir = (md) -> md == 'directory'

			dir_transform = (f) ->
				f = rex.gsub(f, regex, '')
				f = f\gsub('[_]', ' ')
				f = f\match("^%s*(.-)%s*$")
				altf = prompt_for_alternatives(find_alt_names(search_tree, f, maxdist), f) if parent.in == inputdir
				f = altf or f
				parent.out .. '/' .. f

			file_transform = (f) ->
				f = rex.gsub(f, regex, '')
				f = rex.match(f, [[(.*)(v\d)]]) or f
				f = f\gsub('[ ]', '')
				f = rex.gsub(f, [[([^\d]*)(\d+)[^\d]*$]], [[%1_ep%2]], 1)
				f ..= file_ext(file)
				parent.out .. '/' .. f

			in_f = parent.in .. '/' .. file
			out_f = if isdir(mode) then dir_transform(file) else file_transform(file)

			action = if isdir(mode) then "mkdir" else args["a"]
			perform_action(in_f, out_f, action)
			process_files(data, regex, {in: in_f, out: out_f}) if data

		else
			print "ERR: ", file, mode

cli\set_name("regexulon")
cli\add_argument("DIR", "directory to scan")
cli\add_argument("TARGET", "target directory")
cli\optarg("IGNORED", "ignored files/directories", "")
cli\add_option("-a, --action=ACTION", "action to take: cp, mv, symlink, hardlink", "hardlink")
cli\add_flag("-n, --noprompt", "don't prompt when asking for new directory names, pick closest option.")
cli\add_flag("-e, --existing", "skip creating previously existing target directories.")
cli\add_flag("-d, --debug", "script will simulate execution.")
cli\add_flag("-V, --version", "prints the program version")
cli\add_flag("-v, --verbose", "verbose output")

export args = cli\parse_args()
return if not args

af = io.open("anime-list-full.xml", "r")
results = process_xml(af\read("*all"))

export search_tree = bktree\new()
{search_tree\insert(x) for k, x in pairs results}

io.stdout\write("Similarity Search Max Distance: ")
export maxdist = tonumber(io.stdin\read())

ignored_files = switch type(args["IGNORED"])
	when "string" then {}
	else args["IGNORED"]

export inputdir = args["DIR"]
export targetdir = args["TARGET"]
export debug = args["d"]

files = dir_list(args["DIR"])
regex = [==[\(.*?\)|\[.*?\]]==]

processed = process_files(files, regex, {in: inputdir, out: targetdir})

bktree = require "bk-tree"
rex = require "rex_pcre"
cli = require "cliargs"
xml = require "xml"
lfs = require "lfs"

import insert from table

contains = (t, item) ->
	for k, v in pairs t
		return true if v == item
	false

local dir_list, dir_listing
dir_listing = (path, file) ->
	f = path .. '/' .. file
	attr = lfs.attributes(f).mode
	{file, attr, if attr == 'directory' then dir_list(f)}

dir_list = (path) ->
	[dir_listing(path, file) for file in lfs.dir(path) when not contains({'.', '..'}, file)]

map_listing = (structure, func) ->
	results = {}
	for k, v in pairs structure
		res = func(v, map_listing)
		insert(results, res) if res
	results

print_listing = (value, func) ->
	{file, mode, data} = value
	print file, mode
	func(data, print_listing) if data

file_ext = (str) ->
	"." .. str\match(".([^%.]+)$")

dbg = (b) ->
	"[DBG: #{(b and 'Y') or 'N'}]"

perform_action = (old, new, action) ->
	dbg(debug) .. switch action
		when "copy"
			"[cp]: Not yet implemented."
		when "move"
			"[mv]: Not yet implemented."
		when "mkdir"
			lfs.mkdir(new) if not debug
			"[mkdir]: #{old} to: #{new}"
		when "symlink", "hardlink"
			lfs.link(old, new, action != "hardlink") if not debug
			"#{action} [link]: #{old} to: #{new}"
		else
			"unknown action: #{action}"

prompt_for_alternatives = (names, original) ->

	if not names or #names == 0
		print "-- no results for #{original}, skipping -- "
		return original

	print "----------------------------"
	print "Original String: #{original}"
	for i, word in pairs names
		print "##{i} #{word.str}"

	io.stdout\write("Enter number to pick (blank to keep original): ")
	n = io.stdin\read()

	return if tonumber(n) then names[tonumber(n)].str else original

find_alt_names = (tree, name, max_dist) ->

	io.stdout\write("Skip: #{name}? (Y/N): ")
	if io.stdin\read() == "Y"
		io.stdin\flush()
		return {}
	max_dist = (max_dist > 0 and max_dist) or (max_dist < 0 and #name) or 100

	i = 0
	result = {}
	while #result == 0 and i < max_dist
		result = tree\query_sorted(name, i)
		i = i + 1

	[result[i] for i=1, #result when result[i].distance > 0]

process_xml = (string) ->
	shows = {}
	xtable = xml.load(string)

	if not xtable.xml == "anime-list" then error "incorrect XML"
	for k, t in pairs xtable
		if type(t) == "table"
			if t.xml == "anime" then
				for k2, st in pairs t
					if st.xml == "name"
						shows[#shows+1] = st[1] if type(st[1]) == "string"
						print shows[#shows]

	shows

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata

		if rex.gsub(file, regex, '')

			isdir = (md) -> md == 'directory'

			dir_transform = (f) ->
				f = rex.gsub(f, regex, '')
				f = f\gsub('[_]', ' ')
				f = f\match("^%s*(.-)%s*$")
				altf = prompt_for_alternatives(find_alt_names(search_tree, f, maxdist), f) if parent.in == inputdir
				f = altf or f
				parent.out .. '/' .. f

			file_transform = (f) ->
				f = rex.gsub(f, regex, '')
				f = rex.match(f, [[(.*)(v\d)]]) or f
				f = f\gsub('[ ]', '')
				f = rex.gsub(f, [[([^\d]*)(\d+)[^\d]*$]], [[%1_ep%2]], 1)
				f ..= file_ext(file)
				parent.out .. '/' .. f

			in_f = parent.in .. '/' .. file
			out_f = if isdir(mode) then dir_transform(file) else file_transform(file)

			action = if isdir(mode) then "mkdir" else args["a"]
			perform_action(in_f, out_f, action)
			process_files(data, regex, {in: in_f, out: out_f}) if data

		else
			print "ERR: ", file, mode

cli\set_name("regexulon")
cli\add_argument("DIR", "directory to scan")
cli\add_argument("TARGET", "target directory")
cli\optarg("IGNORED", "ignored files/directories", "")
cli\add_option("-a, --action=ACTION", "action to take: cp, mv, symlink, hardlink", "hardlink")
cli\add_flag("-n, --noprompt", "don't prompt when asking for new directory names, pick closest option.")
cli\add_flag("-e, --existing", "skip creating previously existing target directories.")
cli\add_flag("-d, --debug", "script will simulate execution.")
cli\add_flag("-V, --version", "prints the program version")
cli\add_flag("-v, --verbose", "verbose output")

export args = cli\parse_args()
return if not args

af = io.open("anime-list-full.xml", "r")
results = process_xml(af\read("*all"))

export search_tree = bktree\new()
{search_tree\insert(x) for k, x in pairs results}

io.stdout\write("Similarity Search Max Distance: ")
export maxdist = tonumber(io.stdin\read())

ignored_files = switch type(args["IGNORED"])
	when "string" then {}
	else args["IGNORED"]

export inputdir = args["DIR"]
export targetdir = args["TARGET"]
export debug = args["d"]

files = dir_list(args["DIR"])
regex = [==[\(.*?\)|\[.*?\]]==]

processed = process_files(files, regex, {in: inputdir, out: targetdir})


navnedag: arne was butthurt

navnedag: arne was butthurt

Former-commit-id: 94a1d247d2c1258da720bfe20c95d32b1526929b [formerly e00909c2dee464f4b66cdc0484315cb9d6f17f77]
Former-commit-id: 4fbe044c9d7592bb1cc5006dbd4b160423f65d5d

os = require'os'
util = require'util'
data =
  "01.01":"Jesus"
  "02.01":"Dagfinn Dagfrid"
  "03.01":"Alfred Alf"
  "04.01":"Roar Roger"
  "05.01":"Hanna Hanne"
  "06.01":"Aslaug Åslaug"
  "07.01":"Eldbjørg Knut"
  "08.01":"Torfinn Turid"
  "09.01":"Gunnar Gunn"
  "10.01":"Sigmund Sigrun"
  "11.01":"Børge Børre"
  "12.01":"Reinhard Reinert"
  "13.01":"Gisle Gislaug"
  "14.01":"Herbjørn Herbjørg"
  "15.01":"Laurits Laura"
  "16.01":"Hjalmar Hilmar"
  "17.01":"Anton Tønnes Tony"
  "18.01":"Hildur Hild"
  "19.01":"Marius Margunn"
  "20.01":"Fabian Sebastian Bastian"
  "21.01":"Agnes Agnete"
  "22.01":"Ivan Vanja"
  "23.01":"Emil Emilie Emma"
  "24.01":"Joar Jarle Jarl"
  "25.01":"Paul Pål"
  "26.01":"Øystein Esten"
  "27.01":"Gaute Gurly Gry"
  "28.01":"Karl Karoline"
  "29.01":"Herdis Hermod Hermann"
  "30.01":"Gunnhild Gunda"
  "31.01":"Idun Ivar"
  "01.02":"Birte Bjarte"
  "02.02":"Jomar Jostein"
  "03.02":"Ansgar Asgeir"
  "04.02":"Veronika Vera"
  "05.02":"Agate Ågot"
  "06.02":"Dortea Dorte"
  "07.02":"Rikard Rigmor Riborg"
  "08.02":"Åshild Åsne"
  "09.02":"Lone Leikny"
  "10.02":"Ingfrid Ingrid"
  "11.02":"Ingve Yngve"
  "12.02":"Randulf Randi Ronja"
  "13.02":"Svanhild Svanlaug"
  "14.02":"Hjørdis Jardar"
  "15.02":"Sigfred Sigbjørn"
  "16.02":"Julian Juliane Jill"
  "17.02":"Aleksandra Sandra Sondre"
  "18.02":"Frøydis Frode"
  "19.02":"Ella Elna"
  "20.02":"Halldis Halldor"
  "21.02":"Samuel Selma Celine"
  "22.02":"Tina Tine Tim"
  "23.02":"Torstein Torunn"
  "24.02":"Mattias Mattis Mats"
  "25.02":"Viktor Viktoria"
  "26.02":"Inger Ingjerd"
  "27.02":"Laila Lill"
  "28.02":"Marina Maren"
  "01.03":"Audny Audun"
  "02.03":"Erna Ernst"
  "03.03":"Gunnbjørg Gunnveig"
  "04.03":"Ada Adrian"
  "05.03":"Patrick Patricia"
  "06.03":"Annfrid Andor"
  "07.03":"Arild Are"
  "08.03":"Beate Betty Bettina"
  "09.03":"Sverre Sindre"
  "10.03":"Edel Edle"
  "11.03":"Edvin Tale"
  "12.03":"Gregor Gro"
  "13.03":"Greta Grete"
  "14.03":"Mathilde Mette"
  "15.03":"Christel Christer Chris"
  "16.03":"Gudmund Gudny"
  "17.03":"Gjertrud Trude"
  "18.03":"Aleksander Sander Edvard"
  "19.03":"Josef Josefine"
  "20.03":"Joakim Kim"
  "21.03":"Bendik Bengt Bent"
  "22.03":"Paula Pauline"
  "23.03":"Gerda Gerd"
  "24.03":"Ulrikke Rikke"
  "25.03":"Maria Marie Mari"
  "26.03":"Gabriel Glenn"
  "27.03":"Rudolf Rudi"
  "28.03":"Åsta Åste"
  "29.03":"Jonas Jonatan"
  "30.03":"Holger Olga"
  "31.03":"Vebjørn Vegard"
  "01.04":"Aron Arve Arvid"
  "02.04":"Sigvard Sivert"
  "03.04":"Gunnvald Gunnvor"
  "04.04":"Nanna Nancy Nina"
  "05.04":"Irene Eirin Eril"
  "06.04":"Åsmund Asmund"
  "07.04":"Oddveig Oddvin"
  "08.04":"Asle Atle"
  "09.04":"Rannveig Rønnaug"
  "10.04":"Ingvald Ingveig"
  "11.04":"Ylva Ulf"
  "12.04":"Julius Julie"
  "13.04":"Asta Astrid"
  "14.04":"Ellinor Nora"
  "15.04":"Oda Odin Odd"
  "16.04":"Magnus Mons"
  "17.04":"Elise Else Elsa"
  "18.04":"Eilen Eilert"
  "19.04":"Arnfinn Arnstein"
  "20.04":"Kjellaug Kjellrun"
  "21.04":"Jeanette Jannike"
  "22.04":"Oddgeir Oddny"
  "23.04":"Georg Jørgen Jørg"
  "24.04":"Albert Olaug"
  "25.04":"Markus Mark"
  "26.04":"Terese Tea"
  "27.04":"Charles Charlotte Lotte"
  "28.04":"Vivi Vivian"
  "29.04":"Toralf Torolf"
  "30.04":"Gina Gitte"
  "01.05":"Filip Valborg"
  "02.05":"Åsa Åse"
  "03.05":"Gjermund Gøril"
  "04.05":"Monika Mona"
  "05.05":"Gudbrand Gullborg"
  "06.05":"Guri Gyri"
  "07.05":"Maia Mai Maiken"
  "08.05":"Åge Åke"
  "09.05":"Kasper Jesper"
  "10.05":"Asbjørg Asbjørn Espen"
  "11.05":"Magda Malvin"
  "12.05":"Normann Norvald"
  "13.05":"Linda Line Linn"
  "14.05":"Kristian Kristen Karsten"
  "15.05":"Hallvard Hallvor"
  "16.05":"Sara Siren"
  "17.05":"Harald Ragnhild"
  "18.05":"Eirik Erik Erika"
  "19.05":"Torjus Torje Truls"
  "20.05":"Bjørnar Bjørnhild"
  "21.05":"Helene Ellen Eli"
  "22.05":"Henning Henny"
  "23.05":"Oddleif Oddlaug"
  "24.05":"Ester Iris"
  "25.05":"Ragna Ragnar"
  "26.05":"Annbjørg Annlaug"
  "27.05":"Katinka Cato"
  "28.05":"Vilhelm William Willy"
  "29.05":"Magnar Magnhild"
  "30.05":"Gard Geir"
  "31.05":"Pernille Preben"
  "01.06":"June Juni"
  "02.06":"Runa Runar Rune"
  "03.06":"Rasmus Rakel"
  "04.06":"Heidi Heid"
  "05.06":"Torbjørg Torbjørn Torben"
  "06.06":"Gustav Gyda"
  "07.06":"Robert Robin"
  "08.06":"Renate René"
  "09.06":"Kolbein Kolbjørn"
  "10.06":"Ingolf Ingunn"
  "11.06":"Borgar Bjørge Bjørg"
  "12.06":"Sigfrid Sigrid Siri"
  "13.06":"Tone Tonje Tanja"
  "14.06":"Erlend Erland"
  "15.06":"Vigdis Viggo"
  "16.06":"Torhild Toril Tiril"
  "17.06":"Botolf Bodil"
  "18.06":"Bjarne Bjørn"
  "19.06":"Erling Elling"
  "20.06":"Salve Sølve Sølvi"
  "21.06":"Agnar Annar"
  "22.06":"Håkon Maud"
  "23.06":"Elfrid Eldrid"
  "24.06":"Johannes Jon Hans"
  "25.06":"Jørund Jorunn"
  "26.06":"Jenny Jonny"
  "27.06":"Aina Ina Ine"
  "28.06":"Lea Leo Leon"
  "29.06":"Peter Petter Per"
  "30.06":"Solbjørg Solgunn"
  "01.07":"Ask Embla"
  "02.07":"Kjartan Kjellfrid"
  "03.07":"Andrea Andrine André"
  "04.07":"Ulrik Ulla"
  "05.07":"Mirjam Mina"
  "06.07":"Torgrim Torgunn"
  "07.07":"Håvard Hulda"
  "08.07":"Sunniva Synnøve Synne"
  "09.07":"Gøran Jøran Ørjan"
  "10.07":"Anita Anja"
  "11.07":"Kjetil Kjell"
  "12.07":"Elias Eldar"
  "13.07":"Mildrid Melissa Mia"
  "14.07":"Solfrid Solrun"
  "15.07":"Oddmund Oddrun"
  "16.07":"Susanne Sanna"
  "17.07":"Guttorm Gorm"
  "18.07":"Arnulf Ørnulf"
  "19.07":"Gerhard Gjert"
  "20.07":"Margareta Margit Marit"
  "21.07":"Johanne Janne Jane"
  "22.07":"Malene Malin Mali"
  "23.07":"Brita Brit Britt"
  "24.07":"Kristine Kristin Kristi"
  "25.07":"Jakob Jack Jim"
  "26.07":"Anna Anne Ane"
  "27.07":"Marita Rita"
  "28.07":"Reidar Reidun"
  "29.07":"Olav Ola Ole"
  "30.07":"Aurora Audhild Aud"
  "31.07":"Elin Eline"
  "01.08":"Peder Petra"
  "02.08":"Karen Karin"
  "03.08":"Oline Oliver Olve"
  "04.08":"Arnhild Arna Arne"
  "05.08":"Osvald Oskar"
  "06.08":"Gunnlaug Gunnleiv"
  "07.08":"Didrik Doris"
  "08.08":"Evy Yvonne"
  "09.08":"Ronald Ronny"
  "10.08":"Lorents Lars Lasse"
  "11.08":"Torvald Tarald"
  "12.08":"Klara Camilla"
  "13.08":"Anny Anine Ann"
  "14.08":"Hallgeir Hallgjerd"
  "15.08":"Margot Mary Marielle"
  "16.08":"Brynjulf Brynhild"
  "17.08":"Verner Wenche"
  "18.08":"Tormod Torodd"
  "19.08":"Sigvald Sigve"
  "20.08":"Bernhard Bernt"
  "21.08":"Ragnvald Ragni"
  "22.08":"Harriet Harry"
  "23.08":"Signe Signy"
  "24.08":"Belinda Bertil"
  "25.08":"Ludvig Lovise Louise"
  "26.08":"Øyvind Eivind Even"
  "27.08":"Roald Rolf"
  "28.08":"Artur August"
  "29.08":"Johan Jone Jo"
  "30.08":"Benjamin Ben"
  "31.08":"Berta Berte"
  "01.09":"Solveig Solvår"
  "02.09":"Lisa Lise Liss"
  "03.09":"Alise Alvhild Vilde"
  "04.09":"Ida Idar"
  "05.09":"Brede Brian Njål"
  "06.09":"Sollaug Siril Siv"
  "07.09":"Regine Rose"
  "08.09":"Amalie Alma Allan"
  "09.09":"Trygve Tyra Trym"
  "10.09":"Tord Tor"
  "11.09":"Dagny Dag"
  "12.09":"Jofrid Jorid"
  "13.09":"Stian Stig"
  "14.09":"Ingebjørg Ingeborg"
  "15.09":"Aslak Eskil"
  "16.09":"Lillian Lilly"
  "17.09":"Hildebjørg Hildegunn"
  "18.09":"Henriette Henry"
  "19.09":"Konstanse Connie"
  "20.09":"Tobias Tage"
  "21.09":"Trine Trond"
  "22.09":"Kyrre Kåre"
  "23.09":"Snorre Snefrid"
  "24.09":"Jan Jens"
  "25.09":"Ingvar Yngvar"
  "26.09":"Einar Endre"
  "27.09":"Dagmar Dagrun"
  "28.09":"Lena Lene"
  "29.09":"Mikael Mikal Mikkel"
  "30.09":"Helga Helge Hege"
  "01.10":"Rebekka Remi"
  "02.10":"Live Liv"
  "03.10":"Evald Evelyn"
  "04.10":"Frans Frank"
  "05.10":"Brynjar Boye Bo"
  "06.10":"Målfrid Møyfrid"
  "07.10":"Birgitte Birgit Berit"
  "08.10":"Benedikte Bente"
  "09.10":"Leidulf Leif"
  "10.10":"Fridtjof Frida Frits"
  "11.10":"Kevin Kennet Kent"
  "12.10":"Valter Vibeke"
  "13.10":"Torgeir Terje Tarjei"
  "14.10":"Kaia Kai"
  "15.10":"Hedvig Hedda"
  "16.10":"Flemming Finn"
  "17.10":"Marta Marte"
  "18.10":"Kjersti Kjerstin"
  "19.10":"Tora Tore"
  "20.10":"Henrik Heine Henrikke"
  "21.10":"Bergljot Birger"
  "22.10":"Karianne Karine Kine"
  "23.10":"Severin Søren"
  "24.10":"Eilif Eivor"
  "25.10":"Margrete Merete Märtha"
  "26.10":"Amandus Amanda"
  "27.10":"Sturla Sture"
  "28.10":"Simon Simen"
  "29.10":"Noralf Norunn"
  "30.10":"Aksel Åmund Ove"
  "31.10":"Edit Edna"
  "01.11":"Veslemøy Vetle"
  "02.11":"Tove Tuva"
  "03.11":"Raymond Roy"
  "04.11":"Otto Ottar"
  "05.11":"Egil Egon"
  "06.11":"Leonard Lennart"
  "07.11":"Ingebrigt Ingelin"
  "08.11":"Ingvild Yngvild"
  "09.11":"Tordis Teodor"
  "10.11":"Gudbjørg Gudveig"
  "11.11":"Martin Morten Martine"
  "12.11":"Torkjell Torkil"
  "13.11":"Kirsten Kirsti"
  "14.11":"Fredrik Fred Freddy"
  "15.11":"Oddfrid Oddvar"
  "16.11":"Edmund Edgar"
  "17.11":"Hugo Hogne Hauk"
  "18.11":"Magne Magny"
  "19.11":"Elisabet Lisbet"
  "20.11":"Halvdan Helle"
  "21.11":"Mariann Marianne"
  "22.11":"Cecilie Silje Sissel"
  "23.11":"Klement Klaus"
  "24.11":"Gudrun Guro"
  "25.11":"Katarina Katrine Kari"
  "26.11":"Konrad Kurt"
  "27.11":"Torlaug Torleif"
  "28.11":"Ruben Rut"
  "29.11":"Sofie Sonja"
  "30.11":"Andreas Anders"
  "01.12":"Arnold Arnljot Arnt"
  "02.12":"Borghild Borgny Bård"
  "03.12":"Sveinung Svein"
  "04.12":"Barbara Barbro"
  "05.12":"Stine Ståle"
  "06.12":"Nils Nikolai"
  "07.12":"Hallfrid Hallstein"
  "08.12":"Marlene Marion Morgan"
  "09.12":"Anniken Annette"
  "10.12":"Judit og Jytte"
  "11.12":"Daniel Dan"
  "12.12":"Pia Peggy"
  "13.12":"Lucia Lydia"
  "14.12":"Steinar Stein"
  "15.12":"Hilda Hilde"
  "16.12":"Oddbjørg Oddbjørn"
  "17.12":"Inga Inge"
  "18.12":"Kristoffer Kate"
  "19.12":"Iselin Isak"
  "20.12":"Abraham Amund"
  "21.12":"Tomas Tom Tommy"
  "22.12":"Ingemar Ingar"
  "23.12":"Sigurd Sjur"
  "24.12":"Adam Eva"
  "25.12":"Ingen navnedag"
  "26.12":"Stefan Steffen"
  "27.12":"Narve Natalie"
  "28.12":"Unni Une Unn"
  "29.12":"Vidar Vemund"
  "30.12":"David Diana Dina"
  "31.12":"Sylfest Sylvia Sylvi"

PRIVMSG:
  "^%pnavnedag$": (source, destination) =>
    now = os.date'%d.%m'
    names = table.concat(util.split(data[now], ' '), ', ')
    say names
  "^%pnavnedag (%d+).(%d+)$": (source, destination, day, month) =>
    day = string.format('%02d', day)
    month = string.format('%02d', month)
    now = day .. '.' .. month
    names = table.concat(util.split(data[now], ' '), ', ')
    say names
  "^%pnavnedag (.*)$": (source, destination, input) =>
    for key, val in pairs(data)
      names = util.split(val, ' ')
      for name in *names
        if util.utf8.lower(name) == util.utf8.lower(input)
          say "#{key}: #{table.concat(names, ', ')}"

os = require'os'
util = require'util'
data =
  "01.01":"Jesus"
  "02.01":"Dagfinn Dagfrid"
  "03.01":"Alfred Alf"
  "04.01":"Roar Roger"
  "05.01":"Hanna Hanne"
  "06.01":"Aslaug Åslaug"
  "07.01":"Eldbjørg Knut"
  "08.01":"Torfinn Turid"
  "09.01":"Gunnar Gunn"
  "10.01":"Sigmund Sigrun"
  "11.01":"Børge Børre"
  "12.01":"Reinhard Reinert"
  "13.01":"Gisle Gislaug"
  "14.01":"Herbjørn Herbjørg"
  "15.01":"Laurits Laura"
  "16.01":"Hjalmar Hilmar"
  "17.01":"Anton Tønnes Tony"
  "18.01":"Hildur Hild"
  "19.01":"Marius Margunn"
  "20.01":"Fabian Sebastian Bastian"
  "21.01":"Agnes Agnete"
  "22.01":"Ivan Vanja"
  "23.01":"Emil Emilie Emma"
  "24.01":"Joar Jarle Jarl"
  "25.01":"Paul Pål"
  "26.01":"Øystein Esten"
  "27.01":"Gaute Gurly Gry"
  "28.01":"Karl Karoline"
  "29.01":"Herdis Hermod Hermann"
  "30.01":"Gunnhild Gunda"
  "31.01":"Idun Ivar"
  "01.02":"Birte Bjarte"
  "02.02":"Jomar Jostein"
  "03.02":"Ansgar Asgeir"
  "04.02":"Veronika Vera"
  "05.02":"Agate Ågot"
  "06.02":"Dortea Dorte"
  "07.02":"Rikard Rigmor Riborg"
  "08.02":"Åshild Åsne"
  "09.02":"Lone Leikny"
  "10.02":"Ingfrid Ingrid"
  "11.02":"Ingve Yngve"
  "12.02":"Randulf Randi Ronja"
  "13.02":"Svanhild Svanlaug"
  "14.02":"Hjørdis Jardar"
  "15.02":"Sigfred Sigbjørn"
  "16.02":"Julian Juliane Jill"
  "17.02":"Aleksandra Sandra Sondre"
  "18.02":"Frøydis Frode"
  "19.02":"Ella Elna"
  "20.02":"Halldis Halldor"
  "21.02":"Samuel Selma Celine"
  "22.02":"Tina Tine Tim"
  "23.02":"Torstein Torunn"
  "24.02":"Mattias Mattis Mats"
  "25.02":"Viktor Viktoria"
  "26.02":"Inger Ingjerd"
  "27.02":"Laila Lill"
  "28.02":"Marina Maren"
  "01.03":"Audny Audun"
  "02.03":"Erna Ernst"
  "03.03":"Gunnbjørg Gunnveig"
  "04.03":"Ada Adrian"
  "05.03":"Patrick Patricia"
  "06.03":"Annfrid Andor"
  "07.03":"Arild Are"
  "08.03":"Beate Betty Bettina"
  "09.03":"Sverre Sindre"
  "10.03":"Edel Edle"
  "11.03":"Edvin Tale"
  "12.03":"Gregor Gro"
  "13.03":"Greta Grete"
  "14.03":"Mathilde Mette"
  "15.03":"Christel Christer Chris"
  "16.03":"Gudmund Gudny"
  "17.03":"Gjertrud Trude"
  "18.03":"Aleksander Sander Edvard"
  "19.03":"Josef Josefine"
  "20.03":"Joakim Kim"
  "21.03":"Bendik Bengt Bent"
  "22.03":"Paula Pauline"
  "23.03":"Gerda Gerd"
  "24.03":"Ulrikke Rikke"
  "25.03":"Maria Marie Mari"
  "26.03":"Gabriel Glenn"
  "27.03":"Rudolf Rudi"
  "28.03":"Åsta Åste"
  "29.03":"Jonas Jonatan"
  "30.03":"Holger Olga"
  "31.03":"Vebjørn Vegard"
  "01.04":"Aron Arve Arvid"
  "02.04":"Sigvard Sivert"
  "03.04":"Gunnvald Gunnvor"
  "04.04":"Nanna Nancy Nina"
  "05.04":"Irene Eirin Eril"
  "06.04":"Åsmund Asmund"
  "07.04":"Oddveig Oddvin"
  "08.04":"Asle Atle"
  "09.04":"Rannveig Rønnaug"
  "10.04":"Ingvald Ingveig"
  "11.04":"Ylva Ulf"
  "12.04":"Julius Julie"
  "13.04":"Asta Astrid"
  "14.04":"Ellinor Nora"
  "15.04":"Oda Odin Odd"
  "16.04":"Magnus Mons"
  "17.04":"Elise Else Elsa"
  "18.04":"Eilen Eilert"
  "19.04":"Arnfinn Arnstein"
  "20.04":"Kjellaug Kjellrun"
  "21.04":"Jeanette Jannike"
  "22.04":"Oddgeir Oddny"
  "23.04":"Georg Jørgen Jørg"
  "24.04":"Albert Olaug"
  "25.04":"Markus Mark"
  "26.04":"Terese Tea"
  "27.04":"Charles Charlotte Lotte"
  "28.04":"Vivi Vivian"
  "29.04":"Toralf Torolf"
  "30.04":"Gina Gitte"
  "01.05":"Filip Valborg"
  "02.05":"Åsa Åse"
  "03.05":"Gjermund Gøril"
  "04.05":"Monika Mona"
  "05.05":"Gudbrand Gullborg"
  "06.05":"Guri Gyri"
  "07.05":"Maia Mai Maiken"
  "08.05":"Åge Åke"
  "09.05":"Kasper Jesper"
  "10.05":"Asbjørg Asbjørn Espen"
  "11.05":"Magda Malvin"
  "12.05":"Normann Norvald"
  "13.05":"Linda Line Linn"
  "14.05":"Kristian Kristen Karsten"
  "15.05":"Hallvard Hallvor"
  "16.05":"Sara Siren"
  "17.05":"Harald Ragnhild"
  "18.05":"Eirik Erik Erika"
  "19.05":"Torjus Torje Truls"
  "20.05":"Bjørnar Bjørnhild"
  "21.05":"Helene Ellen Eli"
  "22.05":"Henning Henny"
  "23.05":"Oddleif Oddlaug"
  "24.05":"Ester Iris"
  "25.05":"Ragna Ragnar"
  "26.05":"Annbjørg Annlaug"
  "27.05":"Katinka Cato"
  "28.05":"Vilhelm William Willy"
  "29.05":"Magnar Magnhild"
  "30.05":"Gard Geir"
  "31.05":"Pernille Preben"
  "01.06":"June Juni"
  "02.06":"Runa Runar Rune"
  "03.06":"Rasmus Rakel"
  "04.06":"Heidi Heid"
  "05.06":"Torbjørg Torbjørn Torben"
  "06.06":"Gustav Gyda"
  "07.06":"Robert Robin"
  "08.06":"Renate René"
  "09.06":"Kolbein Kolbjørn"
  "10.06":"Ingolf Ingunn"
  "11.06":"Borgar Bjørge Bjørg"
  "12.06":"Sigfrid Sigrid Siri"
  "13.06":"Tone Tonje Tanja"
  "14.06":"Erlend Erland"
  "15.06":"Vigdis Viggo"
  "16.06":"Torhild Toril Tiril"
  "17.06":"Botolf Bodil"
  "18.06":"Bjarne Bjørn"
  "19.06":"Erling Elling"
  "20.06":"Salve Sølve Sølvi"
  "21.06":"Agnar Annar"
  "22.06":"Håkon Maud"
  "23.06":"Elfrid Eldrid"
  "24.06":"Johannes Jon Hans"
  "25.06":"Jørund Jorunn"
  "26.06":"Jenny Jonny"
  "27.06":"Aina Ina Ine"
  "28.06":"Lea Leo Leon"
  "29.06":"Peter Petter Per"
  "30.06":"Solbjørg Solgunn"
  "01.07":"Ask Embla"
  "02.07":"Kjartan Kjellfrid"
  "03.07":"Andrea Andrine André"
  "04.07":"Ulrik Ulla"
  "05.07":"Mirjam Mina"
  "06.07":"Torgrim Torgunn"
  "07.07":"Håvard Hulda"
  "08.07":"Sunniva Synnøve Synne"
  "09.07":"Gøran Jøran Ørjan"
  "10.07":"Anita Anja"
  "11.07":"Kjetil Kjell"
  "12.07":"Elias Eldar"
  "13.07":"Mildrid Melissa Mia"
  "14.07":"Solfrid Solrun"
  "15.07":"Oddmund Oddrun"
  "16.07":"Susanne Sanna"
  "17.07":"Guttorm Gorm"
  "18.07":"Arnulf Ørnulf"
  "19.07":"Gerhard Gjert"
  "20.07":"Margareta Margit Marit"
  "21.07":"Johanne Janne Jane"
  "22.07":"Malene Malin Mali"
  "23.07":"Brita Brit Britt"
  "24.07":"Kristine Kristin Kristi"
  "25.07":"Jakob Jack Jim"
  "26.07":"Anna Anne Ane"
  "27.07":"Marita Rita"
  "28.07":"Reidar Reidun"
  "29.07":"Olav Ola Ole"
  "30.07":"Aurora Audhild Aud"
  "31.07":"Elin Eline"
  "01.08":"Peder Petra"
  "02.08":"Karen Karin"
  "03.08":"Oline Oliver Olve"
  "04.08":"Arnhild Arna Arnt"
  "05.08":"Osvald Oskar"
  "06.08":"Gunnlaug Gunnleiv"
  "07.08":"Didrik Doris"
  "08.08":"Evy Yvonne"
  "09.08":"Ronald Ronny"
  "10.08":"Lorents Lars Lasse"
  "11.08":"Torvald Tarald"
  "12.08":"Klara Camilla"
  "13.08":"Anny Anine Ann"
  "14.08":"Hallgeir Hallgjerd"
  "15.08":"Margot Mary Marielle"
  "16.08":"Brynjulf Brynhild"
  "17.08":"Verner Wenche"
  "18.08":"Tormod Torodd"
  "19.08":"Sigvald Sigve"
  "20.08":"Bernhard Bernt"
  "21.08":"Ragnvald Ragni"
  "22.08":"Harriet Harry"
  "23.08":"Signe Signy"
  "24.08":"Belinda Bertil"
  "25.08":"Ludvig Lovise Louise"
  "26.08":"Øyvind Eivind Even"
  "27.08":"Roald Rolf"
  "28.08":"Artur August"
  "29.08":"Johan Jone Jo"
  "30.08":"Benjamin Ben"
  "31.08":"Berta Berte"
  "01.09":"Solveig Solvår"
  "02.09":"Lisa Lise Liss"
  "03.09":"Alise Alvhild Vilde"
  "04.09":"Ida Idar"
  "05.09":"Brede Brian Njål"
  "06.09":"Sollaug Siril Siv"
  "07.09":"Regine Rose"
  "08.09":"Amalie Alma Allan"
  "09.09":"Trygve Tyra Trym"
  "10.09":"Tord Tor"
  "11.09":"Dagny Dag"
  "12.09":"Jofrid Jorid"
  "13.09":"Stian Stig"
  "14.09":"Ingebjørg Ingeborg"
  "15.09":"Aslak Eskil"
  "16.09":"Lillian Lilly"
  "17.09":"Hildebjørg Hildegunn"
  "18.09":"Henriette Henry"
  "19.09":"Konstanse Connie"
  "20.09":"Tobias Tage"
  "21.09":"Trine Trond"
  "22.09":"Kyrre Kåre"
  "23.09":"Snorre Snefrid"
  "24.09":"Jan Jens"
  "25.09":"Ingvar Yngvar"
  "26.09":"Einar Endre"
  "27.09":"Dagmar Dagrun"
  "28.09":"Lena Lene"
  "29.09":"Mikael Mikal Mikkel"
  "30.09":"Helga Helge Hege"
  "01.10":"Rebekka Remi"
  "02.10":"Live Liv"
  "03.10":"Evald Evelyn"
  "04.10":"Frans Frank"
  "05.10":"Brynjar Boye Bo"
  "06.10":"Målfrid Møyfrid"
  "07.10":"Birgitte Birgit Berit"
  "08.10":"Benedikte Bente"
  "09.10":"Leidulf Leif"
  "10.10":"Fridtjof Frida Frits"
  "11.10":"Kevin Kennet Kent"
  "12.10":"Valter Vibeke"
  "13.10":"Torgeir Terje Tarjei"
  "14.10":"Kaia Kai"
  "15.10":"Hedvig Hedda"
  "16.10":"Flemming Finn"
  "17.10":"Marta Marte"
  "18.10":"Kjersti Kjerstin"
  "19.10":"Tora Tore"
  "20.10":"Henrik Heine Henrikke"
  "21.10":"Bergljot Birger"
  "22.10":"Karianne Karine Kine"
  "23.10":"Severin Søren"
  "24.10":"Eilif Eivor"
  "25.10":"Margrete Merete Märtha"
  "26.10":"Amandus Amanda"
  "27.10":"Sturla Sture"
  "28.10":"Simon Simen"
  "29.10":"Noralf Norunn"
  "30.10":"Aksel Åmund Ove"
  "31.10":"Edit Edna"
  "01.11":"Veslemøy Vetle"
  "02.11":"Tove Tuva"
  "03.11":"Raymond Roy"
  "04.11":"Otto Ottar"
  "05.11":"Egil Egon"
  "06.11":"Leonard Lennart"
  "07.11":"Ingebrigt Ingelin"
  "08.11":"Ingvild Yngvild"
  "09.11":"Tordis Teodor"
  "10.11":"Gudbjørg Gudveig"
  "11.11":"Martin Morten Martine"
  "12.11":"Torkjell Torkil"
  "13.11":"Kirsten Kirsti"
  "14.11":"Fredrik Fred Freddy"
  "15.11":"Oddfrid Oddvar"
  "16.11":"Edmund Edgar"
  "17.11":"Hugo Hogne Hauk"
  "18.11":"Magne Magny"
  "19.11":"Elisabet Lisbet"
  "20.11":"Halvdan Helle"
  "21.11":"Mariann Marianne"
  "22.11":"Cecilie Silje Sissel"
  "23.11":"Klement Klaus"
  "24.11":"Gudrun Guro"
  "25.11":"Katarina Katrine Kari"
  "26.11":"Konrad Kurt"
  "27.11":"Torlaug Torleif"
  "28.11":"Ruben Rut"
  "29.11":"Sofie Sonja"
  "30.11":"Andreas Anders"
  "01.12":"Arnold Arnljot Arnt"
  "02.12":"Borghild Borgny Bård"
  "03.12":"Sveinung Svein"
  "04.12":"Barbara Barbro"
  "05.12":"Stine Ståle"
  "06.12":"Nils Nikolai"
  "07.12":"Hallfrid Hallstein"
  "08.12":"Marlene Marion Morgan"
  "09.12":"Anniken Annette"
  "10.12":"Judit og Jytte"
  "11.12":"Daniel Dan"
  "12.12":"Pia Peggy"
  "13.12":"Lucia Lydia"
  "14.12":"Steinar Stein"
  "15.12":"Hilda Hilde"
  "16.12":"Oddbjørg Oddbjørn"
  "17.12":"Inga Inge"
  "18.12":"Kristoffer Kate"
  "19.12":"Iselin Isak"
  "20.12":"Abraham Amund"
  "21.12":"Tomas Tom Tommy"
  "22.12":"Ingemar Ingar"
  "23.12":"Sigurd Sjur"
  "24.12":"Adam Eva"
  "25.12":"Ingen navnedag"
  "26.12":"Stefan Steffen"
  "27.12":"Narve Natalie"
  "28.12":"Unni Une Unn"
  "29.12":"Vidar Vemund"
  "30.12":"David Diana Dina"
  "31.12":"Sylfest Sylvia Sylvi"

PRIVMSG:
  "^%pnavnedag$": (source, destination) =>
    now = os.date'%d.%m'
    names = table.concat(util.split(data[now], ' '), ', ')
    say names
  "^%pnavnedag (%d+).(%d+)$": (source, destination, day, month) =>
    day = string.format('%02d', day)
    month = string.format('%02d', month)
    now = day .. '.' .. month
    names = table.concat(util.split(data[now], ' '), ', ')
    say names
  "^%pnavnedag (.*)$": (source, destination, input) =>
    for key, val in pairs(data)
      names = util.split(val, ' ')
      for name in *names
        if util.utf8.lower(name) == util.utf8.lower(input)
          say "#{key}: #{table.concat(names, ', ')}"


Fix incorrect event handler definition in comm

-- Copyright (c) 2014 by Adam Hellberg <sharparam@sharparam.com>.
--
-- 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.

NAME, T = ...

DELIMITER = '\t'
DELIMITER_MATCH = '[^' .. DELIMITER .. ']+'

MAX_MESSAGE_LENGTH = 250

local comm, log, misc

{
    :SendAddonMessage
    :wipe
    :UnitName
    :RegisterAddonMessagePrefix
} = _G

{localization: L} = T

T.comm_manager = do
    class CommManager extends T.CallbackManager
        new: =>
            super!
            @prefix = NAME\upper!

        handle: (message, channel, sender) =>
            return unless message
            message = message\trim!
            return if message == ''
            di = message\find DELIMITER
            kind = di and message\sub 1, di - 1 or message
            kind = kind\lower!

            return unless @handlers[kind]

            args = {}
            if di
                arg = message\sub di + 1
                for token in arg\gmatch DELIMITER_MATCH
                    args[#args + 1] = token

            @fire kind, channel, sender, unpack args

        send: (channel, target, kind, ...) =>
            if channel != 'WHISPER' and channel != 'CHANNEL' and target
                return @send channel, nil, target, kind, ...

            message = kind
            for v in *{...}
                message ..= DELIMITER .. v

            if message\len! > MAX_MESSAGE_LENGTH
                log\warn L.comm_message_length, kind, MAX_MESSAGE_LENGTH

            SendAddonMessage @prefix, message, channel, target

        register_callback: (kind, handler) =>
            kind = kind\lower!
            super kind, handler

        unregister_callback: (kind, handler) =>
            kind = kind\lower!
            super kind, handler

        unregister_callbacks: (kind) =>
            kind = kind\lower!
            super kind

        unregister_all_callbacks: (kind) =>
            kind = kind\lower!
            super kind

    CommManager!

{comm_manager: comm, :log, :misc} = T

T.CHAT_MSG_ADDON = (event, prefix, message, channel, sender) ->
    return if prefix != comm.prefix or channel != 'PARTY'
    fixed_sender = misc.fix_name sender
    player = misc.fix_name UnitName 'player'
    return if fixed_sender == player
    comm\handle message, channel, sender

T.SHAREXP_LOADED = ->
    RegisterAddonMessagePrefix comm.prefix
    log\debug L.comm_prefix_registered, comm.prefix

-- Copyright (c) 2014 by Adam Hellberg <sharparam@sharparam.com>.
--
-- 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.

NAME, T = ...

DELIMITER = '\t'
DELIMITER_MATCH = '[^' .. DELIMITER .. ']+'

MAX_MESSAGE_LENGTH = 250

local comm, log, misc

{
    :SendAddonMessage
    :wipe
    :UnitName
    :RegisterAddonMessagePrefix
} = _G

{localization: L} = T

T.comm_manager = do
    class CommManager extends T.CallbackManager
        new: =>
            super!
            @prefix = NAME\upper!

        handle: (message, channel, sender) =>
            return unless message
            message = message\trim!
            return if message == ''
            di = message\find DELIMITER
            kind = di and message\sub 1, di - 1 or message
            kind = kind\lower!

            return unless @handlers[kind]

            args = {}
            if di
                arg = message\sub di + 1
                for token in arg\gmatch DELIMITER_MATCH
                    args[#args + 1] = token

            @fire kind, channel, sender, unpack args

        send: (channel, target, kind, ...) =>
            if channel != 'WHISPER' and channel != 'CHANNEL' and target
                return @send channel, nil, target, kind, ...

            message = kind
            for v in *{...}
                message ..= DELIMITER .. v

            if message\len! > MAX_MESSAGE_LENGTH
                log\warn L.comm_message_length, kind, MAX_MESSAGE_LENGTH

            SendAddonMessage @prefix, message, channel, target

        register_callback: (kind, handler) =>
            kind = kind\lower!
            super kind, handler

        unregister_callback: (kind, handler) =>
            kind = kind\lower!
            super kind, handler

        unregister_callbacks: (kind) =>
            kind = kind\lower!
            super kind

        unregister_all_callbacks: (kind) =>
            kind = kind\lower!
            super kind

    CommManager!

{comm_manager: comm, :log, :misc} = T

T.CHAT_MSG_ADDON = (prefix, message, channel, sender) ->
    return if prefix != comm.prefix or channel != 'PARTY'
    fixed_sender = misc.fix_name sender
    player = misc.fix_name UnitName 'player'
    return if fixed_sender == player
    comm\handle message, channel, sender

T.SHAREXP_LOADED = ->
    RegisterAddonMessagePrefix comm.prefix
    log\debug L.comm_prefix_registered, comm.prefix


implemented lazuli.Application\enable

implemented lazuli.Application\enable

lapis = require "lapis"

import after_dispatch from require "lapis.nginx.context"
import to_json from require "lapis.util"
migrations=require "lapis.db.migrations"
config = (require "lapis.config").get!

modules={}

class extends lapis.Application
  @before_filter =>
    if config.measure_performance
      after_dispatch ->
        print to_json ngx.ctx.performance
    @modules or=modules
    @session.modules or={}
  "/_lazuli/console": if config.enable_console
    (require "lapis.console").make!
  else
    => "console disabled in config"
  "/_lazuli/migrate": if config.enable_web_migration
    =>
      migrations.create_migrations_table!
      migrations.run_migrations require "migrations"
  else
    => "web migration disabled in config"
  new: (...)=>
    @modules=modules
    super ...
  enable: (feature,forcelapis=false)=>
    if not forcelapis
      have,fn=pcall require, "lazuli.modules."..feature..".enabler"
      if have and type(fn)=="function"
        return fn @
    super feature


reduce fat and glue

reduce fat and glue

--
-- 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 PPM2 from _G

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_STARE = CreateConVar('ppm2_render_stare', '1', {FCVAR_ARCHIVE}, 'Make eyes follow players and move when idling')

class PonyRenderController extends PPM2.ControllerChildren
	@AVALIABLE_CONTROLLERS = {}
	@MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

	CompileTextures: => @GetTextureController()\CompileTextures() if @GetTextureController and @GetTextureController()
	new: (controller) =>
		super(controller)
		@hideModels = false
		@modelCached = controller\GetModel()
		@IGNORE_DRAW = false
		@CompileTextures() if @GetEntity()\IsValid()
		@CreateLegs() if @GetEntity() == LocalPlayer()
		@socksModel = controller\GetSocksModel()
		@socksModel\SetNoDraw(false) if IsValid(@socksModel)
		@newSocksModel = controller\GetNewSocksModel()
		@newSocksModel\SetNoDraw(false) if IsValid(@newSocksModel)
		@lastStareUpdate = 0
		@staringAt = NULL
		@rotatedHeadTarget = false
		@idleEyes = true
		@idleEyesActive = false
		@nextRollEyes = 0
		if @GetEntity()\IsValid()
			@CreateFlexController()
			@CreateEmotesController()

	GetModel: => @controller\GetModel()

	GetLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		@CreateLegs() if not IsValid()
		return @legsModel

	CreateLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		for _, ent in ipairs ents.GetAll()
			if ent.isPonyLegsModel
				ent\Remove()

		with @legsModel = ClientsideModel(@modelCached)
			.isPonyLegsModel = true
			.lastRedrawFix = 0
			\SetNoDraw(true)
			.__PPM2_PonyData = @GetData()
			--\PPMBonesModifier()

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

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

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

	UpdateLegs: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return unless IsValid(@legsModel)
		return if @legUpdateFrame == FrameNumberL()
		@legUpdateFrame = FrameNumberL()
		ctime = CurTimeL()
		ply = @GetEntity()
		seq = ply\GetSequence()
		legsModel = @legsModel

		with @legsModel
			PPM2.EntityBonesModifier.ThinkObject(ply.__ppmBonesModifiers) if ply.__ppmBonesModifiers

			for boneid = 0, ply\GetBoneCount() - 1
				\ManipulateBonePosition(0, ply\GetManipulateBonePosition(0))
				\ManipulateBoneAngles(0, ply\GetManipulateBoneAngles(0))
				\ManipulateBoneScale(0, ply\GetManipulateBoneScale(0))

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

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

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

		if ply\InVehicle()
			local bonePos

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

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

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

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

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

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

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

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

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

		@legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

	@LEG_CLIP_VECTOR = Vector(0, 0, -1)
	@LEGS_MAX_DISTANCE = 60 ^ 2
	DrawLegs: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE

		if USE_RENDER_OVERRIDE\GetBool()
			@legsModel\SetNoDraw(false)
			rTime = RealTimeL()
			if @legsModel.lastRedrawFix < rTime
				@legsModel\DrawModel()
				@legsModel.lastRedrawFix = rTime + 5
			if not @legsModel.RenderOverride
				@legsModel.RenderOverride = -> @DrawLegsOverride()
				@legsModel\DrawModel()
			return
		else
			@legsModel\SetNoDraw(true)

		@UpdateLegs()

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

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

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

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

	DrawLegsOverride: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if EyeAngles().p < 60
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

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

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

		render.PopCustomClipPlane()
		render.EnableClipping(oldClip)

	DrawLegsDepth: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

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

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

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

	IsValid: => IsValid(@GetEntity()) and @isValid

	Reset: =>
		@flexes\Reset() if @flexes and @flexes.Reset
		@emotes\Reset() if @emotes and @emotes.Reset
		@GetTextureController()\Reset() if @GetTextureController and @GetTextureController() and @GetTextureController().Reset
		@GetTextureController()\ResetTextures() if @GetTextureController and @GetTextureController()

	Remove: =>
		@flexes\Remove() if @flexes
		@emotes\Remove() if @emotes
		@GetTextureController()\Remove() if @GetTextureController and @GetTextureController()
		@isValid = false

	PlayerDeath: =>
		return if not @isValid
		if @emotes
			@emotes\Remove()
			@emotes = nil
		@HideModels(true)
		@GetTextureController()\ResetTextures() if @GetTextureController() and @GetEntity()\IsPony()

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

	DrawModels: =>
		@socksModel\DrawModel() if IsValid(@socksModel)
		@newSocksModel\DrawModel() if IsValid(@newSocksModel)

	ShouldHideModels: => @hideModels or @GetEntity()\GetNoDraw()

	DoHideModels: (status) =>
		@socksModel\SetNoDraw(status) if IsValid(@socksModel)
		@newSocksModel\SetNoDraw(status) if IsValid(@newSocksModel)

	HideModels: (status = true) =>
		return if @hideModels == status
		@DoHideModels(status)
		@hideModels = status

	CheckTarget: (epos, pos) =>
		return not util.TraceLine({
			start: epos,
			endpos: pos,
			filter: @GetEntity(),
			mask: MASK_BLOCKLOS
		}).Hit

	UpdateStare: =>
		ctime = RealTimeL()
		return if @lastStareUpdate > ctime

		if (not @idleEyes or not ENABLE_STARE\GetBool()) and @idleEyesActive
			@staringAt = NULL
			@GetEntity()\SetEyeTarget(Vector())
			@idleEyesActive = false
			return

		return if not @idleEyes or not ENABLE_STARE\GetBool()
		@idleEyesActive = true
		@lastStareUpdate = ctime + 0.2
		lpos = @GetEntity()\EyePos()

		if IsValid(@staringAt)
			trNew = util.TraceLine({
				start: lpos,
				endpos: lpos + @GetEntity()\EyeAnglesFixed()\Forward() * 270,
				filter: @GetEntity(),
			})

			if trNew.Entity\IsValid() and trNew.Entity\IsPlayer()
				@staringAt = trNew.Entity

			epos = @staringAt\EyePos()
			if epos\Distance(lpos) < 300 and DLib.combat.inPVS(@GetEntity(), @staringAt) and @CheckTarget(lpos, epos)
				@GetEntity()\SetEyeTarget(epos)
				return
			@staringAt = NULL
			@GetEntity()\SetEyeTarget(Vector())

		if player.GetCount() ~= 1
			local last
			max = 300
			local lastpos
			for _, ply in ipairs player.GetAll()
				if @GetEntity() ~= ply
					epos = ply\EyePos()
					dist = epos\Distance(lpos)
					if dist < max and DLib.combat.inPVS(@GetEntity(), ply) and @CheckTarget(lpos, epos)
						max = dist
						last = ply
						lastpos = epos
			if last
				@GetEntity()\SetEyeTarget(lastpos)
				@staringAt = last
				return

		return if @nextRollEyes > ctime
		@nextRollEyes = ctime + math.random(4, 16) / 6
		ang = @GetEntity()\EyeAnglesFixed()
		@eyeRollTargetPos = Vector(math.random(200, 400), math.random(-80, 80), math.random(-20, 20))
		@prevRollTargetPos = @prevRollTargetPos or @eyeRollTargetPos
		-- @GetEntity()\SetEyeTarget(@prevRollTargetPos)

	UpdateEyeRoll: =>
		return if not ENABLE_STARE\GetBool() or not @idleEyes or not @eyeRollTargetPos or IsValid(@staringAt)
		@prevRollTargetPos = LerpVector(FrameTime() * 6, @prevRollTargetPos, @eyeRollTargetPos)
		roll = Vector(@prevRollTargetPos)
		roll\Rotate(@GetEntity()\EyeAnglesFixed())
		@GetEntity()\SetEyeTarget(@GetEntity()\EyePos() + roll)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid

		with @GetTextureController()
			\PreDraw(ent, drawingNewTask)

		if drawingNewTask
			with bones = ent\PPMBonesModifier()
				ent\ResetBoneManipCache()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, ent, @controller)
				\Think(true)
				ent.__ppmBonesModified = true
				ent\ApplyBoneManipulations()

		@flexes\Think(ent) if @flexes
		@emotes\Think(ent) if @emotes
		if @GetEntity()\IsPlayer()
			@UpdateStare()
			@UpdateEyeRoll()

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes') and @GrabData('HideManesSocks')
			@socksModel\SetNoDraw(true) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(true) if IsValid(@newSocksModel)
		else
			@socksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@newSocksModel)

	PostDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid
		@GetTextureController()\PostDraw(ent, drawingNewTask)

	@ARMS_MATERIAL_INDEX = 0
	PreDrawArms: (ent) =>
		return if not @isValid
		if ent and not @armsWeightSetup
			@armsWeightSetup = true
			weight = 1 + (@GetData()\GetWeight() - 1) * 0.4
			vec = LVector(weight, weight, weight)
			ent\ManipulateBoneScale2Safe(i, vec) for i = 1, 13
		ent\SetSubMaterial(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBodyName())
	PostDrawArms: (ent) =>
		return if not @isValid
		ent\SetSubMaterial(@@ARMS_MATERIAL_INDEX, '')

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

	CreateFlexController: =>
		return @flexes if not @isValid
		return if @GetData()\GetNoFlex()
		if not @flexes
			cls = PPM2.GetFlexController(@modelCached)
			return if not cls
			@flexes = cls(@)
		@flexes.ent = @GetEntity()
		return @flexes

	CreateEmotesController: =>
		return @emotes if not @isValid
		if not @emotes or not @emotes\IsValid()
			cls = PPM2.GetPonyExpressionsController(@modelCached)
			return if not cls
			@emotes = cls(@)
		@emotes.ent = @GetEntity()
		return @emotes

	GetFlexController: => @flexes
	GetEmotesController: => @emotes

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

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

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

	DrawModels: =>
		@upperManeModel\DrawModel() if IsValid(@upperManeModel)
		@lowerManeModel\DrawModel() if IsValid(@lowerManeModel)
		@tailModel\DrawModel() if IsValid(@tailModel)
		super()

	DoHideModels: (status) =>
		super(status)
		@upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(status) if IsValid(@tailModel)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		super(ent, drawingNewTask)

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes')
			@upperManeModel\SetNoDraw(true) if IsValid(@upperManeModel) and @GrabData('HideManesMane')
			@lowerManeModel\SetNoDraw(true) if IsValid(@lowerManeModel) and @GrabData('HideManesMane')
			@tailModel\SetNoDraw(true) if IsValid(@tailModel) and @GrabData('HideManesTail')
		else
			@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
			@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
			@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)

hook.Add 'NotifyShouldTransmit', 'PPM2.RenderController', (should) =>
	if data = @GetPonyData()
		if renderer = data\GetRenderController()
			renderer\HideModels(not should)

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

--
-- Copyright (C) 2017-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 PPM2 from _G

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_STARE = CreateConVar('ppm2_render_stare', '1', {FCVAR_ARCHIVE}, 'Make eyes follow players and move when idling')

class PonyRenderController extends PPM2.ControllerChildren
	@AVALIABLE_CONTROLLERS = {}
	@MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

	CompileTextures: => @GetTextureController()\CompileTextures() if @GetTextureController and @GetTextureController()
	new: (controller) =>
		super(controller)
		@hideModels = false
		@modelCached = controller\GetModel()
		@IGNORE_DRAW = false
		@CompileTextures() if @GetEntity()\IsValid()
		@CreateLegs() if @GetEntity() == LocalPlayer()
		@socksModel = controller\GetSocksModel()
		@socksModel\SetNoDraw(false) if IsValid(@socksModel)
		@newSocksModel = controller\GetNewSocksModel()
		@newSocksModel\SetNoDraw(false) if IsValid(@newSocksModel)
		@lastStareUpdate = 0
		@staringAt = NULL
		@rotatedHeadTarget = false
		@idleEyes = true
		@idleEyesActive = false
		@nextRollEyes = 0
		if @GetEntity()\IsValid()
			@CreateFlexController()
			@CreateEmotesController()

	GetModel: => @controller\GetModel()

	GetLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		@CreateLegs() if not IsValid()
		return @legsModel

	CreateLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		for _, ent in ipairs ents.GetAll()
			if ent.isPonyLegsModel
				ent\Remove()

		with @legsModel = ClientsideModel(@modelCached)
			.isPonyLegsModel = true
			.lastRedrawFix = 0
			\SetNoDraw(true)
			.__PPM2_PonyData = @GetData()
			--\PPMBonesModifier()

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

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

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

	UpdateLegs: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return unless IsValid(@legsModel)
		return if @legUpdateFrame == FrameNumberL()
		@legUpdateFrame = FrameNumberL()
		ctime = CurTimeL()
		ply = @GetEntity()
		seq = ply\GetSequence()
		legsModel = @legsModel

		with @legsModel
			PPM2.EntityBonesModifier.ThinkObject(ply.__ppmBonesModifiers) if ply.__ppmBonesModifiers

			for boneid = 0, ply\GetBoneCount() - 1
				\ManipulateBonePosition(0, ply\GetManipulateBonePosition(0))
				\ManipulateBoneAngles(0, ply\GetManipulateBoneAngles(0))
				\ManipulateBoneScale(0, ply\GetManipulateBoneScale(0))

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

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

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

		if ply\InVehicle()
			local bonePos

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

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

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

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

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

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

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

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

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

		@legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

	@LEG_CLIP_VECTOR = Vector(0, 0, -1)
	@LEGS_MAX_DISTANCE = 60 ^ 2
	DrawLegs: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE

		if USE_RENDER_OVERRIDE\GetBool()
			@legsModel\SetNoDraw(false)
			rTime = RealTimeL()
			if @legsModel.lastRedrawFix < rTime
				@legsModel\DrawModel()
				@legsModel.lastRedrawFix = rTime + 5
			if not @legsModel.RenderOverride
				@legsModel.RenderOverride = -> @DrawLegsOverride()
				@legsModel\DrawModel()
			return
		else
			@legsModel\SetNoDraw(true)

		@UpdateLegs()

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

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

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

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

	DrawLegsOverride: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if EyeAngles().p < 60
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

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

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

		render.PopCustomClipPlane()
		render.EnableClipping(oldClip)

	DrawLegsDepth: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

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

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

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

	IsValid: => IsValid(@GetEntity()) and @isValid

	Reset: =>
		@flexes\Reset() if @flexes and @flexes.Reset
		@emotes\Reset() if @emotes and @emotes.Reset
		@GetTextureController()\Reset() if @GetTextureController and @GetTextureController() and @GetTextureController().Reset
		@GetTextureController()\ResetTextures() if @GetTextureController and @GetTextureController()

	Remove: =>
		@flexes\Remove() if @flexes
		@emotes\Remove() if @emotes
		@GetTextureController()\Remove() if @GetTextureController and @GetTextureController()
		@isValid = false

	PlayerDeath: =>
		return if not @isValid
		if @emotes
			@emotes\Remove()
			@emotes = nil
		@HideModels(true)
		@GetTextureController()\ResetTextures() if @GetTextureController() and @GetEntity()\IsPony()

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

	DrawModels: =>
		@socksModel\DrawModel() if IsValid(@socksModel)
		@newSocksModel\DrawModel() if IsValid(@newSocksModel)

	ShouldHideModels: => @hideModels or @GetEntity()\GetNoDraw()

	DoHideModels: (status) =>
		@socksModel\SetNoDraw(status) if IsValid(@socksModel)
		@newSocksModel\SetNoDraw(status) if IsValid(@newSocksModel)

	HideModels: (status = true) =>
		return if @hideModels == status
		@DoHideModels(status)
		@hideModels = status

	CheckTarget: (epos, pos) =>
		return not util.TraceLine({
			start: epos,
			endpos: pos,
			filter: @GetEntity(),
			mask: MASK_BLOCKLOS
		}).Hit

	UpdateStare: =>
		ctime = RealTimeL()
		return if @lastStareUpdate > ctime

		if (not @idleEyes or not ENABLE_STARE\GetBool()) and @idleEyesActive
			@staringAt = NULL
			@GetEntity()\SetEyeTarget(Vector())
			@idleEyesActive = false
			return

		return if not @idleEyes or not ENABLE_STARE\GetBool()
		@idleEyesActive = true
		@lastStareUpdate = ctime + 0.2
		lpos = @GetEntity()\EyePos()

		if IsValid(@staringAt)
			trNew = util.TraceLine({
				start: lpos,
				endpos: lpos + @GetEntity()\EyeAnglesFixed()\Forward() * 270,
				filter: @GetEntity(),
			})

			if trNew.Entity\IsValid() and trNew.Entity\IsPlayer()
				@staringAt = trNew.Entity

			epos = @staringAt\EyePos()
			if epos\Distance(lpos) < 300 and DLib.combat.inPVS(@GetEntity(), @staringAt) and @CheckTarget(lpos, epos)
				@GetEntity()\SetEyeTarget(epos)
				return
			@staringAt = NULL
			@GetEntity()\SetEyeTarget(Vector())

		if player.GetCount() ~= 1
			local last
			max = 300
			local lastpos
			for _, ply in ipairs player.GetAll()
				if @GetEntity() ~= ply
					epos = ply\EyePos()
					dist = epos\Distance(lpos)
					if dist < max and DLib.combat.inPVS(@GetEntity(), ply) and @CheckTarget(lpos, epos)
						max = dist
						last = ply
						lastpos = epos
			if last
				@GetEntity()\SetEyeTarget(lastpos)
				@staringAt = last
				return

		return if @nextRollEyes > ctime
		@nextRollEyes = ctime + math.random(4, 16) / 6
		ang = @GetEntity()\EyeAnglesFixed()
		@eyeRollTargetPos = Vector(math.random(200, 400), math.random(-80, 80), math.random(-20, 20))
		@prevRollTargetPos = @prevRollTargetPos or @eyeRollTargetPos
		-- @GetEntity()\SetEyeTarget(@prevRollTargetPos)

	UpdateEyeRoll: =>
		return if not ENABLE_STARE\GetBool() or not @idleEyes or not @eyeRollTargetPos or IsValid(@staringAt)
		@prevRollTargetPos = LerpVector(FrameTime() * 6, @prevRollTargetPos, @eyeRollTargetPos)
		roll = Vector(@prevRollTargetPos)
		roll\Rotate(@GetEntity()\EyeAnglesFixed())
		@GetEntity()\SetEyeTarget(@GetEntity()\EyePos() + roll)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid

		with @GetTextureController()
			\PreDraw(ent, drawingNewTask)

		if drawingNewTask
			with bones = ent\PPMBonesModifier()
				ent\ResetBoneManipCache()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, ent, @controller)
				\Think(true)
				ent.__ppmBonesModified = true
				ent\ApplyBoneManipulations()

		@flexes\Think(ent) if @flexes
		@emotes\Think(ent) if @emotes
		if @GetEntity()\IsPlayer()
			@UpdateStare()
			@UpdateEyeRoll()

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes') and @GrabData('HideManesSocks')
			@socksModel\SetNoDraw(true) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(true) if IsValid(@newSocksModel)
		else
			@socksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@newSocksModel)

	PostDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid
		@GetTextureController()\PostDraw(ent, drawingNewTask)

	@ARMS_MATERIAL_INDEX = 0
	PreDrawArms: (ent) =>
		return if not @isValid
		if ent and not @armsWeightSetup
			@armsWeightSetup = true
			weight = @GetData()\GetWeight()
			vec = LVector(weight, weight, weight)
			ent\ManipulateBoneScale2Safe(i, vec) for i = 1, 13
		ent\SetSubMaterial(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBodyName())
	PostDrawArms: (ent) =>
		return if not @isValid
		ent\SetSubMaterial(@@ARMS_MATERIAL_INDEX, '')

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

	CreateFlexController: =>
		return @flexes if not @isValid
		return if @GetData()\GetNoFlex()
		if not @flexes
			cls = PPM2.GetFlexController(@modelCached)
			return if not cls
			@flexes = cls(@)
		@flexes.ent = @GetEntity()
		return @flexes

	CreateEmotesController: =>
		return @emotes if not @isValid
		if not @emotes or not @emotes\IsValid()
			cls = PPM2.GetPonyExpressionsController(@modelCached)
			return if not cls
			@emotes = cls(@)
		@emotes.ent = @GetEntity()
		return @emotes

	GetFlexController: => @flexes
	GetEmotesController: => @emotes

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

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

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

	DrawModels: =>
		@upperManeModel\DrawModel() if IsValid(@upperManeModel)
		@lowerManeModel\DrawModel() if IsValid(@lowerManeModel)
		@tailModel\DrawModel() if IsValid(@tailModel)
		super()

	DoHideModels: (status) =>
		super(status)
		@upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(status) if IsValid(@tailModel)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		super(ent, drawingNewTask)

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes')
			@upperManeModel\SetNoDraw(true) if IsValid(@upperManeModel) and @GrabData('HideManesMane')
			@lowerManeModel\SetNoDraw(true) if IsValid(@lowerManeModel) and @GrabData('HideManesMane')
			@tailModel\SetNoDraw(true) if IsValid(@tailModel) and @GrabData('HideManesTail')
		else
			@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
			@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
			@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)

hook.Add 'NotifyShouldTransmit', 'PPM2.RenderController', (should) =>
	if data = @GetPonyData()
		if renderer = data\GetRenderController()
			renderer\HideModels(not should)

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


default configuration: player vs AI

default configuration: player vs AI

require("lib/lgm/lgm")
require("wall")
require("player")
require("ai_player")

export wScr, hScr
wScr = love.window.getWidth
hScr = love.window.getHeight

kPAUSE = false

love.load = ->

    arenaOutSize = 40

    colorP1 = {227,138,23}
    colorP2 = {91,205,229}

    useSound = true

    bgm_main = love.audio.newSource("res/bgm_main.ogg", "stream")
    bgm_main\setVolume(0.5)
    sfx_turn = {}
    for i=1,10
        s = love.audio.newSource("res/sfx_turn.wav", "static")
        s\setVolume(0.5)
        s\setPitch(0.5 + 0.1 * i)
        table.insert(sfx_turn, s)

    wallSet\add(Wall arenaOutSize, arenaOutSize, wScr() - arenaOutSize,
        arenaOutSize,
        {255,255,255},
        -1)
    wallSet\add(Wall arenaOutSize,
        arenaOutSize,
        arenaOutSize,
        hScr() - arenaOutSize,
        {255,255,255},
        -1)
    wallSet\add(Wall wScr() - arenaOutSize,
        arenaOutSize,
        wScr() - arenaOutSize,
        hScr() - arenaOutSize,
        {255,255,255},
        -1)
    wallSet\add(Wall arenaOutSize,
        hScr() - arenaOutSize,
        wScr() - arenaOutSize,
        hScr() - arenaOutSize,
        {255,255,255},
        -1)

    playerSet\add(AI_Player wScr() - (arenaOutSize + 10),
        hScr() - (arenaOutSize + 10),
        -1, 0,colorP1)
    playerSet\add(Player arenaOutSize + 10, arenaOutSize + 10, 1, 0, colorP2)

    bgm_main\play()

    love.update = (dt) ->
        if not kPAUSE
            for wall in wallSet\iter()
                wall\update(dt)
                if (wall.time > wall.lifeTime) then
                    wallSet\removeID(wall.id)
            for player in playerSet\iter()
                player\update(dt)

    love.draw = ->
        for player in playerSet\iter()
            player\draw()
            love.graphics.setColor(player.color)
            love.graphics.print(player.score, 50 + 30 * player.id , 10)

        for wall in wallSet\iter()
            wall\draw()

        -- music icon ♪
        if useSound
            love.graphics.setColor(255,255,255)
            love.graphics.print("m to mute", 1, 1)

    love.keypressed = (k) ->
        switch k
            when "up", "right","left", "down"
                playerSet\find(1)\keypressed(k)
            when "w", "z"
                playerSet\find(2)\keypressed("up")
            when "d"
                playerSet\find(2)\keypressed("right")
            when "s"
                playerSet\find(2)\keypressed("down")
            when "q", "a"
                playerSet\find(2)\keypressed("left")

            when "p"
                kPAUSE = not kPAUSE

            when "m"
                useSound = not useSound
                curV = love.audio.getVolume()
                love.audio.setVolume(1.0 - curV)

            when "escape"
                love.event.push("quit")

    export playSfxTurn = ->
        i = -1
        while i == -1 or not sfx_turn[i]\isStopped()
            i = love.math.random(1, #sfx_turn)
        sfx_turn[i]\play()

require("lib/lgm/lgm")
require("wall")
require("player")
require("ai_player")

export wScr, hScr
wScr = love.window.getWidth
hScr = love.window.getHeight

kPAUSE = false

love.load = ->

    arenaOutSize = 40

    colorP1 = {227,138,23}
    colorP2 = {91,205,229}

    useSound = true

    bgm_main = love.audio.newSource("res/bgm_main.ogg", "stream")
    bgm_main\setVolume(0.5)
    sfx_turn = {}
    for i=1,10
        s = love.audio.newSource("res/sfx_turn.wav", "static")
        s\setVolume(0.5)
        s\setPitch(0.5 + 0.1 * i)
        table.insert(sfx_turn, s)

    wallSet\add(Wall arenaOutSize, arenaOutSize, wScr() - arenaOutSize,
        arenaOutSize,
        {255,255,255},
        -1)
    wallSet\add(Wall arenaOutSize,
        arenaOutSize,
        arenaOutSize,
        hScr() - arenaOutSize,
        {255,255,255},
        -1)
    wallSet\add(Wall wScr() - arenaOutSize,
        arenaOutSize,
        wScr() - arenaOutSize,
        hScr() - arenaOutSize,
        {255,255,255},
        -1)
    wallSet\add(Wall arenaOutSize,
        hScr() - arenaOutSize,
        wScr() - arenaOutSize,
        hScr() - arenaOutSize,
        {255,255,255},
        -1)

    playerSet\add(Player wScr() - (arenaOutSize + 10),
        hScr() - (arenaOutSize + 10),
        -1, 0,colorP1)
    playerSet\add(Player arenaOutSize + 10, arenaOutSize + 10, 1, 0, colorP2)

    playerSet\add(AI_Player wScr() / 2, hScr() / 2, 0, 1, {255, 105, 180})

    bgm_main\play()

    love.update = (dt) ->
        if not kPAUSE
            for wall in wallSet\iter()
                wall\update(dt)
                if (wall.time > wall.lifeTime) then
                    wallSet\removeID(wall.id)
            for player in playerSet\iter()
                player\update(dt)

    love.draw = ->
        for player in playerSet\iter()
            player\draw()
            love.graphics.setColor(player.color)
            love.graphics.print(player.score, 50 + 30 * player.id , 10)

        for wall in wallSet\iter()
            wall\draw()

        -- music icon ♪
        if useSound
            love.graphics.setColor(255,255,255)
            love.graphics.print("m to mute", 1, 1)

    love.keypressed = (k) ->
        switch k
            when "up", "right","left", "down"
                playerSet\find(1)\keypressed(k)
            when "w", "z"
                playerSet\find(2)\keypressed("up")
            when "d"
                playerSet\find(2)\keypressed("right")
            when "s"
                playerSet\find(2)\keypressed("down")
            when "q", "a"
                playerSet\find(2)\keypressed("left")

            when "p"
                kPAUSE = not kPAUSE

            when "m"
                useSound = not useSound
                curV = love.audio.getVolume()
                love.audio.setVolume(1.0 - curV)

            when "escape"
                love.event.push("quit")

    export playSfxTurn = ->
        i = -1
        while i == -1 or not sfx_turn[i]\isStopped()
            i = love.math.random(1, #sfx_turn)
        sfx_turn[i]\play()


user management

user management

class FaviconMixin
  userManagementMixinMenu: ()=>
    ul class: "lazuli module user_management mixin_menu "..(@lazuli_modules_usermanagement_currentuser and "logged_in" or "logged_out"), ->
      if @lazuli_modules_usermanagement_currentuser
        li class: "logout", ->
          a href:@url_for("lazuli_modules_usermanagement_logout"), "Logout"
      else
        li class: "login", ->
          a href:@url_for("lazuli_modules_usermanagement_login"), "Login"
        li class: "register", ->
          a href:@url_for("lazuli_modules_usermanagement_register"), "Register"

class FaviconMixin
  userManagementMixinMenu: ()->
    ul class: "lazuli module user_management mixin_menu "..(@lazuli_modules_usermanagement_currentuser and "logged_in" or "logged_out"), ->
      if @lazuli_modules_usermanagement_currentuser
        li class: "logout", ->
          a href:@url_for("lazuli_modules_usermanagement_logout"), "Logout"
      else
        li class: "login", ->
          a href:@url_for("lazuli_modules_usermanagement_login"), "Login"
        li class: "register", ->
          a href:@url_for("lazuli_modules_usermanagement_register"), "Register"


improve hammerspoon profile actions

improve hammerspoon profile actions

require 'moonscript'

import unpack from table

class Profile
  new: (title, screens, config) =>
    @title = title
    @screens = screens
    @config = config

  _actionsFor: (appName) =>
    actions = @config[appName]
    if actions then actions else @config["_"]

  _activateForApp: (app) =>
    actions = @_actionsFor(app\title!)
    if actions then for action in *actions do for win in *app\allWindows! do action\perform(win)

  isActive: =>
    for screen in *hs.screen.allScreens()
      if hs.fnutils.contains(@screens, screen\id!) then return true
    return false

  activateFor: (appName) =>
    app = hs.appfinder.appFromName(appName)
    if app then @_activateForApp(app)

  activate: =>
    hs.alert.show("Arranging #{@title}", 1)
    for app in *hs.application.runningApplications! do @_activateForApp(app)

{
  :Profile
}

require 'moonscript'

import unpack from table

class Profile
  new: (title, screens, config) =>
    @title = title
    @screens = screens
    @config = config

  isActive: =>
    for screen in *hs.screen.allScreens()
      if hs.fnutils.contains(@screens, screen\id!) then return true
    return false

  activateFor: (appname) =>
    app = hs.appfinder.appFromName(appname)
    if not app then return {}
    actions = @config[appname]
    windows = app\allWindows!
    for action in *actions
      for win in *windows do action\perform(win)
    return windows

  activate: =>
    hs.alert.show("Arranging #{@title}", 1)
    processed = [w for appname, _ in pairs(@config) for w in *@activateFor(appname)]

    -- Apply actions to all remaining windows
    actions = @config["_"]
    if not actions then return

    windows = [w for w in *hs.window.allWindows! when not hs.fnutils.contains(processed, w)]
    for action in *actions
      for win in *windows do action\perform(win)

{
  :Profile
}


acl logic

acl logic

import Model, enum from require "lapis.db.model"
import create_table, types, add_column from require "lapis.db.schema"

Users = require "lazuli.modules.user_management.models.users"
ACLs = require "models.acls"

class ACL_Entries extends Model

  matchUser: (user)=>
    if user
      if type(user)=="number"
        user=Users\find user
      return nil, "user not found" unless user
    switch @target_type
      when @@target_types.user
        if user and @target_id == user.id
          return @policy
      when @@target_types.include, @@target_types.include_inverted
        switch @target_id
          when @@special_targets.include_everyone
            return @policy
          when @@special_targets.include_logged_in
            return @policy if user
          else
            list=ACLs\find @target_id
            return nil, "include "..tostring(@target_id).." not found" unless list
            ret=list\matchUser user, false
            return nil, err if ret == nil and err
            if type(ret)=="boolean"
              return not ret if @target_type == @@target_types.include_inverted
              return ret

  @relations: {
    {"acl", belongs_to: "ACLs"}
    --{"target_user", belongs_to: "Users"}
    --{"target_include", belongs_to: "ACLs"}
  }

  @get_relation_model: (name)=> switch name
    when "Users"
      require "lazuli.modules.user_management.models.users"
    when "ACLs"
      require "models.acls"

  @target_types: enum {
    include: 1
    include_inverted: 2
    user: 3
  }

  @special_targets: enum {
    include_everyone: -1
    include_logged_in: -2
  }

  @migrations: {
    ->
      create_table "acl_entries", {
        {"id",                types.serial}
        {"acl_id",            types.integer}
        {"position",          types.integer default: 0}
        {"policy",            types.boolean}
        {"target_type",       types.integer}
        {"target_id",         types.integer null: true}
        "PRIMARY KEY (id)"
      }
  }

import Model, enum from require "lapis.db.model"
import create_table, types, add_column from require "lapis.db.schema"

Users = require "lazuli.modules.user_management.models.users"
ACLs = require "models.acls"

class ACL_Entries extends Model

  matchUser: (user)=>
    if user
      if type(user)=="number"
        user=Users\find user
      return nil, "user not found" unless user
    switch @target_type
      when @@target_types.user
        if user and @target_id == user.id
          return @policy
      when @@target_types.include, @@target_types.include_inverted
        switch @target_id
          when @@special_targets.include_everyone
            return @policy
          when @@special_targets.include_logged_in
            if user
              return @policy
          else
            list=ACLs\find @target_id
            return nil, "include "..tostring(@target_id).." not found" unless list
            ret=list\matchUser user, false
            return nil, err if ret == nil and err
            if type(ret)=="boolean"
              return not ret if @target_type == @@target_types.include_inverted
              return ret

  @relations: {
    {"acl", belongs_to: "ACLs"}
    --{"target_user", belongs_to: "Users"}
    --{"target_include", belongs_to: "ACLs"}
  }

  @get_relation_model: (name)=> switch name
    when "Users"
      require "lazuli.modules.user_management.models.users"
    when "ACLs"
      require "models.acls"

  @target_types: enum {
    include: 1
    include_inverted: 2
    user: 3
  }

  @special_targets: enum {
    include_everyone: -1
    include_logged_in: -2
  }

  @migrations: {
    ->
      create_table "acl_entries", {
        {"id",                types.serial}
        {"acl_id",            types.integer}
        {"position",          types.integer default: 0}
        {"policy",            types.boolean}
        {"target_type",       types.integer}
        {"target_id",         types.integer null: true}
        "PRIMARY KEY (id)"
      }
  }