logger: correct patterns

logger: correct patterns

Former-commit-id: d82c2c8cf4bd26f89a7b072d370ff9559aa2ec82 [formerly a2ff83623ecdbe9ee00c413f0cf9a9e3aab969ba]
Former-commit-id: 21a6985f8afb5d18a55bb85317ccd762796881ec

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

conn = false

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

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

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

  conn\autocommit(true)

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

dbh = ->
  connect! unless conn

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

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

  return conn

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

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

  unless arg
    arg = ''

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

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

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

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

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

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

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

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

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

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

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

conn = false

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

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

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

  conn\autocommit(true)

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

dbh = ->
  connect! unless conn

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

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

  return conn

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

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

  unless arg
    arg = ''

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

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

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

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

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

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

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

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

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

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


Fixed bad include

Fixed bad include

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

export PPM2
PPM2 = PPM2 or {}

DLib.manifest({
	name: 'PPM/2'
	prefix: 'dlib/autorun/ppm2'

	shared: {
		'common/controller_children.lua'
		'common/registry.lua'
		'common/functions.lua'
		'common/bodygroup_controller.lua'
		'common/weight_controller.lua'
		'common/pony_expressions_controller.lua'
		'common/emotes.lua'
		'common/flex_controller.lua'
		'common/registry_data.lua'
		'common/ponydata.lua'
		'common/ponyfly.lua'
		'common/size_controller.lua'
		'common/hooks.lua'
	}

	client: {
		'client/bones_modifier.lua'
		'client/data_instance.lua'
		'client/materials_registry.lua'
		'client/texture_controller.lua'
		'client/new_texture_controller.lua'
		'client/render.lua'
		'client/hooks.lua'
		'client/functions.lua'
		'client/render_controller.lua'
		'client/emotes.lua'
		'client/player_menu.lua'
		'client/editor.lua'
		'client/rag_edit.lua'
	}

	server: {
		'server/misc.lua'
		'server/hooks.lua'
		'server/emotes.lua'
		'server/hitgroups.lua'
		'server/rag_edit.lua'
	}
})

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

export PPM2
PPM2 = PPM2 or {}

DLib.manifest({
	name: 'PPM/2'
	prefix: 'dlib/autorun/ppm2'

	shared: {
		'common/controller_children.lua'
		'common/registry.lua'
		'common/functions.lua'
		'common/bodygroup_controller.lua'
		'common/weight_controller.lua'
		'common/pony_expressions_controller.lua'
		'common/emotes.lua'
		'common/flex_controller.lua'
		'common/registry_data.lua'
		'common/ponydata.lua'
		'common/ponyfly.lua'
		'common/size_controller.lua'
		'common/hooks.lua'
	}

	client: {
		'client/bones_modifier.lua'
		'client/data_instance.lua'
		'client/materials_registry.lua'
		'client/texture_controller.lua'
		'client/new_texture_controller.lua'
		'client/render.lua'
		'client/hooks.lua'
		'client/functions.lua'
		'client/render_controller.lua'
		'client/emotes.lua'
		'client/player_menu.lua'
		'client/editor.lua'
		'client/rag_edit.lua'
	}

	server: {
		'server/hooks.lua'
		'server/emotes.lua'
		'server/hitgroups.lua'
		'server/rag_edit.lua'
	}
})


Enable applications

Enable applications

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

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

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

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

    disable_email_confirmation true
    security_new_user_url 'http://127.0.0.1:19222/new_user'

    email_cooldown 20
    uploads_dir 'uploads'
    applications_enabled true

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

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

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

    disable_email_confirmation true
    security_new_user_url 'http://10.131.29.36:19222/new_user'

    email_cooldown 30 * 60
    uploads_dir 'uploads'
    applications_enabled true

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

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

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

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

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

    disable_email_confirmation true
    security_new_user_url 'http://127.0.0.1:19222/new_user'

    email_cooldown 20
    uploads_dir 'uploads'
    applications_enabled true

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

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

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

    disable_email_confirmation true
    security_new_user_url 'http://10.131.29.36:19222/new_user'

    email_cooldown 30 * 60
    uploads_dir 'uploads'
    applications_enabled false

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


Yolo kraj prijava

Yolo kraj prijava

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

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

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

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

    disable_email_confirmation true
    security_new_user_url 'http://127.0.0.1:19222/new_user'
    security_map_app_id_url 'http://127.0.0.1:19222/map_app_id'

    email_cooldown 20
    uploads_dir 'uploads'
    applications_enabled false

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

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

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

    disable_email_confirmation true
    security_new_user_url 'http://10.131.29.36:19222/new_user'
    security_map_app_id_url 'http://10.131.29.36:19222/map_app_id'

    email_cooldown 30 * 60
    uploads_dir 'uploads'
    applications_enabled true

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

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

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

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

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

    disable_email_confirmation true
    security_new_user_url 'http://127.0.0.1:19222/new_user'
    security_map_app_id_url 'http://127.0.0.1:19222/map_app_id'

    email_cooldown 20
    uploads_dir 'uploads'
    applications_enabled true

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

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

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

    disable_email_confirmation true
    security_new_user_url 'http://10.131.29.36:19222/new_user'
    security_map_app_id_url 'http://10.131.29.36:19222/map_app_id'

    email_cooldown 30 * 60
    uploads_dir 'uploads'
    applications_enabled true

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


logger: correct patterns

logger: correct patterns

Former-commit-id: c13159edbb089570183efd9a39090fde0688d415 [formerly cfcbf04dccc4e0ed22281b1f16a09312ae7adb16]
Former-commit-id: cc03397f3304f84f2b5921566df1d0d93918c8b0


docs for updated print

docs for updated print

format "lib.reference"

set "title", "Lua Reference"

lua_type = (o) ->
  o.invoke_operator = ":"
  type o

package {
  name: "nacl"
  description: [[
    Interface to the NaCl container.
  ]]

  method {
    name: "prefetch"
    args: {"resource_table"}
    description: [[
      Causes Aroma to block and fetch a collection of resources at once. The
      browser will download all the resources in parallel, which will take much
      less time than requesting them individually.

      This will not return any of the resources, it will only cache their
      values on the client so when you do ask for the request later it will be
      available without blocking the game.
    ]]
    code: [[
      nacl.prefetch({
        images = {
          "thing.png",
          "world.png",
        }
      })
    ]]
  }

  method {
    name: "track_event"
    args: {"category", "action", "label", "value", "interactive"}
    description: [[
      This will attempt to send a custom event to Google Analytics. Only works
      if Google Analytics is available on the game page.
    ]]

    code: [[
      if player.beat_game then
        nacl.track_event("my_game", "player_event", "win")
      end
    ]]
  }
}

package {
  name: "aroma"
  description: [[
    The `aroma` object serves two parts. It contains all Aroma
    submodules, which are also listed on this page, and it is where callback
    functions are assigned.

    Callbacks are functions that you create and assign to the `aroma` object.
    They will be called by the engine when certain events happen.

    You only need to assign these callback functions when if you are using
    them, leaving them out is perfectly okay. You should never need to call
    these functions manually (but you can).

    Here is the complete list of callbacks:
  ]]

  method {
    name: "draw"
    description: [[
      Where all of your drawing code should go.
    ]]
    code: [[
      function aroma.draw()
        aroma.graphics.print("hello world", 10, 10)
      end
    ]]
  }

  method {
    name: "update"
    args: {"dt"}
    description: [[
      Where game state updates should go. Takes one argument, `dt`, the amount
      of time in seconds since the last frame.

      You should not draw from this function, nothing will show up.
    ]]
    code: [[
      function aroma.update(dt)
        player.x = player.x + speed * dt
      end
    ]]
  }

  method {
    name: "focus"
    args: {"has_focus"}
    description: [[
      Called when the focus of the game window has changed
    ]]
    code: [[
      function aroma.focus(has_focus)
        print("Focus changed:", has_focus)
      end
    ]]
  }

  method {
    name: "keypressed"
    args: {"key_name", "key_code"}
    description: [[
      Called when a key is pressed down. Given two arguments:

      * `key_name` -- A string representing the name of the key pressed

      * `key_code` -- An integer representing the code of the key
    ]]

    code: [[
      function aroma.keypressed(key_name, key_code)
        print("Key pressed:", key_name, key_code)
      end
    ]]
  }

  method {
    name: "keyreleased"
    args: {"key_name", "key_code"}
    description: [[
      Callen when a key is released.

      Arguments are the same as [aroma.keypressed](#aroma.keypressed).
    ]]
  }
}

package {
  name: "aroma.graphics"
  show_tags: true
  tag_order: { "drawing", "constructors", "transformations", "color" }

  description: [[
    Functions responsible for drawing things on the screen or changing the
    state of drawing.
  ]]

  method {
    name: "setColor"
    tag: "color"
    args: { "red", "green", "blue", "[alpha]" }
    description: "Set the current color for drawing."

    code: [[
      -- draw a red rectangle
      function aroma.draw()
        aroma.graphics.setColor(255,0,0)
        aroma.graphics.rectangle(10,10, 50,50)
      end
    ]]
  }

  method {
    name: "getColor"
    tag: "color"
    returns: {"red", "green", "blue", "alpha"}
    description: "Gets the current color for drawing."
  }

  method {
    name: "setBackgroundColor"
    tag: "color"
    args: { "red", "green", "blue", "alpha" }
    description: "Set the color the screen is cleared to after every frame."
  }

  method {
    name: "setLineWidth"
    args: {"width"}
    description: "Set the width of lines lines drawn."
  }

  method {
    name: "getWidth"
    returns: { "width" }
    description: "Gets the width of the viewport in pixels."
  }

  method {
    name: "getHeight"
    returns: { "height" }
    description: "Gets the height of the viewport in pixels."
  }

  method {
    name: "reset"
    description: [[
      Resets graphics state to default. This includes:

        * Background color
        * Drawing color
        * Blend mode
    ]]
  }

  method {
    name: "rectangle"
    tag: "drawing"
    args: { "[render_style]", "x", "y", "width", "height" }
    description: [[
      Draws a rectangle on the screen.

      The color of the rectangle is the current color. See
      [aroma.graphics.setColor](#aroma.graphics.setColor).

      `render_style` is an optional string that determines how the rectangle is
      drawn. `"fill"` is the default. Must be one of the following:

        * `"fill"`
        * `"line"`
    ]]
  }

  method {
    name: "draw"
    tag: "drawing"
    args: {
      "drawable", "x", "y",
      "[rotate]", "[scale_x]", "[scale_y]", "[origin_x]", "[origin_y]"
    }
    description: [[
      Draws a *drawable* object on the screen. Right now the only drawable
      objects are images.

      `rotate` is clockwise rotation in degrees.

      `origin_x` and  `origin_y` can be used to control the origin of rotation.
    ]]
  }

  method {
    name: "drawq"
    tag: "drawing"
    args: {
      "drawable", "quad", "x", "y",
      "[rotate]", "[scale_x]", "[scale_y]", "[origin_x]", "[origin_y]"
    }
    description: [[
      Similar to [aroma.graphics.draw](#aroma.graphics.draw), but takes a
      [Quad](#aroma.graphics.Quad) as second argument.

      A Quad can be used to only draw a portion of an image. Useful for drawing
      sprites from a single image.
    ]]

    code: [[
      local img = aroma.graphics.newImage("hi.png")
      local q = aroma.graphics.newQuad(5, 5, 10, 10, img:getWidth(), img:getHeight())

      -- draws the part of the image specified by the quad at 10, 10
      function aroma.draw()
        aroma.graphics.drawq(img, q, 10, 10)
      end
    ]]
  }

  method {
    name: "setDefaultShader"
    args: {"shader"}
    description: [[
      Replaces the shader responsible for all drawing. Be careful with this. If
      the shader does not work in a specific way then nothing may be drawn or
      Aroma may crash.

      `shader` must be a shader created with
      [aroma.graphics.newShader](#aroma.graphics.newShader).
    ]]
  }

  method {
    name: "newShader"
    tag: "constructors"
    args: {"vertex_shader_source", "fragment_shader_source"}
    returns: {"shader"}
    description: [[
      Create a new shader.
    ]]
  }

  method {
    name: "newQuad"
    tag: "constructors"
    args: {"x", "y", "width", "height", "source_width", "source_height"}
    returns: {"quad"}
    description: [[
      Creates a new `Quad` for use with [aroma.graphics.drawq](#aroma.graphics.drawq).

      `source_width` and `source_height` are typically the width and height of
      the image the quad is being used on.

      `x`, `y`, `width`, `height` represent the rectangle that should be drawn
      from the image.
    ]]
  }

  method {
    name: "newImage"
    tag: "constructors"
    args: {"image_url"}
    returns: {"image"}
    description: [[
      Loads and image by url and returns it as an [Image](#Image) object.

      Images can be drawn to the screen using
      [aroma.graphics.draw](#aroma.graphics.draw).
    ]]

    code: [[
      local image = aroma.graphics.newImage("hello.png")

      function aroma.draw()
        aroma.graphics.draw(image, 10, 10)
      end
    ]]
  }

  method {
    name: "newFont"
    tag: "constructors"
    args: {"font_name", "[alphabet]"}
    returns: {"font"}
    description: [[
      Creates a new font and returns it. Fonts are rendered by the browser in a
      `<canvas>` tag and then passed back to Aroma.

      `font_name` must be a valid CSS font value.

      `alphabet` is an optional string whose characters will be the glyphs
      available in the font. Defaults to all numbers, letters, and symbols on
      an English keyboard.

      Custom fonts can be made available by embedding fonts into the stylesheet
      of the host page using `@font-face`.
    ]]

    code: [[
      local monospace = aroma.graphics.newFont("32px monospace")

      -- only numbers can be written with this font
      local numbers = aroma.graphics.newFont("10pt serif", "0987654321")
    ]]
  }

  method {
    name: "newImageFont"
    tag: "constructors"
    args: {"image", "alphabet"}
    returns: {"font"}
    description: [[
      Creates a new font from an image and returns it. Unlike
      [aroma.graphics.newFont](#aroma.graphics.newFont), letters are extracted
      from a bitmap instead a typeface from the browser.

      * `image` -- An image url or an ImageData object.
      * `alphabet` -- A string of letters as they appear in the image.

      The image must be formatted as a single row of letters where each letter
      is separated by a spacer color:

      ![number font](img/number_font.png)

      The spacer color is always the top left color in the image, so place a
      spacer column as the leftmost thing in the image. The spacer column can be any width.

      The rightmost part of the image must also be a spacer column, or the last
      letter will be left out.

    ]]
    code: [[
      -- write some text with the font above
      local number_font = aroma.graphics.newImageFont("numbers.png", " 1234567890")
      aroma.graphics.setFont(number_font)

      function aroma.draw()
        aroma.graphics.print("1337", 10, 10)
      end
    ]]
  }

  method {
    name: "getFont"
    returns: { "font" }
    description: [[
      Gets the current font.
    ]]
  }

  method {
    name: "setFont"
    args: { "font" }
    description: [[
      Sets the current font. `font` must be a font return by
      [aroma.graphics.newFont](#aroma.graphics.newFont).
    ]]
  }

  method {
    name: "push"
    tag: "transformations"
    description: [[
      Copies and pushes the current transformation onto the transformation
      stack.
    ]]
  }

  method {
    name: "pop"
    tag: "transformations"
    description: [[
      Pops the current transformation off the transformation stack.
    ]]
  }

  method {
    name: "translate"
    tag: "transformations"
    args: {"tx", "tx"}
    description: [[
      Modifies the current transformation by translating all draws by `tx` and
      `ty`.
    ]]

    code: [[
      function aroma.draw()
        aroma.graphics.translate(20, 20)
        -- will be drawn at 25, 25
        aroma.graphics.rectangle(5, 5, 10, 10)
      end
    ]]
  }

  method {
    name: "scale"
    tag: "transformations"
    args: { "sx", "sy" }
    description: [[
      Modifies the current transformation by scaling all draws by `sx` and
      `sy`.
    ]]
  }

  method {
    name: "rotate"
    tag: "transformations"
    args: { "deg" }
    description: [[
      Modifies the current transformation to rotate all draws by `deg` degrees.

      The center of origin is at `(0,0)`. It is helpful to combine this with
      translation control the center of origin relative to the object being
      drawing.
    ]]
  }

  method {
    name: "print"
    tag: "drawing"
    args: {
      "[font]", "text", "x", "y",
      "[rotate]", "[scale_x]", "[scale_y]", "[origin_x]", "[origin_y]"
    }
    description: [[
      Draws text on the screen. If `font` is not provided, the default font
      will be used. See [aroma.graphics.setFont](#aroma.graphics.setFont).
    ]]
  }

  lua_type {
    name: "Image"

    description: [[
      Images can be drawn with
    ]]

    method {
      name: "getWidth"
      returns: {"width"}

      description: [[
        Gets the width of the image.
      ]]
    }

    method {
      name: "getHeight"
      returns: {"height"}

      description: [[
        Gets the height of the image.
      ]]
    }

    method {
      name: "setWrap"
      args: {"horiz_wrap", "vert_wrap"}
      description: [[
        Sets the wrapping mode of the image when overdrawn (by something like
        `drawq`). By default wrap is set to `"clamp"`.

        Possible values for each:

        * `"clamp"` -- the color at the edge of the texture will be used to
          fill the extra space.

        * `"repeat"` -- The texture will repeat to fill extra space.
      ]]
    }

    method {
      name: "setFilter"
      args: {"min_filter", "mag_filter"}
      description: [[
        Sets how images are filtered when they are scaled. By default wrap is
        set to `"linear"` for both min and mag.

        * `min_filter` -- applied when the image is rendered smaller
          than its original dimensions.

        * `mag_filter` -- applied when the image is rendered larger
          its original dimensions.

        Posible values include:

        * `"linear"` --  The colors of nearby pixels are blended, creates a
          smoothing effect.

        * `"nearest"` -- no blending is done. This is the best filter to use
          when working with pixel art.

      ]]
    }
  }

  lua_type {
    name: "Font"
    description: [[
      Represents a loaded font. Created with [aroma.graphics.newFont](#aroma.graphics.newFont).

      Can be drawn with [setFont](#aroma.graphics.setFont) or
      [print](#aroma.graphics.print).
    ]]
  }

  lua_type {
    name: "Quad"
    description: [[
      Represents a rectangular portion of something. Created with [aroma.graphics.newQuad](#aroma.graphics.newQuad).

      Drawn with [aroma.graphics.drawq](#aroma.graphics.drawq).
    ]]

    method {
      name: "flip"
      args: {"x", "y"}
      description: [[
        * `x` -- Flips Quad horizontally if true

        * `y` -- Flips Quad vertically if true
      ]]
    }
  }
}

package {
  name: "aroma.audio"
  description: [[
    Functions for working with sound.
  ]]

  method {
    name: "newSource"
    args: {"source_url", "[source_type]"}
    returns: {"audio"}
    description: [[
      Loads a new source by url. Returns an [AudioSource](#aroma.audio.AudioSource).

      `source_type` is an optional value that determines how the sound is
      loaded. `"static"` is the default. The following types are valid:

      * `"static"` -- The function will block until the entire audio source is
        loaded into memory and ready be played. Uses the Web Audio API.
        Suitable for short samples such as sound effects.

      * `"streaming"` -- Uses the `<audio>` tag. The function will not wait for
        the entire file to download, will only verify that the file exists.
        Playback of the sound might be delayed. Can also be unreliable.
        Suitable for background music.
    ]]

    code: [[
      local bg = aroma.audio.newSource("game/theme.ogg", "streaming")
      local effect = aroma.audio.newSource("game/shoot.ogg")

      bg:play() -- not guaranteed to play immediately, could still be downloading

      effect:play() -- guaranteed to play
    ]]
  }

  lua_type {
    name: "AudioSource"
    instance_name: "source"

    description: [[
      A static or stremaing audio srouce. Created with [aroma.audio.newSource](#aroma.audio.newSource).
    ]]

    method {
      name: "play"
    }

    method {
      name: "pause"
    }

    method {
      name: "stop"
    }

    method {
      name: "setLooping"
      args: {"looping"}
    }

  }

}

package {
  name: "aroma.image"
  description: [[
    Functions for manipulating images and image data.
  ]]

  method {
    name: "newImageData"
    args: {"image_url"}
    returns: {"data"}
  }
}

package {
  name: "aroma.keyboard"
  description: "Functions for querying information about the keyboard."

  method {
    name: "isDown"
    args: {"key_name"}
    returns: {"is_down"}
    description: [[
      Checks if a key is currently pressed down.
    ]]

    code: [[
      function aroma.update()
        if aroma.keyboard.isDown(" ") then
          print("Space is pressed!")
        end
      end
    ]]
  }
}

package {
  name: "aroma.timer"
  description: "Functions relating to the internal timer."

  method {
    name: "getTime"
    returns: {"time"}
    description: [[
      Gets the value of the internal timer in seconds. The value by itself is
      meaningless but it useful when compared against other times.
    ]]
  }

  method {
    name: "getFPS"
    returns: {"fps"}
    description: "Gets the frames per second of the game."
  }

}

format "lib.reference"

set "title", "Lua Reference"

lua_type = (o) ->
  o.invoke_operator = ":"
  type o

package {
  name: "nacl"
  description: [[
    Interface to the NaCl container.
  ]]

  method {
    name: "prefetch"
    args: {"resource_table"}
    description: [[
      Causes Aroma to block and fetch a collection of resources at once. The
      browser will download all the resources in parallel, which will take much
      less time than requesting them individually.

      This will not return any of the resources, it will only cache their
      values on the client so when you do ask for the request later it will be
      available without blocking the game.
    ]]
    code: [[
      nacl.prefetch({
        images = {
          "thing.png",
          "world.png",
        }
      })
    ]]
  }

  method {
    name: "track_event"
    args: {"category", "action", "label", "value", "interactive"}
    description: [[
      This will attempt to send a custom event to Google Analytics. Only works
      if Google Analytics is available on the game page.
    ]]

    code: [[
      if player.beat_game then
        nacl.track_event("my_game", "player_event", "win")
      end
    ]]
  }
}

package {
  name: "aroma"
  description: [[
    The `aroma` object serves two parts. It contains all Aroma
    submodules, which are also listed on this page, and it is where callback
    functions are assigned.

    Callbacks are functions that you create and assign to the `aroma` object.
    They will be called by the engine when certain events happen.

    You only need to assign these callback functions when if you are using
    them, leaving them out is perfectly okay. You should never need to call
    these functions manually (but you can).

    Here is the complete list of callbacks:
  ]]

  method {
    name: "draw"
    description: [[
      Where all of your drawing code should go.
    ]]
    code: [[
      function aroma.draw()
        aroma.graphics.print("hello world", 10, 10)
      end
    ]]
  }

  method {
    name: "update"
    args: {"dt"}
    description: [[
      Where game state updates should go. Takes one argument, `dt`, the amount
      of time in seconds since the last frame.

      You should not draw from this function, nothing will show up.
    ]]
    code: [[
      function aroma.update(dt)
        player.x = player.x + speed * dt
      end
    ]]
  }

  method {
    name: "focus"
    args: {"has_focus"}
    description: [[
      Called when the focus of the game window has changed
    ]]
    code: [[
      function aroma.focus(has_focus)
        print("Focus changed:", has_focus)
      end
    ]]
  }

  method {
    name: "keypressed"
    args: {"key_name", "key_code"}
    description: [[
      Called when a key is pressed down. Given two arguments:

      * `key_name` -- A string representing the name of the key pressed

      * `key_code` -- An integer representing the code of the key
    ]]

    code: [[
      function aroma.keypressed(key_name, key_code)
        print("Key pressed:", key_name, key_code)
      end
    ]]
  }

  method {
    name: "keyreleased"
    args: {"key_name", "key_code"}
    description: [[
      Callen when a key is released.

      Arguments are the same as [aroma.keypressed](#aroma.keypressed).
    ]]
  }
}

package {
  name: "aroma.graphics"
  show_tags: true
  tag_order: { "drawing", "constructors", "transformations", "color" }

  description: [[
    Functions responsible for drawing things on the screen or changing the
    state of drawing.
  ]]

  method {
    name: "setColor"
    tag: "color"
    args: { "red", "green", "blue", "[alpha]" }
    description: "Set the current color for drawing."

    code: [[
      -- draw a red rectangle
      function aroma.draw()
        aroma.graphics.setColor(255,0,0)
        aroma.graphics.rectangle(10,10, 50,50)
      end
    ]]
  }

  method {
    name: "getColor"
    tag: "color"
    returns: {"red", "green", "blue", "alpha"}
    description: "Gets the current color for drawing."
  }

  method {
    name: "setBackgroundColor"
    tag: "color"
    args: { "red", "green", "blue", "alpha" }
    description: "Set the color the screen is cleared to after every frame."
  }

  method {
    name: "setLineWidth"
    args: {"width"}
    description: "Set the width of lines lines drawn."
  }

  method {
    name: "getWidth"
    returns: { "width" }
    description: "Gets the width of the viewport in pixels."
  }

  method {
    name: "getHeight"
    returns: { "height" }
    description: "Gets the height of the viewport in pixels."
  }

  method {
    name: "reset"
    description: [[
      Resets graphics state to default. This includes:

        * Background color
        * Drawing color
        * Blend mode
    ]]
  }

  method {
    name: "rectangle"
    tag: "drawing"
    args: { "[render_style]", "x", "y", "width", "height" }
    description: [[
      Draws a rectangle on the screen.

      The color of the rectangle is the current color. See
      [aroma.graphics.setColor](#aroma.graphics.setColor).

      `render_style` is an optional string that determines how the rectangle is
      drawn. `"fill"` is the default. Must be one of the following:

        * `"fill"`
        * `"line"`
    ]]
  }

  method {
    name: "draw"
    tag: "drawing"
    args: {
      "drawable", "x", "y",
      "[rotate]", "[scale_x]", "[scale_y]", "[origin_x]", "[origin_y]"
    }
    description: [[
      Draws a *drawable* object on the screen. Right now the only drawable
      objects are images.

      `rotate` is clockwise rotation in degrees.

      `origin_x` and  `origin_y` can be used to control the origin of rotation.
    ]]
  }

  method {
    name: "drawq"
    tag: "drawing"
    args: {
      "drawable", "quad", "x", "y",
      "[rotate]", "[scale_x]", "[scale_y]", "[origin_x]", "[origin_y]"
    }
    description: [[
      Similar to [aroma.graphics.draw](#aroma.graphics.draw), but takes a
      [Quad](#aroma.graphics.Quad) as second argument.

      A Quad can be used to only draw a portion of an image. Useful for drawing
      sprites from a single image.
    ]]

    code: [[
      local img = aroma.graphics.newImage("hi.png")
      local q = aroma.graphics.newQuad(5, 5, 10, 10, img:getWidth(), img:getHeight())

      -- draws the part of the image specified by the quad at 10, 10
      function aroma.draw()
        aroma.graphics.drawq(img, q, 10, 10)
      end
    ]]
  }

  method {
    name: "setDefaultShader"
    args: {"shader"}
    description: [[
      Replaces the shader responsible for all drawing. Be careful with this. If
      the shader does not work in a specific way then nothing may be drawn or
      Aroma may crash.

      `shader` must be a shader created with
      [aroma.graphics.newShader](#aroma.graphics.newShader).
    ]]
  }

  method {
    name: "newShader"
    tag: "constructors"
    args: {"vertex_shader_source", "fragment_shader_source"}
    returns: {"shader"}
    description: [[
      Create a new shader.
    ]]
  }

  method {
    name: "newQuad"
    tag: "constructors"
    args: {"x", "y", "width", "height", "source_width", "source_height"}
    returns: {"quad"}
    description: [[
      Creates a new `Quad` for use with [aroma.graphics.drawq](#aroma.graphics.drawq).

      `source_width` and `source_height` are typically the width and height of
      the image the quad is being used on.

      `x`, `y`, `width`, `height` represent the rectangle that should be drawn
      from the image.
    ]]
  }

  method {
    name: "newImage"
    tag: "constructors"
    args: {"image_url"}
    returns: {"image"}
    description: [[
      Loads and image by url and returns it as an [Image](#Image) object.

      Images can be drawn to the screen using
      [aroma.graphics.draw](#aroma.graphics.draw).
    ]]

    code: [[
      local image = aroma.graphics.newImage("hello.png")

      function aroma.draw()
        aroma.graphics.draw(image, 10, 10)
      end
    ]]
  }

  method {
    name: "newFont"
    tag: "constructors"
    args: {"font_name", "[alphabet]"}
    returns: {"font"}
    description: [[
      Creates a new font and returns it. Fonts are rendered by the browser in a
      `<canvas>` tag and then passed back to Aroma.

      `font_name` must be a valid CSS font value.

      `alphabet` is an optional string whose characters will be the glyphs
      available in the font. Defaults to all numbers, letters, and symbols on
      an English keyboard.

      Custom fonts can be made available by embedding fonts into the stylesheet
      of the host page using `@font-face`.
    ]]

    code: [[
      local monospace = aroma.graphics.newFont("32px monospace")

      -- only numbers can be written with this font
      local numbers = aroma.graphics.newFont("10pt serif", "0987654321")
    ]]
  }

  method {
    name: "newImageFont"
    tag: "constructors"
    args: {"image", "alphabet"}
    returns: {"font"}
    description: [[
      Creates a new font from an image and returns it. Unlike
      [aroma.graphics.newFont](#aroma.graphics.newFont), letters are extracted
      from a bitmap instead a typeface from the browser.

      * `image` -- An image url or an ImageData object.
      * `alphabet` -- A string of letters as they appear in the image.

      The image must be formatted as a single row of letters where each letter
      is separated by a spacer color:

      ![number font](img/number_font.png)

      The spacer color is always the top left color in the image, so place a
      spacer column as the leftmost thing in the image. The spacer column can be any width.

      The rightmost part of the image must also be a spacer column, or the last
      letter will be left out.

    ]]
    code: [[
      -- write some text with the font above
      local number_font = aroma.graphics.newImageFont("numbers.png", " 1234567890")
      aroma.graphics.setFont(number_font)

      function aroma.draw()
        aroma.graphics.print("1337", 10, 10)
      end
    ]]
  }

  method {
    name: "getFont"
    returns: { "font" }
    description: [[
      Gets the current font.
    ]]
  }

  method {
    name: "setFont"
    args: { "font" }
    description: [[
      Sets the current font. `font` must be a font return by
      [aroma.graphics.newFont](#aroma.graphics.newFont).
    ]]
  }

  method {
    name: "push"
    tag: "transformations"
    description: [[
      Copies and pushes the current transformation onto the transformation
      stack.
    ]]
  }

  method {
    name: "pop"
    tag: "transformations"
    description: [[
      Pops the current transformation off the transformation stack.
    ]]
  }

  method {
    name: "translate"
    tag: "transformations"
    args: {"tx", "tx"}
    description: [[
      Modifies the current transformation by translating all draws by `tx` and
      `ty`.
    ]]

    code: [[
      function aroma.draw()
        aroma.graphics.translate(20, 20)
        -- will be drawn at 25, 25
        aroma.graphics.rectangle(5, 5, 10, 10)
      end
    ]]
  }

  method {
    name: "scale"
    tag: "transformations"
    args: { "sx", "sy" }
    description: [[
      Modifies the current transformation by scaling all draws by `sx` and
      `sy`.
    ]]
  }

  method {
    name: "rotate"
    tag: "transformations"
    args: { "deg" }
    description: [[
      Modifies the current transformation to rotate all draws by `deg` degrees.

      The center of origin is at `(0,0)`. It is helpful to combine this with
      translation control the center of origin relative to the object being
      drawing.
    ]]
  }

  method {
    name: "print"
    tag: "drawing"
    args: {"[font]", "text", "x", "y"}
    description: [[
      Draws text on the screen. If `font` is not provided, the default font
      will be used. See [aroma.graphics.setFont](#aroma.graphics.setFont).
    ]]
  }

  lua_type {
    name: "Image"

    description: [[
      Images can be drawn with
    ]]

    method {
      name: "getWidth"
      returns: {"width"}

      description: [[
        Gets the width of the image.
      ]]
    }

    method {
      name: "getHeight"
      returns: {"height"}

      description: [[
        Gets the height of the image.
      ]]
    }

    method {
      name: "setWrap"
      args: {"horiz_wrap", "vert_wrap"}
      description: [[
        Sets the wrapping mode of the image when overdrawn (by something like
        `drawq`). By default wrap is set to `"clamp"`.

        Possible values for each:

        * `"clamp"` -- the color at the edge of the texture will be used to
          fill the extra space.

        * `"repeat"` -- The texture will repeat to fill extra space.
      ]]
    }

    method {
      name: "setFilter"
      args: {"min_filter", "mag_filter"}
      description: [[
        Sets how images are filtered when they are scaled. By default wrap is
        set to `"linear"` for both min and mag.

        * `min_filter` -- applied when the image is rendered smaller
          than its original dimensions.

        * `mag_filter` -- applied when the image is rendered larger
          its original dimensions.

        Posible values include:

        * `"linear"` --  The colors of nearby pixels are blended, creates a
          smoothing effect.

        * `"nearest"` -- no blending is done. This is the best filter to use
          when working with pixel art.

      ]]
    }
  }

  lua_type {
    name: "Font"
    description: [[
      Represents a loaded font. Created with [aroma.graphics.newFont](#aroma.graphics.newFont).

      Can be drawn with [setFont](#aroma.graphics.setFont) or
      [print](#aroma.graphics.print).
    ]]
  }

  lua_type {
    name: "Quad"
    description: [[
      Represents a rectangular portion of something. Created with [aroma.graphics.newQuad](#aroma.graphics.newQuad).

      Drawn with [aroma.graphics.drawq](#aroma.graphics.drawq).
    ]]

    method {
      name: "flip"
      args: {"x", "y"}
      description: [[
        * `x` -- Flips Quad horizontally if true

        * `y` -- Flips Quad vertically if true
      ]]
    }
  }
}

package {
  name: "aroma.audio"
  description: [[
    Functions for working with sound.
  ]]

  method {
    name: "newSource"
    args: {"source_url", "[source_type]"}
    returns: {"audio"}
    description: [[
      Loads a new source by url. Returns an [AudioSource](#aroma.audio.AudioSource).

      `source_type` is an optional value that determines how the sound is
      loaded. `"static"` is the default. The following types are valid:

      * `"static"` -- The function will block until the entire audio source is
        loaded into memory and ready be played. Uses the Web Audio API.
        Suitable for short samples such as sound effects.

      * `"streaming"` -- Uses the `<audio>` tag. The function will not wait for
        the entire file to download, will only verify that the file exists.
        Playback of the sound might be delayed. Can also be unreliable.
        Suitable for background music.
    ]]

    code: [[
      local bg = aroma.audio.newSource("game/theme.ogg", "streaming")
      local effect = aroma.audio.newSource("game/shoot.ogg")

      bg:play() -- not guaranteed to play immediately, could still be downloading

      effect:play() -- guaranteed to play
    ]]
  }

  lua_type {
    name: "AudioSource"
    instance_name: "source"

    description: [[
      A static or stremaing audio srouce. Created with [aroma.audio.newSource](#aroma.audio.newSource).
    ]]

    method {
      name: "play"
    }

    method {
      name: "pause"
    }

    method {
      name: "stop"
    }

    method {
      name: "setLooping"
      args: {"looping"}
    }

  }

}

package {
  name: "aroma.image"
  description: [[
    Functions for manipulating images and image data.
  ]]

  method {
    name: "newImageData"
    args: {"image_url"}
    returns: {"data"}
  }
}

package {
  name: "aroma.keyboard"
  description: "Functions for querying information about the keyboard."

  method {
    name: "isDown"
    args: {"key_name"}
    returns: {"is_down"}
    description: [[
      Checks if a key is currently pressed down.
    ]]

    code: [[
      function aroma.update()
        if aroma.keyboard.isDown(" ") then
          print("Space is pressed!")
        end
      end
    ]]
  }
}

package {
  name: "aroma.timer"
  description: "Functions relating to the internal timer."

  method {
    name: "getTime"
    returns: {"time"}
    description: [[
      Gets the value of the internal timer in seconds. The value by itself is
      meaningless but it useful when compared against other times.
    ]]
  }

  method {
    name: "getFPS"
    returns: {"fps"}
    description: "Gets the frames per second of the game."
  }

}


plugins/output: Only one argument to QUIT


amatyr: fix wind unit

amatyr: fix wind unit

Former-commit-id: f6d14520ef9c2480cbf609ecb4544aa95f782e2d
Former-commit-id: 80f85e6af2876822ba00c9eeef6af554d8e663fb

{:simplehttp, :json} = require'util'

windSpeedToName = (ws) ->
  windtext = ''
  unless ws
    return windtext
  if ws > 32
    windtext = 'orkan!'
  elseif ws > 28.5
    windtext = 'sterk storm'
  elseif ws > 24.5
    windtext = 'full storm'
  elseif ws > 20.8
    windtext = 'liten storm'
  elseif ws > 17.2
    windtext = 'sterk kuling'
  elseif ws > 13.9
    windtext = 'stiv kuling'
  elseif ws > 10.8
    windtext = 'liten kuling'
  elseif ws > 8
    windtext = 'frisk bris'
  elseif ws > 5.5
    windtext = 'laber bris'
  elseif ws > 3.4
    windtext = 'lett bris'
  elseif ws > 1.5
    windtext = 'svak vind'
  elseif ws > 0.3
    windtext = 'flau vind'
  else
    windtext = 'vindstille'
  return windtext

handleWeather = (source, destination, url) =>
    simplehttp url, (data) ->
      result = json.decode data
      if result
        result = result[1]
        temp = result.outtemp
        ws = result.windspeed
        rain = result.dayrain
        windtext = windSpeedToName(ws)
        wgs = windSpeedToName(result.windgust)
        if wgs ~= windtext
          wgs = ", #{wgs} i kasta"
        else
          wgs = ''

        time = result.datetime\sub(12, 16)

        msg = string.format '\002%.1f\002 °C, \002%.1f\002 m/s (%s%s), \002%.1f\002 mm nedbør (%s)', temp, ws, windtext, wgs, rain, time
        @Privmsg destination, msg

PRIVMSG:
  '^%pnaustvêr$': (source, destination) =>
    url = 'http://yr.hveem.no/api/now'
    handleWeather(@, source, destination, url)
  '^%pvêr$': (source, destination, input) =>
    url = 'http://yr.teigen.be/api/now'
    handleWeather(@, source, destination, url)

{:simplehttp, :json} = require'util'

windSpeedToName = (ws) ->
  windtext = ''
  unless ws
    return windtext
  if ws > 32
    windtext = 'orkan!'
  elseif ws > 28.5
    windtext = 'sterk storm'
  elseif ws > 24.5
    windtext = 'full storm'
  elseif ws > 20.8
    windtext = 'liten storm'
  elseif ws > 17.2
    windtext = 'sterk kuling'
  elseif ws > 13.9
    windtext = 'stiv kuling'
  elseif ws > 10.8
    windtext = 'liten kuling'
  elseif ws > 8
    windtext = 'frisk bris'
  elseif ws > 5.5
    windtext = 'laber bris'
  elseif ws > 3.4
    windtext = 'lett bris'
  elseif ws > 1.5
    windtext = 'svak vind'
  elseif ws > 0.3
    windtext = 'flau vind'
  else
    windtext = 'vindstille'
  return windtext

handleWeather = (source, destination, url) =>
    simplehttp url, (data) ->
      result = json.decode data
      if result
        result = result[1]
        temp = result.outtemp
        ws = result.windspeed / 3.6 -- correct unit
        rain = result.dayrain
        windtext = windSpeedToName(ws)
        wgs = windSpeedToName(result.windgust/3.6)
        if wgs ~= windtext
          wgs = ", #{wgs} i kasta"
        else
          wgs = ''

        time = result.datetime\sub(12, 16)

        msg = string.format '\002%.1f\002 °C, \002%.1f\002 m/s (%s%s), \002%.1f\002 mm nedbør (%s)', temp, ws, windtext, wgs, rain, time
        @Privmsg destination, msg

PRIVMSG:
  '^%pnaustvêr$': (source, destination) =>
    url = 'http://yr.hveem.no/api/now'
    handleWeather(@, source, destination, url)
  '^%pvêr$': (source, destination, input) =>
    url = 'http://yr.teigen.be/api/now'
    handleWeather(@, source, destination, url)


Fix Entity\render

Fix Entity\render

* Type in variable and wrong indentation for graphic\render
* Also call super! from update

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

export ^

class Entity extends Tweener
	-- Constructor. Can be usd to place the Entity and assign a graphic and mask.
	-- @param	x			X position to place the Entity.
	-- @param	y			Y position to place the Entity.
	-- @param	graphic		Graphic to assign to the Entity.
	-- @param	mask		Mask to assign to the Entity.
	new: (x = 0, y = 0, graphic = nil, mask = nil) =>
		super!
		-- If the Entity should render.
		@visible = true
		-- If the Entity should respond to collision checks.
		@collidable = true
		-- If the entity should follow the camera.
		@followCamera = false
		-- X position of the Entity in the Scene.
		@__x = x
		-- Y position of the Entity in the Scene.
		@__y = y
		-- X origin of the Entity's hitbox.
		@originX = 0
		-- Y origin of the Entity's hitbox.
		@originY = 0
		-- Width of the Entity's hitbox.
		@width = 0
		-- Height of the Entity's hitbox.
		@height = 0

		-- The bottommost position of the Entity's hitbox.
		@bottom = 0
		-- The center x position of the Entity's hitbox.
		@centerX = 0
		-- The center y position of the Entity's hitbox.
		@centerY = 0
		-- The collision type, used for collision checking.
		@__typeNext = nil
		@__typePrev = nil
		@__type = ""
		@name = ""

		@__point = LP.point
		@__camera = LP.point2

		-- Collision information.
		@__hitbox = Mask!
		-- An optional Mask component, used for specialized collision. If this is not assigned, collision checks will use the Entity's hitbox by default.
		@__mask = mask if mask != nil
		@__moveX, @__moveY = 0, 0
		@__collide_x, @__collide_y = 0, 0

		-- The rendering layer of this Entity. Higher layers are rendered first.
		@__layer = LP.BASELAYER!

		-- Graphical component to render to the screen.
		@__graphic = graphic if graphic != nil

		@__hitbox\assignTo @

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

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

	-- Y position of the Entity in the Scene.
	y: (value) =>
		if value != nil
			@__y = value
			return @__y
		if @followCamera
			@__y + LP.camera.y
		else
			@__y

	-- Some helper accessors

	-- If the Entity collides with the camera rectangle.
	onCamera: =>
		return false if @__scene == nil
		@collideRect @x!, @y!, @__scene.camera.x, @__scene.camera.y, LP.width, LP.height

	-- Half the Entity's width.
	halfWidth: => @width/2
	-- Half the Entity's height.
	halfHeight: => @height/2
	-- The center x position of the Entity's hitbox.
	centerX: => @x! - @originX + @halfWidth!
	-- The center y position of the Entity's hitbox.
	centerY: => @y! - @originY + @halfHeight!
	-- The leftmost position of the Entity's hitbox.
	left: => @x! - @originX
	-- The rightmost position of the Entity's hitbox.
	right: => @x! - @originX + @width
	-- The topmost position of the Entity's hitbox.
	top: => @y! - @originY
	-- The bottommost position of the Entity's hitbox.
	bottom: => @y! - @originY + @height

	layer: (value) =>
		return @__layer if value == nil

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

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

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

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

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

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

		@__type

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

		@__mask\assignTo(nil) if @__mask != nil
		@__mask = if value then value else nil
		@__mask\assignTo(@) if value

		@__mask

	graphic: (value) =>
		return @__graphic if value == nil or @__graphic == value

		value.layer = @__layer if value
		@__graphic = if value then value else nil
		@__graphic

	-- The World object is deprecated for FlashPunk-like compatibility
	-- TODO give a massage about this kept for porting compatibility
	world: (value) =>
		@__scene = value if value != nil
		@__scene

	-- Override this, called when the Entity is added to a Scene.
	added: =>
	-- Override this, called when the Entity is removed from a Scene.
	removed: =>
	-- Updates the Entity.
	update: => super!

	-- Renders the Entity. If you override this for special behaviour,
	-- remember to call super.render() to render the Entity's graphic.
	render: =>
		if @__graphic != nil and @__graphic.visible
			if @__graphic.relative
				@__point.x = @x!
				@__point.y = @y!
			else
				@__point.x, @__point.y = 0, 0
				@__camera.x = if @__scene == nil then LP.camera.x else @__scene.camera.x
				@__camera.y = if @__scene == nil then LP.camera.y else @__scene.camera.y

			@__graphic\render @__point, @__camera

	-- Checks for a collision against an Entity type.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or null if none were collided.
	collide: (type, x, y) =>
		return nil if @__scene == nil

		return nil if not @collidable or @__scene.__typeFirst == nil
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y
		e = @__scene.typeFirst
		if @__mask == nil
			while e != nil
				c = do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width > e.x - e.originX
					c3 = y - @originY + @height > e.y - e.originY
					c4 = x - @originX < e.x - e.originX + e.width
					c5 = y - @originY < e.y - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if c
					if e.__mask == nil or e.__mask\collide @HITBOX
						@x @__collide_x
						@y @__collide_y
						return e

				e = e.__typeNext

			@x @__collide_x
			@y @__colide_y

			return nil

		while e != nil
			c = do
				c1 = e.collidable and e != @
				c2 = x - @originX + @width > e.x - e.originX
				c3 = y - @originY + @height > e.y - e.originY
				c4 = x - @originX < e.x - e.originX + e.width
				c5 = y - @originY < e.y - e.originY + e.height
				c1 and c2 and c3 and c4 and c5
			if c
				if e.__mask\collide (if e.__mask != nil then e.__mask else e.HITBOX)
					@x @__collide_x
					@y @__collide_y
					return e

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y

		return nil

	-- Checks for collision against multiple Entity types.
	-- @param	types		An Array or Vector of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or null if none were collided.
	collideTypes: (types, x, y) =>
		return nil if @__scene == nil

		if type(types) == "string"
			return @collide types, x, y
		else
			if types != nil
				for t in *types
					e = @collide t, x, y
					return e if e != nil

		return nil

	-- Checks if this Entity collides with a specific Entity.
	-- @param	e		The Entity to collide against.
	-- @param	x		Virtual x position to place this Entity.
	-- @param	y		Virtual y position to place this Entity.
	-- @return	The Entity if they overlap, or null if they don't.
	collideWith: (e, x, y) =>
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		c = do
			c1 = @collidable and e.collidable
			c2 = x - @originX + @width > e.x - e.originX
			c3 = y - @originY + @height > e.y - e.originY
			c4 = x - @originX < e.x - e.originX + e.width
			c5 = y - @originY < e.y - e.originY + e.height
			c1 and c2 and c3 and c4 and c5
		if c
			if @__mask == nil
				if e.__mask == nil or e.__mask\collide @__hitbox
					@x @__collide_x
					@y @__collide_y
					return e
				@x @__collide_x
				@y @__collide_y
				return nil

			if (if @__mask\collide(e.__mask) != nil then e.__mask else e.__hitbox)
				@x @__collide_x
				@y @__collide_y
				return e

		@x @__collide_x
		@y @__collide_y
		return nil

	-- Checks if this Entity overlaps the specified rectangle.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	rX			X position of the rectangle.
	-- @param	rY			Y position of the rectangle.
	-- @param	rWidth		Width of the rectangle.
	-- @param	rHeight		Height of the rectangle.
	-- @return	If they overlap.
	collideRect: (x, y, rX, rY, rWidth, rHeight) =>
		c = do
			c1 = x - @originX + @width >= rX
			c2 = y - @originY + @height >= rY
			c3 = x - @originX <= rX + rWidth
			c4 = y - @originY <= rY + rHeight
			c1 and c2 and c3 and c4
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x = rX
			LP.entity.y = rY
			LP.entity.width = rWidth
			LP.entity.height = rHeight
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Checks if this Entity overlaps the specified position.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	pX			X position.
	-- @param	pY			Y position.
	-- @return	If the Entity intersects with the position.
	collidePoint: (x, y, pX, pY) =>
		c = do
			c1 = pX >= x - @originX
			c2 = pY >= y - @originY
			c3 = pX < x - @originX + @width
			c4 = pY < y - @originY + height
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x, LP.entity.y = pX, pY
			LP.entity.width, LP.entity.height = 1, 1
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Populates an array with all collided Entities of a type.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	-- @return	The array, populated with all collided Entities.
	collideInto: (type, x, y, array) =>
		return if @__scene == nil

		e = @__scene.__typeFirst\get type
		return if not @collidable or e == nil

		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		if @__mask == nil
			while e != nil
				c = do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width > e.x - e.originX
					c3 = y - @originY + @height > e.y - e.originY
					c4 = x - @originX < e.x - e.originX + e.width
					c5 = y - @originY < e.y - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if c
					array[#array+1] = e if e__mask == nil or e.__mask\collide @__hitbox

				e = e.__typeNext

			@x @__collide_x
			@y @__collide_y
			return

		while e != nil
			c = do
				c1 = e.collidable and e != @
				c2 = x - @originX + @width > e.x - e.originX
				c3 = y - @originY + @height > e.y - e.originY
				c4 = x - @originX < e.x - e.originX + e.width
				c5 = y - @originY < e.y - e.originY + e.height
				c1 and c2 and c3 and c4 and c5
			if c
				array[#array+1] = e if (if @__mask\collide(e.__mask) != nil then e.__mask else e.__hitbox)

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y
		return

	-- Populates an array with all collided Entities of multiple types.
	-- @param	types		An array of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	-- @return	The array, populated with all collided Entities.
	collideTypesInto: (types, x, y, array) =>
		return if @__scene == nil
		for t in *types
			@collideInto t, x, y, array

	-- Adds the graphic to the Entity via a Graphiclist.
	-- @param	g		Graphic to add.
	addGraphic: (g) =>
		g.layer = @__layer
		if @__graphic == nil
			@graphic g
		else if moon_type(g) == Graphiclist
			@graphic!\add g
		else
			list = Graphiclist!
			list\add graphic if @graphic! != nil
			list\add g
			@graphic list
			list\setEntity @

		g

	-- Sets the Entity's hitbox properties.
	-- @param	width		Width of the hitbox.
	-- @param	height		Height of the hitbox.
	-- @param	originX		X origin of the hitbox.
	-- @param	originY		Y origin of the hitbox.
	setHitbox: (width, height, originX, originY) =>
		@width, @height = width, height
		@originX, @originY = originX, originY

	-- Sets the Entity's hitbox to match that of the provided object.
	-- @param	o		The object defining the hitbox (eg. an Image or Rectangle).
	setHitboxTo: (o) =>
		@width, @height = o.width, o.height

		if o.originX and o.originY
			@originX, @originY = o.originX, o.originY
		else
			@originX, @originY = o.x, o.y
			@originX, @originY = -@originX, -@originY

	-- Sets the origin of the Entity.
	-- @param	x		X origin.
	-- @param	y		Y origin.
	setOrigin: (x, y) => @originX, @originY = x, y

	-- Center's the Entity's origin (half width & height).
	centerOrigin: => @originX, @originY = @halfWidth!, @halfHeight!

	-- Calculates the distance from another Entity.
	-- @param	e				The other Entity.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceFrom: (e, useHitboxes) =>
		return math.sqrt (@x! - e.x) * (@x! - e.x) + (@y! - e.y) * (@y - e.y) if not useHitboxes
		LP.distanceRects @x! - @originX, @y! - @originY, @width, @height, e.x - e.originX, e.y - e.originY, e.width, e.height

	-- Calculates the distance from this Entity to the point.
	-- @param	px				X position.
	-- @param	py				Y position.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceToPoint: (px, py, useHitbox) =>
		return math.sqrt (@x! - px) * (@x! - px) + (@y! - py) * (@y! - py) if not useHitbox
		LP.distanceRectPoint px, py, @x! - @originX, @y! - @originY, @width, @height

	-- Calculates the distance from this Entity to the rectangle.
	-- @param	rx			X position of the rectangle.
	-- @param	ry			Y position of the rectangle.
	-- @param	rwidth		Width of the rectangle.
	-- @param	rheight		Height of the rectangle.
	-- @return	The distance.
	distanceToRect: (rx, ry, rwidth, rheight) =>
		LP.distanceRects rx, ry, rwidth, rheight, @x! - @originX, @y! - @originY, @width, @height

	-- Gets the class name as a string.
	-- @return	A string representing the class name.
	toString: => @@.__name

	__tostring: => @toString!

	-- Moves the Entity by the amount, retaining integer values for its x and y.
	-- @param	x			Horizontal offset.
	-- @param	y			Vertical offset.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveBy: (x, y, solidType, sweep) =>
		@__moveX += x
		@__moveY += y
		x, y = Math.round(@__moveX), Math.round(@__moveY)
		@__moveX -= x
		@__moveY -= y

		if solidType != nil
			if x != 0
				if @collidable and (sweep or @collideTypes(solidType, @x! + x, @y!) != nil)
					sign = if x > 0 then 1 else -1
					while x != 0
						e = @collideTypes solidType, @x! + sign, @y!
						if e != nil
							break if @moveCollideX e
							@x @x! + sign
						else
							@x @x! + sign

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

			if y != 0
				if @collidable and (sweep or @collideTypes(solidType, @x!, @y! + y) != nil)
					sign = if y > 0 then 1 else -1
					while y != 0
						e = @collideTypes solidType, @x!, @y! + sign
						if e != nil
							break if @moveCollideY e
							@y @y! + sign
						else
							@y @y! + sign

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

	-- Moves the Entity to the position, retaining integer values for its x and y.
	-- @param	x			X position.
	-- @param	y			Y position.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTo: (x, y, solidType, sweep) => moveBy x - @x!, y - @y!, solidType, sweep

	-- Moves towards the target position, retaining integer values for its x and y.
	-- @param	x			X target.
	-- @param	y			Y target.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTowards: (x, y, amount, solidType, sweep) =>
		@__point.x, @__point.y = x - @x!, y - @y!
		@__point.normalize amount if @__point.x * @__point.x + @__point.y * @__point.y > amount * amount
		@moveBy @__point.x, @__point.y, solidType, sweep

	-- Moves at an angle by a certain amount, retaining integer values for its x and y.
	-- @param	angle		Angle to move at in degrees.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveAtAngle: (angle, amount, solidType, sweep) =>
		angle *= LP.RAD!
		@moveBy math.cos(angle) * amount, math.sin(angle) * amount, solidType, sweep

	-- When you collide with an Entity on the x-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideX: (e) => true

	-- When you collide with an Entity on the y-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideY: (e) => true

	-- Clamps the Entity's hitbox on the x-axis.
	-- @param	left		Left bounds.
	-- @param	right		Right bounds.
	-- @param	padding		Optional padding on the clamp.
	clampHorizontal: (left, right, padding) =>
		@x left + @originX + padding if @x! - @originX < left + padding
		@x right - @width + @originX - padding if @x! - @originX + @width > right - padding

	-- Clamps the Entity's hitbox on the y axis.
	-- @param	top			Min bounds.
	-- @param	bottom		Max bounds.
	-- @param	padding		Optional padding on the clamp.
	clampVertical: (top, bottom, padding) =>
		@y top + @originY + padding if @y! - @originY < top + padding
		@y bottom - @height + @originX - padding if @y! - @originY + @height > bottom - padding

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

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

export ^

class Entity extends Tweener
	-- Constructor. Can be usd to place the Entity and assign a graphic and mask.
	-- @param	x			X position to place the Entity.
	-- @param	y			Y position to place the Entity.
	-- @param	graphic		Graphic to assign to the Entity.
	-- @param	mask		Mask to assign to the Entity.
	new: (x = 0, y = 0, graphic = nil, mask = nil) =>
		super!
		-- If the Entity should render.
		@visible = true
		-- If the Entity should respond to collision checks.
		@collidable = true
		-- If the entity should follow the camera.
		@followCamera = false
		-- X position of the Entity in the Scene.
		@__x = x
		-- Y position of the Entity in the Scene.
		@__y = y
		-- X origin of the Entity's hitbox.
		@originX = 0
		-- Y origin of the Entity's hitbox.
		@originY = 0
		-- Width of the Entity's hitbox.
		@width = 0
		-- Height of the Entity's hitbox.
		@height = 0

		-- The bottommost position of the Entity's hitbox.
		@bottom = 0
		-- The center x position of the Entity's hitbox.
		@centerX = 0
		-- The center y position of the Entity's hitbox.
		@centerY = 0
		-- The collision type, used for collision checking.
		@__typeNext = nil
		@__typePrev = nil
		@__type = ""
		@name = ""

		@__point = LP.point
		@__camera = LP.point2

		-- Collision information.
		@__hitbox = Mask!
		-- An optional Mask component, used for specialized collision. If this is not assigned, collision checks will use the Entity's hitbox by default.
		@__mask = mask if mask != nil
		@__moveX, @__moveY = 0, 0
		@__collide_x, @__collide_y = 0, 0

		-- The rendering layer of this Entity. Higher layers are rendered first.
		@__layer = LP.BASELAYER!

		-- Graphical component to render to the screen.
		@__graphic = graphic if graphic != nil

		@__hitbox\assignTo @

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

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

	-- Y position of the Entity in the Scene.
	y: (value) =>
		if value != nil
			@__y = value
			return @__y
		if @followCamera
			@__y + LP.camera.y
		else
			@__y

	-- Some helper accessors

	-- If the Entity collides with the camera rectangle.
	onCamera: =>
		return false if @__scene == nil
		@collideRect @x!, @y!, @__scene.camera.x, @__scene.camera.y, LP.width, LP.height

	-- Half the Entity's width.
	halfWidth: => @width/2
	-- Half the Entity's height.
	halfHeight: => @height/2
	-- The center x position of the Entity's hitbox.
	centerX: => @x! - @originX + @halfWidth!
	-- The center y position of the Entity's hitbox.
	centerY: => @y! - @originY + @halfHeight!
	-- The leftmost position of the Entity's hitbox.
	left: => @x! - @originX
	-- The rightmost position of the Entity's hitbox.
	right: => @x! - @originX + @width
	-- The topmost position of the Entity's hitbox.
	top: => @y! - @originY
	-- The bottommost position of the Entity's hitbox.
	bottom: => @y! - @originY + @height

	layer: (value) =>
		return @__layer if value == nil

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

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

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

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

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

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

		@__type

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

		@__mask\assignTo(nil) if @__mask != nil
		@__mask = if value then value else nil
		@__mask\assignTo(@) if value

		@__mask

	graphic: (value) =>
		return @__graphic if value == nil or @__graphic == value

		value.layer = @__layer if value
		@__graphic = if value then value else nil
		@__graphic

	-- The World object is deprecated for FlashPunk-like compatibility
	-- TODO give a massage about this kept for porting compatibility
	world: (value) =>
		@__scene = value if value != nil
		@__scene

	-- Override this, called when the Entity is added to a Scene.
	added: =>
	-- Override this, called when the Entity is removed from a Scene.
	removed: =>
	-- Updates the Entity.
	update: =>

	-- Renders the Entity. If you override this for special behaviour,
	-- remember to call super.render() to render the Entity's graphic.
	render: =>
		if @__graphic != nil and @__graphic.visable
			if @__graphic.relative
				@__point.x = @x!
				@__point.y = @y!
			else
				@__point.x, @__point.y = 0, 0
				@__camera.x = if @__scene == nil then LP.camera.x else @__scene.camera.x
				@__camera.y = if @__scene == nil then LP.camera.y else @__scene.camera.y
				@__graphic\render (if @__renderTarget != nil then @__renderTarget else LP.buffer), @__point, @__camera

	-- Checks for a collision against an Entity type.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or null if none were collided.
	collide: (type, x, y) =>
		return nil if @__scene == nil

		return nil if not @collidable or @__scene.__typeFirst == nil
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y
		e = @__scene.typeFirst
		if @__mask == nil
			while e != nil
				c = do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width > e.x - e.originX
					c3 = y - @originY + @height > e.y - e.originY
					c4 = x - @originX < e.x - e.originX + e.width
					c5 = y - @originY < e.y - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if c
					if e.__mask == nil or e.__mask\collide @HITBOX
						@x @__collide_x
						@y @__collide_y
						return e

				e = e.__typeNext

			@x @__collide_x
			@y @__colide_y

			return nil

		while e != nil
			c = do
				c1 = e.collidable and e != @
				c2 = x - @originX + @width > e.x - e.originX
				c3 = y - @originY + @height > e.y - e.originY
				c4 = x - @originX < e.x - e.originX + e.width
				c5 = y - @originY < e.y - e.originY + e.height
				c1 and c2 and c3 and c4 and c5
			if c
				if e.__mask\collide (if e.__mask != nil then e.__mask else e.HITBOX)
					@x @__collide_x
					@y @__collide_y
					return e

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y

		return nil

	-- Checks for collision against multiple Entity types.
	-- @param	types		An Array or Vector of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or null if none were collided.
	collideTypes: (types, x, y) =>
		return nil if @__scene == nil

		if type(types) == "string"
			return @collide types, x, y
		else
			if types != nil
				for t in *types
					e = @collide t, x, y
					return e if e != nil

		return nil

	-- Checks if this Entity collides with a specific Entity.
	-- @param	e		The Entity to collide against.
	-- @param	x		Virtual x position to place this Entity.
	-- @param	y		Virtual y position to place this Entity.
	-- @return	The Entity if they overlap, or null if they don't.
	collideWith: (e, x, y) =>
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		c = do
			c1 = @collidable and e.collidable
			c2 = x - @originX + @width > e.x - e.originX
			c3 = y - @originY + @height > e.y - e.originY
			c4 = x - @originX < e.x - e.originX + e.width
			c5 = y - @originY < e.y - e.originY + e.height
			c1 and c2 and c3 and c4 and c5
		if c
			if @__mask == nil
				if e.__mask == nil or e.__mask\collide @__hitbox
					@x @__collide_x
					@y @__collide_y
					return e
				@x @__collide_x
				@y @__collide_y
				return nil

			if (if @__mask\collide(e.__mask) != nil then e.__mask else e.__hitbox)
				@x @__collide_x
				@y @__collide_y
				return e

		@x @__collide_x
		@y @__collide_y
		return nil

	-- Checks if this Entity overlaps the specified rectangle.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	rX			X position of the rectangle.
	-- @param	rY			Y position of the rectangle.
	-- @param	rWidth		Width of the rectangle.
	-- @param	rHeight		Height of the rectangle.
	-- @return	If they overlap.
	collideRect: (x, y, rX, rY, rWidth, rHeight) =>
		c = do
			c1 = x - @originX + @width >= rX
			c2 = y - @originY + @height >= rY
			c3 = x - @originX <= rX + rWidth
			c4 = y - @originY <= rY + rHeight
			c1 and c2 and c3 and c4
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x = rX
			LP.entity.y = rY
			LP.entity.width = rWidth
			LP.entity.height = rHeight
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Checks if this Entity overlaps the specified position.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	pX			X position.
	-- @param	pY			Y position.
	-- @return	If the Entity intersects with the position.
	collidePoint: (x, y, pX, pY) =>
		c = do
			c1 = pX >= x - @originX
			c2 = pY >= y - @originY
			c3 = pX < x - @originX + @width
			c4 = pY < y - @originY + height
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x, LP.entity.y = pX, pY
			LP.entity.width, LP.entity.height = 1, 1
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Populates an array with all collided Entities of a type.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	-- @return	The array, populated with all collided Entities.
	collideInto: (type, x, y, array) =>
		return if @__scene == nil

		e = @__scene.__typeFirst\get type
		return if not @collidable or e == nil

		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		if @__mask == nil
			while e != nil
				c = do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width > e.x - e.originX
					c3 = y - @originY + @height > e.y - e.originY
					c4 = x - @originX < e.x - e.originX + e.width
					c5 = y - @originY < e.y - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if c
					array[#array+1] = e if e__mask == nil or e.__mask\collide @__hitbox

				e = e.__typeNext

			@x @__collide_x
			@y @__collide_y
			return

		while e != nil
			c = do
				c1 = e.collidable and e != @
				c2 = x - @originX + @width > e.x - e.originX
				c3 = y - @originY + @height > e.y - e.originY
				c4 = x - @originX < e.x - e.originX + e.width
				c5 = y - @originY < e.y - e.originY + e.height
				c1 and c2 and c3 and c4 and c5
			if c
				array[#array+1] = e if (if @__mask\collide(e.__mask) != nil then e.__mask else e.__hitbox)

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y
		return

	-- Populates an array with all collided Entities of multiple types.
	-- @param	types		An array of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	-- @return	The array, populated with all collided Entities.
	collideTypesInto: (types, x, y, array) =>
		return if @__scene == nil
		for t in *types
			@collideInto t, x, y, array

	-- Adds the graphic to the Entity via a Graphiclist.
	-- @param	g		Graphic to add.
	addGraphic: (g) =>
		g.layer = @__layer
		if @__graphic == nil
			@graphic g
		else if moon_type(g) == Graphiclist
			@graphic!\add g
		else
			list = Graphiclist!
			list\add graphic if @graphic! != nil
			list\add g
			@graphic list
			list\setEntity @

		g

	-- Sets the Entity's hitbox properties.
	-- @param	width		Width of the hitbox.
	-- @param	height		Height of the hitbox.
	-- @param	originX		X origin of the hitbox.
	-- @param	originY		Y origin of the hitbox.
	setHitbox: (width, height, originX, originY) =>
		@width, @height = width, height
		@originX, @originY = originX, originY

	-- Sets the Entity's hitbox to match that of the provided object.
	-- @param	o		The object defining the hitbox (eg. an Image or Rectangle).
	setHitboxTo: (o) =>
		@width, @height = o.width, o.height

		if o.originX and o.originY
			@originX, @originY = o.originX, o.originY
		else
			@originX, @originY = o.x, o.y
			@originX, @originY = -@originX, -@originY

	-- Sets the origin of the Entity.
	-- @param	x		X origin.
	-- @param	y		Y origin.
	setOrigin: (x, y) => @originX, @originY = x, y

	-- Center's the Entity's origin (half width & height).
	centerOrigin: => @originX, @originY = @halfWidth!, @halfHeight!

	-- Calculates the distance from another Entity.
	-- @param	e				The other Entity.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceFrom: (e, useHitboxes) =>
		return math.sqrt (@x! - e.x) * (@x! - e.x) + (@y! - e.y) * (@y - e.y) if not useHitboxes
		LP.distanceRects @x! - @originX, @y! - @originY, @width, @height, e.x - e.originX, e.y - e.originY, e.width, e.height

	-- Calculates the distance from this Entity to the point.
	-- @param	px				X position.
	-- @param	py				Y position.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceToPoint: (px, py, useHitbox) =>
		return math.sqrt (@x! - px) * (@x! - px) + (@y! - py) * (@y! - py) if not useHitbox
		LP.distanceRectPoint px, py, @x! - @originX, @y! - @originY, @width, @height

	-- Calculates the distance from this Entity to the rectangle.
	-- @param	rx			X position of the rectangle.
	-- @param	ry			Y position of the rectangle.
	-- @param	rwidth		Width of the rectangle.
	-- @param	rheight		Height of the rectangle.
	-- @return	The distance.
	distanceToRect: (rx, ry, rwidth, rheight) =>
		LP.distanceRects rx, ry, rwidth, rheight, @x! - @originX, @y! - @originY, @width, @height

	-- Gets the class name as a string.
	-- @return	A string representing the class name.
	toString: => @@.__name

	__tostring: => @toString!

	-- Moves the Entity by the amount, retaining integer values for its x and y.
	-- @param	x			Horizontal offset.
	-- @param	y			Vertical offset.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveBy: (x, y, solidType, sweep) =>
		@__moveX += x
		@__moveY += y
		x, y = Math.round(@__moveX), Math.round(@__moveY)
		@__moveX -= x
		@__moveY -= y

		if solidType != nil
			if x != 0
				if @collidable and (sweep or @collideTypes(solidType, @x! + x, @y!) != nil)
					sign = if x > 0 then 1 else -1
					while x != 0
						e = @collideTypes solidType, @x! + sign, @y!
						if e != nil
							break if @moveCollideX e
							@x @x! + sign
						else
							@x @x! + sign

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

			if y != 0
				if @collidable and (sweep or @collideTypes(solidType, @x!, @y! + y) != nil)
					sign = if y > 0 then 1 else -1
					while y != 0
						e = @collideTypes solidType, @x!, @y! + sign
						if e != nil
							break if @moveCollideY e
							@y @y! + sign
						else
							@y @y! + sign

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

	-- Moves the Entity to the position, retaining integer values for its x and y.
	-- @param	x			X position.
	-- @param	y			Y position.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTo: (x, y, solidType, sweep) => moveBy x - @x!, y - @y!, solidType, sweep

	-- Moves towards the target position, retaining integer values for its x and y.
	-- @param	x			X target.
	-- @param	y			Y target.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTowards: (x, y, amount, solidType, sweep) =>
		@__point.x, @__point.y = x - @x!, y - @y!
		@__point.normalize amount if @__point.x * @__point.x + @__point.y * @__point.y > amount * amount
		@moveBy @__point.x, @__point.y, solidType, sweep

	-- Moves at an angle by a certain amount, retaining integer values for its x and y.
	-- @param	angle		Angle to move at in degrees.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveAtAngle: (angle, amount, solidType, sweep) =>
		angle *= LP.RAD!
		@moveBy math.cos(angle) * amount, math.sin(angle) * amount, solidType, sweep

	-- When you collide with an Entity on the x-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideX: (e) => true

	-- When you collide with an Entity on the y-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideY: (e) => true

	-- Clamps the Entity's hitbox on the x-axis.
	-- @param	left		Left bounds.
	-- @param	right		Right bounds.
	-- @param	padding		Optional padding on the clamp.
	clampHorizontal: (left, right, padding) =>
		@x left + @originX + padding if @x! - @originX < left + padding
		@x right - @width + @originX - padding if @x! - @originX + @width > right - padding

	-- Clamps the Entity's hitbox on the y axis.
	-- @param	top			Min bounds.
	-- @param	bottom		Max bounds.
	-- @param	padding		Optional padding on the clamp.
	clampVertical: (top, bottom, padding) =>
		@y top + @originY + padding if @y! - @originY < top + padding
		@y bottom - @height + @originX - padding if @y! - @originY + @height > bottom - padding

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