imageupload: chunked upload, video

imageupload: chunked upload, video

Former-commit-id: fba367729fdbb2cb5417b02d9384050c00e76f72 [formerly 753f78f657be7ae54c5b4f5ad324faa583fd6fd2]
Former-commit-id: 14b3631115cdf2180bce2b7cf5a01f4da0630721

{:simplehttp, :json, :urlEncode} = require'util'
html2unicode = require 'html'
nixio = require'nixio'
fs = require'nixio.fs'
ltn12 = require'ltn12'

urlbase = '/image/'

safe = (fn) ->
  f, ext = fn\match'^(.*)%.(.-)$'
  f = f\gsub '[^%w%-]', ''
  return f..'.'..ext

ivar2.webserver.regUrl "#{urlbase}(.*)$", (req, res) ->
  send = (body, code, content_type) ->
    if not code then code = 200
    if not content_type then content_type = 'text/html'
    res\set_status code
    res\set_header 'Content-Type', content_type
    res\set_header 'Content-Length', #body
    res\set_body body
    res\send!
    res.on_error = (res, req, err) ->
      ivar2\Log 'error', 'imageupload: error: %s', err

  file = req.url\match '/file/(.*)$'
  if file
    fn = "cache/images/#{safe file}"
    size = nixio.fs.stat(fn).size
    fd = ltn12.source.file(io.open fn, 'rb')
    --body = fd\read '*a'
    --fd\close!
    content_type = 'image/jpeg'
    if file\match '.png'
      content_type = 'image/png'
    if file\match '.svg'
      content_type = 'image/svg'
    if file\match '.mp4'
      content_type = 'video/mp4'
    if file\match '.mp3'
      content_type = 'audio/mp3'

    res\set_status code
    res\set_header 'Content-Type', content_type
    res\set_header 'Content-Length', size
    res\set_body fd
    res\send!
    ivar2\Log 'error', 'imageupload: serving: %s', fn

    return

  channel = req.url\match('channel=(.+)')
  unless channel
    send 'Invalid channel', 404
    return

  channel = html2unicode channel
  unescaped_channel = channel\gsub '%%23', '#'

  html = [[
  <!DOCTYPE html>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="IRCSNAP ]]..unescaped_channel..[[">
  <meta name="theme-color" content="#8f4099">

  <title>IRCSNAP</title>

Share to IRC app - ]]..unescaped_channel..[[ edition

Fill in your nick/name and optional text and then click on one of the two yellow buttons to attach image. It will appear instantly on IRC!</p
    <form>

        <input id="sender" type="text" required>

        Your name

        <input id="text" type="text" required>

        Attach text

    </form>

Uploading. Percent complete: 0 %

      📷 Snap picture! <input type="file" accept="image/*" id="capturei" capture="camera" style="visibility:hidden;">
      📂 Browse gallery! <input type="file" accept="image/*" id="capturef" style="visibility:hidden;">
      🎥 Capture video! <input type="file" accept="video/*" capture="camcorder" id="capturev" style="visibility:hidden;">

        What is this?

Share to IRC is a simple web app for sharing media directly from your device to an IRC channel.

Click Tools -> Add to homescreen to create a convenient shortcut for easy and fast access to sharing

  ]]
  if req.method == 'POST'
    fn = req.headers['X-Filename']
    sender = req.headers['X-Sender'] or ''
    text = req.headers['X-Text'] or ''
    file = req.filename
    ivar2\Log 'info', "imageupload: Recieved file name: <#{fn}> datalen: #{#file}, sender: <#{sender}> text: <#{text}>, channel: <#{unescaped_channel}>"
    if fn and file
      html = 'Ok'
      realfn = "#{os.time!}-#{safe fn}"
      save = ->
        --fd = nixio.open "cache/images/#{realfn}", 'w+', 0400
        --written = fd\write file
        --fd\sync!
        --fd\close!
        -- Move tempfile to real location
        fs.move req.filename, "cache/images/#{realfn}"
        if sender ~= ''
          sender = "<#{sender}> "
        msg = "[IRCSNAP] #{sender}#{text\sub(1, 100)} http://irc.lart.no:#{ivar2.config.webserverport}#{urlbase}file/#{realfn}"
        ivar2\Privmsg unescaped_channel, msg
      ok, err = pcall(save)
      unless ok
        ivar2\Log 'error', "imageupload: Error during saving upload: %s", err
    else
      html = 'Not OK'

  send html

-- Attempt to create the cache folder.
nixio.fs.mkdir('cache/images')

PRIVMSG:
  '^%pircsnap$': (source, destination) =>
    channel = urlEncode destination
    say "http://irc.lart.no:#{ivar2.config.webserverport}#{urlbase}?channel=#{channel}"

{:simplehttp, :json, :urlEncode} = require'util'
html2unicode = require 'html'
nixio = require'nixio'
ltn12 = require'ltn12'

urlbase = '/image/'

safe = (fn) ->
  f, ext = fn\match'^(.*)%.(.-)$'
  f = f\gsub '[^%w%-]', ''
  return f..'.'..ext

ivar2.webserver.regUrl "#{urlbase}(.*)$", (req, res) ->
  send = (body, code, content_type) ->
    if not code then code = 200
    if not content_type then content_type = 'text/html'
    res\set_status code
    res\set_header 'Content-Type', content_type
    res\set_header 'Content-Length', #body
    res\set_body body
    res\send!
    res.on_error = (res, req, err) ->
      ivar2\Log 'error', 'imageupload: error: %s', err

  file = req.url\match '/file/(.*)$'
  if file
    fn = "cache/images/#{safe file}"
    size = nixio.fs.stat(fn).size
    fd = ltn12.source.file(io.open fn, 'rb')
    --body = fd\read '*a'
    --fd\close!
    content_type = 'image/jpeg'
    if file\match '.png'
      content_type = 'image/png'
    if file\match '.svg'
      content_type = 'image/svg'
    if file\match '.mp4'
      content_type = 'video/mp4'
    if file\match '.mp3'
      content_type = 'audio/mp3'

    res\set_status code
    res\set_header 'Content-Type', content_type
    res\set_header 'Content-Length', size
    res\set_body fd
    res\send!
    ivar2\Log 'error', 'imageupload: serving: %s', fn

    return

  channel = req.url\match('channel=(.+)')
  unless channel
    send 'Invalid channel', 404
    return

  channel = html2unicode channel
  unescaped_channel = channel\gsub '%%23', '#'

  html = [[
  <!DOCTYPE html>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="IRCSNAP ]]..unescaped_channel..[[">
  <meta name="theme-color" content="#8f4099">

  <title>IRCSNAP</title>

Share to IRC app - ]]..unescaped_channel..[[ edition

Fill in your nick/name and optional text and then click on one of the two yellow buttons to attach image. It will appear instantly on IRC!</p
    <form>

        <input id="sender" type="text" required>

        Your name

        <input id="text" type="text" required>

        Attach text

    </form>

Uploading. Percent complete: 0 %

      📷 Snap picture! <input type="file" accept="image/*" id="capturei" capture="camera" style="visibility:hidden;">
      📂 Browse gallery! <input type="file" accept="image/*" id="capturef" style="visibility:hidden;">

        What is this?

Share to IRC is a simple web app for sharing media directly from your device to an IRC channel.

Click Tools -> Add to homescreen to create a convenient shortcut for easy and fast access to sharing

  ]]
  if req.method == 'POST'
    fn = req.headers['X-Filename']
    sender = req.headers['X-Sender'] or ''
    text = req.headers['X-Text'] or ''
    file = req.body
    ivar2\Log 'info', "imageupload: Recieved file name: <#{fn}> datalen: #{#file}, sender: <#{sender}> text: <#{text}>, channel: <#{unescaped_channel}>"
    if fn and file
      html = 'Ok'
      realfn = "#{os.time!}-#{safe fn}"
      save = ->
        fd = nixio.open "cache/images/#{realfn}", 'w+', 0400
        written = fd\write file
        fd\sync!
        fd\close!
        if sender ~= ''
          sender = "<#{sender}> "
        msg = "[IRCSNAP] #{sender}#{text\sub(1, 100)} http://irc.lart.no:#{ivar2.config.webserverport}#{urlbase}file/#{realfn}"
        ivar2\Privmsg unescaped_channel, msg
      ok, err = pcall(save)
      unless ok
        ivar2\Log 'error', "imageupload: Error during saving upload: %s", err
    else
      html = 'Not OK'

  send html

-- Attempt to create the cache folder.
nixio.fs.mkdir('cache/images')

PRIVMSG:
  '^%pircsnap$': (source, destination) =>
    channel = urlEncode destination
    say "http://irc.lart.no:#{ivar2.config.webserverport}#{urlbase}?channel=#{channel}"


Skip player update if dead

Skip player update if dead

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

timer.Create 'PPM2.ModelWatchdog', 1, 0, ->
    for ply in *player.GetAll()
        model = ply\GetModel()
        ply.__ppm2_lastmodel = ply.__ppm2_lastmodel or model
        if ply.__ppm2_lastmodel ~= model
            data = ply\GetPonyData()
            if data and data.ModelChanges
                data\ModelChanges(ply.__ppm2_lastmodel, model)
                ply.__ppm2_lastmodel = model

do
    catchError = (err) ->
        PPM2.Message 'Slow Update Error: ', err
        PPM2.Message debug.traceback()
    timer.Create 'PPM2.SlowUpdate', 5, 0, ->
        for ply in *player.GetAll()
            continue if not ply\Alive()
            data = ply\GetPonyData()
            continue if not data
            xpcall(data.SlowUpdate, catchError, data) if data.SlowUpdate

DISABLE_HOOFSTEP_SOUND_CLIENT = CreateConVar('ppm2_cl_no_hoofsound', '0', {FCVAR_ARCHIVE}, 'Disable hoofstep sound play time') if CLIENT
DISABLE_HOOFSTEP_SOUND = CreateConVar('ppm2_no_hoofsound', '0', {FCVAR_ARCHIVE, FCVAR_REPLICATED}, 'Disable hoofstep sound play time')

hook.Add 'PlayerStepSoundTime', 'PPM2.Hooks', (stepType = STEPSOUNDTIME_NORMAL, isWalking = false) =>
    return if not IsValid(@)
    return if not @IsPonyCached()
    return if CLIENT and DISABLE_HOOFSTEP_SOUND_CLIENT\GetBool()
    return if DISABLE_HOOFSTEP_SOUND\GetBool()
    rate = @GetPlaybackRate() * .5
    if @Crouching()
        switch stepType
            when STEPSOUNDTIME_NORMAL
                return not isWalking and (300 / rate) or (600 / rate)
            when STEPSOUNDTIME_ON_LADDER
                return 500 / rate
            when STEPSOUNDTIME_WATER_KNEE
                return not isWalking and (400 / rate) or (800 / rate)
            when STEPSOUNDTIME_WATER_FOOT
                return not isWalking and (350 / rate) or (700 / rate)
    else
        switch stepType
            when STEPSOUNDTIME_NORMAL
                return not isWalking and (150 / rate) or (300 / rate)
            when STEPSOUNDTIME_ON_LADDER
                return 500 / rate
            when STEPSOUNDTIME_WATER_KNEE
                return not isWalking and (250 / rate) or (500 / rate)
            when STEPSOUNDTIME_WATER_FOOT
                return not isWalking and (175 / rate) or (350 / rate)

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

timer.Create 'PPM2.ModelWatchdog', 1, 0, ->
    for ply in *player.GetAll()
        model = ply\GetModel()
        ply.__ppm2_lastmodel = ply.__ppm2_lastmodel or model
        if ply.__ppm2_lastmodel ~= model
            data = ply\GetPonyData()
            if data and data.ModelChanges
                data\ModelChanges(ply.__ppm2_lastmodel, model)
                ply.__ppm2_lastmodel = model

do
    catchError = (err) ->
        PPM2.Message 'Slow Update Error: ', err
        PPM2.Message debug.traceback()
    timer.Create 'PPM2.SlowUpdate', 5, 0, ->
        for ply in *player.GetAll()
            data = ply\GetPonyData()
            continue if not data
            xpcall(data.SlowUpdate, catchError, data) if data.SlowUpdate

DISABLE_HOOFSTEP_SOUND_CLIENT = CreateConVar('ppm2_cl_no_hoofsound', '0', {FCVAR_ARCHIVE}, 'Disable hoofstep sound play time') if CLIENT
DISABLE_HOOFSTEP_SOUND = CreateConVar('ppm2_no_hoofsound', '0', {FCVAR_ARCHIVE, FCVAR_REPLICATED}, 'Disable hoofstep sound play time')

hook.Add 'PlayerStepSoundTime', 'PPM2.Hooks', (stepType = STEPSOUNDTIME_NORMAL, isWalking = false) =>
    return if not IsValid(@)
    return if not @IsPonyCached()
    return if CLIENT and DISABLE_HOOFSTEP_SOUND_CLIENT\GetBool()
    return if DISABLE_HOOFSTEP_SOUND\GetBool()
    rate = @GetPlaybackRate() * .5
    if @Crouching()
        switch stepType
            when STEPSOUNDTIME_NORMAL
                return not isWalking and (300 / rate) or (600 / rate)
            when STEPSOUNDTIME_ON_LADDER
                return 500 / rate
            when STEPSOUNDTIME_WATER_KNEE
                return not isWalking and (400 / rate) or (800 / rate)
            when STEPSOUNDTIME_WATER_FOOT
                return not isWalking and (350 / rate) or (700 / rate)
    else
        switch stepType
            when STEPSOUNDTIME_NORMAL
                return not isWalking and (150 / rate) or (300 / rate)
            when STEPSOUNDTIME_ON_LADDER
                return 500 / rate
            when STEPSOUNDTIME_WATER_KNEE
                return not isWalking and (250 / rate) or (500 / rate)
            when STEPSOUNDTIME_WATER_FOOT
                return not isWalking and (175 / rate) or (350 / rate)


sylfest: such danned

sylfest: such danned

lpeg = require 'lpeg'
import match, locale, P, R, V, S, Ct, C, Cg, Cf, Cc, Cp from lpeg
import concat from table

-- Helper for searching anywhere in string for patterns
anywhere = (p) ->
  P{ p + 1 * V(1) }
-- Helper for maybe pattern
maybe = (p) -> p^-1
-- Match norwegian words
word = R('az') + R('AZ') + S('æøåÆØÅ')
-- With an '
apo = S "'`"
-- With bad endings
bend = P("ene") + P("en") + P("et") + P("ing") + P("ar") + P("ane") + P("er")
-- Separated by or line end
wend = S(',.? ') + P(-1)
-- Linestart exceptions
bstart = maybe(P(' ')) * S[["'-«]]

-- Capture word with bad endings followed by good word end
sylfestpatt = C(word^1 * apo * bend) * P(wend)
-- Search anywhere in string, capture all sylfestwords, do not match if line starts with excepted linestarts
fullpatt = Ct(anywhere(sylfestpatt)^1 - bstart)

dannedHandler = (s, d, a) =>
  if string.lower(d) == '#tihlde'
    reply "Du henger her i #TIHLDE og tror du er dannet? Det heter «verre», ingen æ."
  if string.lower(d) == '#lart'
    reply "Her på kanalen vert det forventa ørlite språkinnsats, Det heiter «verre», ingen æ."

PRIVMSG:
  -- run our handler on any message that contains '
  ".*['`].*": (source, destination, line) =>
    matches = fullpatt\match(line)
    if matches
      say "Sylfest likar ikkje: %s", concat(matches, ', ')
  'værst': dannedHandler
  'værre': dannedHandler

lpeg = require 'lpeg'
import match, locale, P, R, V, S, Ct, C, Cg, Cf, Cc, Cp from lpeg
import concat from table

-- Helper for searching anywhere in string for patterns
anywhere = (p) ->
  P{ p + 1 * V(1) }
-- Helper for maybe pattern
maybe = (p) -> p^-1
-- Match norwegian words
word = R('az') + R('AZ') + S('æøåÆØÅ')
-- With an '
apo = S "'`"
-- With bad endings
bend = P("ene") + P("en") + P("et") + P("ing") + P("ar") + P("ane") + P("er")
-- Separated by or line end
wend = S(',.? ') + P(-1)
-- Linestart exceptions
bstart = maybe(P(' ')) * S[["'-«]]

-- Capture word with bad endings followed by good word end
sylfestpatt = C(word^1 * apo * bend) * P(wend)
-- Search anywhere in string, capture all sylfestwords, do not match if line starts with excepted linestarts
fullpatt = Ct(anywhere(sylfestpatt)^1 - bstart)

-- run our handler on any message that contains '
PRIVMSG:
  ".*['`].*": (source, destination, line) =>
    matches = fullpatt\match(line)
    if matches
      say "Sylfest likar ikkje: %s", concat(matches, ', ')


trigger: improve list. lay groundwork for future destination source

Former-commit-id: d25f519ccab8ecb22dfd2fd8788dc780c7a12415 [formerly 15ed3a795ca33259608f7ee238fe129f23f2574b]
Former-commit-id: 811343e3f8d2e2e9ac18eee9acc2e9f3187df977

sql = require'lsqlite3'

openDb = ->
  db = sql.open 'cache/trigger.sql'

  db\exec [[
    CREATE TABLE IF NOT EXISTS trigger (
      name UNIQUE ON CONFLICT REPLACE,
      pattern UNIQUE ON CONFLICT REPLACE,
      destination,
      funcstr
    );
    ]]
  return db

triggerHelp  = (source, destination, argument) =>
  help = 'Usage: ¤trigger add <name>|<lua pattern>|<lua code>. Ex: ¤trigger add help|^!help (%w+)|say "%s, you need help. Delete: ¤trigger del <name>. List: ¤trigger list. Show: ¤trigger show <name>"'
  patt = @ChannelCommandPattern('^%p', 'triger', destination)
  help = help\gsub '¤', patt
  @Msg 'privmsg', destination, source, help

-- Construct a safe environ for trigger with two functions; say and reply
--- Preferrably you should be able to cross-call modules here to make aliasing possible
-- TODO: needs a proper sandbox to prevent endless loops, etc
sandbox = (func) ->
  (source, destination, ...) =>
    -- TODO check destination here to decide if it's a local or global trigger
    -- Store args so we can use them in env
    arg = {...}
    env =
      say: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      reply: (str) ->
        @Msg 'privmsg', destination, source, source.nick..': '..str, unpack(arg),
      print: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      :source
      :destination
      simplehttp:require'simplehttp'
      json:require'json'
      :string
      :type
      :tostring
      :tonumber
      :ipars
      :pairs
      :table
      :next

    --setfenv func, setmetatable(env, {__index: _G })
    setfenv func, env
    success, err = pcall func, arg
    if err
      @Msg 'privmsg', destination, source, err
    else
      success

-- Construct a safe function to run and return a handler
triggerHandler = (source, destination, funcstr) =>
  func, err = loadstring funcstr
  unless func
    @Msg 'privmsg', destination, source, 'Trigger error: %s', err
  else
    sandbox(func)

-- Register the command
regCommand = (source, destination, name, pattern, funcstr) =>
  db = openDb!
  ins = db\prepare "INSERT INTO trigger (name, pattern, funcstr, destination) VALUES(?, ?, ?, ?)"
  code = ins\bind_values name, pattern, funcstr, destination
  code = ins\step!
  code = ins\finalize!
  db\close!

  @RegisterCommand 'trigger', pattern, triggerHandler(@, source, destination, funcstr)

delCommand = (source, destination, name) =>
  db = openDb!
  -- Find pattern which is used for deletion of handler
  stmt = db\prepare "SELECT pattern FROM trigger WHERE name = ?"
  code = stmt\bind_values name
  code = stmt\step!
  if code == sqlite3.DONE
    -- Invalid name
    db\close!
    @Msg 'privmsg', destination, source, 'Invalid name'
    return
  pattern = stmt\get_values()[1]

  -- Delete trigger from db
  ins = db\prepare "DELETE FROM trigger WHERE name = ?"
  code = ins\bind_values name
  code = ins\step!
  code = ins\finalize!
  db\close!

  @UnregisterCommand 'triggers', pattern
  @Msg 'privmsg', destination, source, "Trigger #{pattern} deleted"

listTriggers = (source, destination) =>
  db = openDb!
  stmt = db\prepare "SELECT name FROM trigger"
  out = {}
  for row in stmt\nrows! do
    table.insert out, row.name

  db\close!

  if #out > 0
    @Msg 'privmsg', destination, source, "Triggers: "..table.concat(out, ', ')
  else
    @Msg 'privmsg', destination, source, "No triggers defined"

showTrigger = (source, destination, name) =>
  db = openDb!
  stmt = db\prepare "SELECT * FROM trigger WHERE name = ?"
  code = stmt\bind_values name
  for row in stmt\nrows! do
    @Msg 'privmsg', destination, source, "Trigger: \002#{row.name}\002, pattern: #{row.pattern}, code: #{row.funcstr}"
  db\close!

-- Register commands on startup
db = openDb!
for row in db\nrows 'SELECT pattern, funcstr, destination FROM trigger'
  ivar2\RegisterCommand 'triggers', row.pattern, triggerHandler(ivar2, nil, row.destination, row.funcstr)
db\close!

PRIVMSG:
  '^%ptrigger$': triggerHelp
  '^%ptrigger add (.+)|(.+)|(.+)$': regCommand
  '^%ptrigger del (.+)$': delCommand
  '^%ptrigger list$': listTriggers
  '^%ptrigger show (.+)$': showTrigger

sql = require'lsqlite3'

openDb = ->
  db = sql.open 'cache/trigger.sql'

  db\exec [[
    CREATE TABLE IF NOT EXISTS trigger (
      name UNIQUE ON CONFLICT REPLACE,
      pattern UNIQUE ON CONFLICT REPLACE,
      funcstr
    );
    ]]
  return db

triggerHelp  = (source, destination, argument) =>
   @Msg 'privmsg', destination, source, 'Usage: !trigger add <name>|<lua pattern>|<lua code>. Ex: !trigger add help|^!help (%w+)|say "%s, you need help. Delete: !trigger del <name>"'

-- Construct a safe environ for trigger with two functions; say and reply
--- Preferrably you should be able to cross-call modules here to make aliasing possible
sandbox = (func) ->
  (source, destination, ...) =>
    -- Store args so we can use them in env
    arg = {...}
    env =
      say: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      reply: (str) ->
        @Msg 'privmsg', destination, source, source.nick..': '..str, unpack(arg),
      print: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      simplehttp:require'simplehttp'
      json:require'json'
      :string
      :type
      :tostring
      :tonumber
      :ipars
      :pairs
      :table
      :next

    --setfenv func, setmetatable(env, {__index: _G })
    setfenv func, env
    success, err = pcall func, arg
    if err
      @Msg 'privmsg', destination, source, err
    else
      success

-- Construct a safe function to run and return a handler
triggerHandler = (source, destination, funcstr) =>
  func, err = loadstring funcstr
  unless func
    @Msg 'privmsg', destination, source, 'Trigger error: %s', err
  else
    sandbox(func)

-- Register the command
regCommand = (source, destination, name, pattern, funcstr) =>
  db = openDb!
  ins = db\prepare "INSERT INTO trigger (name, pattern, funcstr) VALUES(?, ?, ?)"
  code = ins\bind_values name, pattern, funcstr
  code = ins\step!
  code = ins\finalize!
  db\close!

  @RegisterCommand 'trigger', pattern, triggerHandler(@, source, destination, funcstr)

delCommand = (source, destination, name) =>
  db = openDb!
  -- Find pattern which is used for deletion of handler
  stmt = db\prepare "SELECT pattern FROM trigger WHERE name = ?"
  code = stmt\bind_values name
  code = stmt\step!
  if code == sqlite3.DONE
    -- Invalid name
    db\close!
    @Msg 'privmsg', destination, source, 'Invalid name'
    return
  pattern = stmt\get_values()[1]

  -- Delete trigger from db
  ins = db\prepare "DELETE FROM trigger WHERE name = ?"
  code = ins\bind_values name
  code = ins\step!
  code = ins\finalize!
  db\close!

  @UnregisterCommand 'triggers', pattern
  @Msg 'privmsg', destination, source, "Trigger #{pattern} deleted"

listTriggers = (source, destination) =>
  db = openDb!
  stmt = db\prepare "SELECT name FROM trigger"
  out = {}
  for row in stmt\nrows! do
    table.insert out, row.name

  db\close!

  if #out > 0
    @Msg 'privmsg', destination, source, "Triggers: "..table.concat(out, ', ')

showTrigger = (source, destination, name) =>
  db = openDb!
  stmt = db\prepare "SELECT * FROM trigger WHERE name = ?"
  code = stmt\bind_values name
  for row in stmt\nrows! do
    @Msg 'privmsg', destination, source, "Trigger: \002#{row.name}\002, pattern: #{row.pattern}, code: #{row.funcstr}"
  db\close!

-- Register commands on startup
db = openDb!
for row in db\nrows 'SELECT pattern, funcstr FROM trigger'
  ivar2\RegisterCommand 'triggers', row.pattern, triggerHandler(ivar2, nil, nil, row.funcstr)
db\close!

PRIVMSG:
  '^%ptrigger$': triggerHelp
  '^%ptrigger add (.+)|(.+)|(.+)$': regCommand
  '^%ptrigger del (.+)$': delCommand
  '^%ptrigger list$': listTriggers
  '^%ptrigger show (.+)$': showTrigger


quiz bgcolor: right is green, my is yellow

model = require('model')
quizs = require('quiz.all')

bgcolor = (task, i) ->
  form = task\quiz().state == model.ACTIVE
  bc = 'white'
  if not form and task\ans_i(i) == 1
    bc = 'green'
  if task\ans_i(i) == task.selected
    bc = 'yellow'
  return bc

class Helpers
  ans_form: (task, i) =>
    url = @url_for("answer", {id: task.id})
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input type: "hidden", name: "task", value: task.id
      input type: "hidden", name: "ans", value: i
      input type: "submit", value: @_('Select')

  cancel_form: =>
    url = @url_for("cancel", {id: @quiz.id})
    raw '

'
    element 'hr'
    raw '

'
    font color: 'red', @_("Dangerous zone")
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input {type: "submit", value: @_('Cancel quiz'),
        style: 'background-color: pink'}

  help_for: (task) =>
    quiz_name = task\quiz().name
    func = quizs[quiz_name][task.name]
    r, a1, a2, a3, a4, help = func(@)
    return help

  task_table: (task) =>
    form = task\quiz().state == model.ACTIVE
    element "table", border: 1, ->
      element "tr", ->
        element "td", colspan: 4, ->
          b task.name
          p @help_for(task)
          pre task.text
      element "tr", ->
        for i = 1, 4
          element "td", {width: 100,
              bgcolor: bgcolor(task, i)}, ->
            pre task\ans(i)
            if form
              @ans_form task, i

  tasks_number_switcher: (quiz) =>
    element 'table', -> element 'tr', ->
      element 'td', ->
        url = @url_for('all-tasks', {id: quiz.id})
        form method: 'POST', action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input {type: 'submit',
            value: @_[[All tasks at one page]]}
      element 'td', ->
        url = @url_for('one-task', {id: quiz.id})
        form method: 'POST', action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input {type: 'submit',
            value: @_[[One task at one page]]}
    raw '
'

model = require('model')
quizs = require('quiz.all')

bgcolor = (task, i) ->
  form = task\quiz().state == model.ACTIVE
  bc = 'white'
  if task\ans_i(i) == task.selected
    bc = 'yellow'
  if not form and task\ans_i(i) == task.selected
    bc = 'red'
  if not form and task\ans_i(i) == 1
    bc = 'green'
  return bc

class Helpers
  ans_form: (task, i) =>
    url = @url_for("answer", {id: task.id})
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input type: "hidden", name: "task", value: task.id
      input type: "hidden", name: "ans", value: i
      input type: "submit", value: @_('Select')

  cancel_form: =>
    url = @url_for("cancel", {id: @quiz.id})
    raw '

'
    element 'hr'
    raw '

'
    font color: 'red', @_("Dangerous zone")
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input {type: "submit", value: @_('Cancel quiz'),
        style: 'background-color: pink'}

  help_for: (task) =>
    quiz_name = task\quiz().name
    func = quizs[quiz_name][task.name]
    r, a1, a2, a3, a4, help = func(@)
    return help

  task_table: (task) =>
    form = task\quiz().state == model.ACTIVE
    element "table", border: 1, ->
      element "tr", ->
        element "td", colspan: 4, ->
          b task.name
          p @help_for(task)
          pre task.text
      element "tr", ->
        for i = 1, 4
          element "td", {width: 100,
              bgcolor: bgcolor(task, i)}, ->
            pre task\ans(i)
            if form
              @ans_form task, i

  tasks_number_switcher: (quiz) =>
    element 'table', -> element 'tr', ->
      element 'td', ->
        url = @url_for('all-tasks', {id: quiz.id})
        form method: 'POST', action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input {type: 'submit',
            value: @_[[All tasks at one page]]}
      element 'td', ->
        url = @url_for('one-task', {id: quiz.id})
        form method: 'POST', action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input {type: 'submit',
            value: @_[[One task at one page]]}
    raw '
'


results: color tests with score >= 90% with green

model = require('model')
quizs = require('quiz.all')

bgcolor = (task, i) ->
  form = task\quiz().state == model.ACTIVE
  bc = 'white'
  if not form and task\ans_i(i) == 1
    bc = 'green'
  if task\ans_i(i) == task.selected
    bc = 'yellow'
  return bc

class Helpers
  ans_form: (task, i) =>
    url = @url_for("answer", {id: task.id})
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input type: "hidden", name: "task", value: task.id
      input type: "hidden", name: "ans", value: i
      input type: "submit", value: @_('Select')

  cancel_form: =>
    element 'hr'
    element 'table', -> element 'tr', ->
      element 'td', ->
        url = @url_for("cancel", {id: @quiz.id})
        form method: "POST", action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input {type: "submit", value: @_('Cancel quiz'),
            style: 'background-color: pink'}
      element 'td', ->
        font color: 'red', @_("Dangerous zone")

  help_for: (task) =>
    quiz_name = task\quiz().name
    func = quizs[quiz_name][task.name]
    r, a1, a2, a3, a4, help = func(@)
    return help

  task_table: (task) =>
    form = task\quiz().state == model.ACTIVE
    element "table", border: 1, ->
      element "tr", ->
        element "td", colspan: 4, style: "padding:10px", ->
          if task\quiz().state == model.FINISHED
            b task.name
          p @help_for(task)
          pre task.text
      element "tr", ->
        for i = 1, 4
          element "td", {width: 100, style: "padding:10px",
              bgcolor: bgcolor(task, i)}, ->
            pre task\ans(i)
            if form
              @ans_form task, i

  tasks_number_switcher: (quiz) =>
    element 'table', -> element 'tr', ->
      element 'td', ->
        url = @url_for('all-tasks', {id: quiz.id})
        form method: 'POST', action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input {type: 'submit',
            value: @_[[All tasks at one page]]}
      element 'td', ->
        url = @url_for('one-task', {id: quiz.id})
        form method: 'POST', action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input {type: 'submit',
            value: @_[[One task at one page]]}

  new_test_button: (name) =>
    url = @url_for("new-quiz")
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input {type: "hidden", name: "name", value: name}
      input type: "submit", value: name

  finish_quiz_button: =>
    element 'table', -> element 'tr', ->
      element 'td', ->
        url = @url_for("finish", {id: @quiz.id})
        form method: "POST", action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input type: "submit", value: @_('Finish this quiz')
      element 'td', ->
        font color: 'red', @_([[Quiz is not finished untill
          you press the button]])

  detailed_results: (q, quizs) =>
    h1 @quiz_name
    print_header = ->
      element 'tr', ->
        element 'td' -- id
        element 'td', -> text @_("user")
        element 'td', -> text @_("right answers")
        element 'td', -> text @_("tasks")
        for task_name, func in pairs(q) do
          element 'td', ->
            div class: 'vertical-text', ->
              div class: 'vertical-text__inner', task_name
    element 'table', border: 1, ->
      print_header!
      name2score = {}
      for quiz in *quizs
        color = 'white'
        if quiz.tasks and quiz.right_answers >= quiz.tasks * 0.9
          color = 'green'
        if quiz.tasks and quiz.right_answers < quiz.tasks * 0.9
          color = 'red'
        element 'tr', ->
          element 'td', ->
            a href: @url_for("prep-quiz", {id: quiz.id}), ->
              text '#'
          element 'td', ->
            url = @url_for("prep-quizs-of", {user: quiz.user})
            a href: url, ->
              text quiz.user
          element 'td', bgcolor: color, ->
            text quiz.right_answers
          element 'td', bgcolor: color, ->
            text quiz.tasks
          tasks = quiz\all_tasks!
          name2task = {}
          for task in *tasks
            name2task[task.name] = task
          for task_name, func in pairs(q) do
            task = name2task[task_name]
            color = 'white'
            t = ''
            if task and task.selected == 1
              color = 'green'
              t = '1'
              if not name2score[task_name]
                name2score[task_name] = 0
              name2score[task_name] = name2score[task_name] + 1
            if task and task.selected > 1
              color = 'red'
              t = '0'
            element 'td', bgcolor: color, ->
              text t
      element 'tr', ->
        element 'td' -- id
        element 'td' --, -> text @_("user")
        element 'td' --, -> text @_("right answers")
        element 'td' --, -> text @_("tasks")
        for task_name, func in pairs(q) do
          element 'td', ->
            if name2score[task_name]
              text name2score[task_name]
            else
              text 0
      print_header!

model = require('model')
quizs = require('quiz.all')

bgcolor = (task, i) ->
  form = task\quiz().state == model.ACTIVE
  bc = 'white'
  if not form and task\ans_i(i) == 1
    bc = 'green'
  if task\ans_i(i) == task.selected
    bc = 'yellow'
  return bc

class Helpers
  ans_form: (task, i) =>
    url = @url_for("answer", {id: task.id})
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input type: "hidden", name: "task", value: task.id
      input type: "hidden", name: "ans", value: i
      input type: "submit", value: @_('Select')

  cancel_form: =>
    element 'hr'
    element 'table', -> element 'tr', ->
      element 'td', ->
        url = @url_for("cancel", {id: @quiz.id})
        form method: "POST", action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input {type: "submit", value: @_('Cancel quiz'),
            style: 'background-color: pink'}
      element 'td', ->
        font color: 'red', @_("Dangerous zone")

  help_for: (task) =>
    quiz_name = task\quiz().name
    func = quizs[quiz_name][task.name]
    r, a1, a2, a3, a4, help = func(@)
    return help

  task_table: (task) =>
    form = task\quiz().state == model.ACTIVE
    element "table", border: 1, ->
      element "tr", ->
        element "td", colspan: 4, style: "padding:10px", ->
          if task\quiz().state == model.FINISHED
            b task.name
          p @help_for(task)
          pre task.text
      element "tr", ->
        for i = 1, 4
          element "td", {width: 100, style: "padding:10px",
              bgcolor: bgcolor(task, i)}, ->
            pre task\ans(i)
            if form
              @ans_form task, i

  tasks_number_switcher: (quiz) =>
    element 'table', -> element 'tr', ->
      element 'td', ->
        url = @url_for('all-tasks', {id: quiz.id})
        form method: 'POST', action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input {type: 'submit',
            value: @_[[All tasks at one page]]}
      element 'td', ->
        url = @url_for('one-task', {id: quiz.id})
        form method: 'POST', action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input {type: 'submit',
            value: @_[[One task at one page]]}

  new_test_button: (name) =>
    url = @url_for("new-quiz")
    form method: "POST", action: url, ->
      input {type: "hidden", name: "csrf_token",
          value: @new_csrf}
      input {type: "hidden", name: "name", value: name}
      input type: "submit", value: name

  finish_quiz_button: =>
    element 'table', -> element 'tr', ->
      element 'td', ->
        url = @url_for("finish", {id: @quiz.id})
        form method: "POST", action: url, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input type: "submit", value: @_('Finish this quiz')
      element 'td', ->
        font color: 'red', @_([[Quiz is not finished untill
          you press the button]])

  detailed_results: (q, quizs) =>
    h1 @quiz_name
    print_header = ->
      element 'tr', ->
        element 'td' -- id
        element 'td', -> text @_("user")
        element 'td', -> text @_("right answers")
        element 'td', -> text @_("tasks")
        for task_name, func in pairs(q) do
          element 'td', ->
            div class: 'vertical-text', ->
              div class: 'vertical-text__inner', task_name
    element 'table', border: 1, ->
      print_header!
      name2score = {}
      for quiz in *quizs
        color = 'white'
        if quiz.tasks and quiz.right_answers == quiz.tasks
          color = 'green'
        if quiz.tasks and quiz.right_answers < quiz.tasks
          color = 'red'
        element 'tr', ->
          element 'td', ->
            a href: @url_for("prep-quiz", {id: quiz.id}), ->
              text '#'
          element 'td', ->
            url = @url_for("prep-quizs-of", {user: quiz.user})
            a href: url, ->
              text quiz.user
          element 'td', bgcolor: color, ->
            text quiz.right_answers
          element 'td', bgcolor: color, ->
            text quiz.tasks
          tasks = quiz\all_tasks!
          name2task = {}
          for task in *tasks
            name2task[task.name] = task
          for task_name, func in pairs(q) do
            task = name2task[task_name]
            color = 'white'
            t = ''
            if task and task.selected == 1
              color = 'green'
              t = '1'
              if not name2score[task_name]
                name2score[task_name] = 0
              name2score[task_name] = name2score[task_name] + 1
            if task and task.selected > 1
              color = 'red'
              t = '0'
            element 'td', bgcolor: color, ->
              text t
      element 'tr', ->
        element 'td' -- id
        element 'td' --, -> text @_("user")
        element 'td' --, -> text @_("right answers")
        element 'td' --, -> text @_("tasks")
        for task_name, func in pairs(q) do
          element 'td', ->
            if name2score[task_name]
              text name2score[task_name]
            else
              text 0
      print_header!


translationsssss

translationsssss

-- Copyright (C) 2017-2018 DBot

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

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

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

-- editor stuffs

gui.ppm2.editor.eyes.separate = 'Getrennte Einstellungen für Augen verwenden'
gui.ppm2.editor.eyes.url = 'Augen URL Textur'
gui.ppm2.editor.eyes.url_desc = 'Bei Verwendung der Augen URL Textur; Die folgenden Optionen haben keine Auswirkung'

gui.ppm2.editor.eyes.lightwarp_desc = 'Lightwarp wirkt nur auf EyeRefract Augen'
gui.ppm2.editor.eyes.lightwarp = "Lightwarp"
gui.ppm2.editor.eyes.desc1 = "Lightwarp texture URL input\nIt must be 256x16!"
gui.ppm2.editor.eyes.desc2 = "Glossiness strength\nThis parameters adjucts strength of real time reflections on eye\nTo see changes, set ppm2_cl_reflections convar to 1\nOther players would see reflections only with ppm2_cl_reflections set to 1\n0 - is matted; 1 - is mirror"

for {tprefix, prefix} in *{{'def', ''}, {'left', 'Links '}, {'right', 'Rechts '}}
	gui.ppm2.editor.eyes[tprefix].lightwarp.shader = "#{prefix}Use EyeRefract shader"
	gui.ppm2.editor.eyes[tprefix].lightwarp.cornera = "#{prefix}Use Eye Cornera diffuse"
	gui.ppm2.editor.eyes[tprefix].lightwarp.glossiness = "#{prefix}Glossiness"

	gui.ppm2.editor.eyes[tprefix].type = "#{prefix}Eye type"
	gui.ppm2.editor.eyes[tprefix].lines = "#{prefix}Eye lines"
	gui.ppm2.editor.eyes[tprefix].derp = "#{prefix}Derp eye"
	gui.ppm2.editor.eyes[tprefix].derp_strength = "#{prefix}Derp eye strength"
	gui.ppm2.editor.eyes[tprefix].iris_size = "#{prefix}Eye size"

	gui.ppm2.editor.eyes[tprefix].points_inside = "#{prefix}Eye lines points inside"
	gui.ppm2.editor.eyes[tprefix].width = "#{prefix}Eye width"
	gui.ppm2.editor.eyes[tprefix].height = "#{prefix}Eye height"

	gui.ppm2.editor.eyes[tprefix].pupil.width = "#{prefix}Pupil width"
	gui.ppm2.editor.eyes[tprefix].pupil.height = "#{prefix}Pupil height"
	gui.ppm2.editor.eyes[tprefix].pupil.size = "#{prefix}Pupil size"

	gui.ppm2.editor.eyes[tprefix].pupil.shift_x = "#{prefix}Pupil Shift X"
	gui.ppm2.editor.eyes[tprefix].pupil.shift_y = "#{prefix}Pupil Shift Y"
	gui.ppm2.editor.eyes[tprefix].pupil.rotation = "#{prefix}Eye rotation"

	gui.ppm2.editor.eyes[tprefix].background = "#{prefix}Eye background"
	gui.ppm2.editor.eyes[tprefix].pupil_size = "#{prefix}Pupil"
	gui.ppm2.editor.eyes[tprefix].top_iris = "#{prefix}Top eye iris"
	gui.ppm2.editor.eyes[tprefix].bottom_iris = "#{prefix}Bottom eye iris"
	gui.ppm2.editor.eyes[tprefix].line1 = "#{prefix}Eye line 1"
	gui.ppm2.editor.eyes[tprefix].line2 = "#{prefix}Eye line 2"
	gui.ppm2.editor.eyes[tprefix].reflection = "#{prefix}Eye reflection effect"
	gui.ppm2.editor.eyes[tprefix].effect = "#{prefix}Eye effect"

gui.ppm2.editor.generic.title = 'PPM/2 Pony Editor'
gui.ppm2.editor.generic.title_file = '%q - PPM/2 Pony Editor'
gui.ppm2.editor.generic.title_file_unsaved = '%q* - PPM/2 Pony Editor (unsaved changes!)'

gui.ppm2.editor.generic.yes = 'Yas!'
gui.ppm2.editor.generic.no = 'Noh!'
gui.ppm2.editor.generic.ohno = 'Onoh!'
gui.ppm2.editor.generic.okay = 'Okai ;w;'
gui.ppm2.editor.generic.datavalue = '%s\nData value: %q'
gui.ppm2.editor.generic.url = '%s\n\nLink goes to: %s'
gui.ppm2.editor.generic.url_field = 'URL Field'
gui.ppm2.editor.generic.spoiler = 'Mysterious spoiler'

gui.ppm2.editor.generic.restart.needed = 'Editor restart required'
gui.ppm2.editor.generic.restart.text = 'You should restart editor for applying change.\nRestart now?\nUnsaved data will lost!'
gui.ppm2.editor.generic.fullbright = 'Fullbright'
gui.ppm2.editor.generic.wtf = 'For some reason, your player has no NetworkedPonyData - Nothing to edit!\nTry ppm2_reload in your console and try to open editor again'

gui.ppm2.editor.io.random.title = 'Randomize!'
gui.ppm2.editor.io.newfile.title = 'New File'
gui.ppm2.editor.io.newfile.confirm = 'Really want to create a new file?'
gui.ppm2.editor.io.newfile.toptext = 'Reset'
gui.ppm2.editor.io.delete.confirm = 'Do you really want to delete that file?\nIt will be gone forever!\n(a long time!)'
gui.ppm2.editor.io.delete.title = 'Really Delete?'

gui.ppm2.editor.io.filename = 'Filename'
gui.ppm2.editor.io.hint = 'Open file by double click'
gui.ppm2.editor.io.reload = 'Reload file list'
gui.ppm2.editor.io.failed = 'Failed to import.'

gui.ppm2.editor.io.warn.oldfile = '!!! It may or may not work. You will be squished.'
gui.ppm2.editor.io.warn.text = "Currently, you did not stated your changes.\nDo you really want to open another file?"
gui.ppm2.editor.io.warn.header = 'Unsaved changes!'
gui.ppm2.editor.io.save.button = 'Save'
gui.ppm2.editor.io.save.text = 'Enter file name without ppm2/ and .dat\nTip: to save as autoload, type "_current" (without ")'
gui.ppm2.editor.io.wear = 'Apply changes (wear)'

gui.ppm2.editor.seq.standing = 'Standing'
gui.ppm2.editor.seq.move = 'Moving'
gui.ppm2.editor.seq.walk = 'Walking'
gui.ppm2.editor.seq.sit = 'Sit'
gui.ppm2.editor.seq.swim = 'Swim'
gui.ppm2.editor.seq.run = 'Run'
gui.ppm2.editor.seq.duckwalk = 'Crouch walk'
gui.ppm2.editor.seq.duck = 'Crouch'
gui.ppm2.editor.seq.jump = 'Jump'

gui.ppm2.editor.misc.race = 'Race'
gui.ppm2.editor.misc.weight = 'Weight'
gui.ppm2.editor.misc.size = 'Pony Size'
gui.ppm2.editor.misc.hide_weapons = 'Should hide weapons'
gui.ppm2.editor.misc.chest = 'Male chest buff'
gui.ppm2.editor.misc.gender = 'Gender'
gui.ppm2.editor.misc.wings = 'Wings Type'
gui.ppm2.editor.misc.flexes = 'Flexes controls'
gui.ppm2.editor.misc.no_flexes2 = 'No flexes on new model'
gui.ppm2.editor.misc.no_flexes_desc = 'You can disable separately any flex state controller\nSo these flexes can be modified with third-party addons (like PAC3)'

gui.ppm2.editor.misc.hide_pac3 = 'Hide entitites when using PAC3 entity'
gui.ppm2.editor.misc.hide_mane = 'Hide mane when using PAC3 entity'
gui.ppm2.editor.misc.hide_tail = 'Hide tail when using PAC3 entity'
gui.ppm2.editor.misc.hide_socks = 'Hide socks when using PAC3 entity'

gui.ppm2.editor.tabs.main = 'General'
gui.ppm2.editor.tabs.files = 'Files'
gui.ppm2.editor.tabs.old_files = 'Old Files'
gui.ppm2.editor.tabs.cutiemark = 'Cutiemark'
gui.ppm2.editor.tabs.head = 'Head anatomy'
gui.ppm2.editor.tabs.eyes = 'Eyes'
gui.ppm2.editor.tabs.face = 'Face'
gui.ppm2.editor.tabs.mouth = 'Mouth'
gui.ppm2.editor.tabs.left_eye = 'Left Eye'
gui.ppm2.editor.tabs.right_eye = 'Right Eye'
gui.ppm2.editor.tabs.mane_horn = 'Mane and Horn'
gui.ppm2.editor.tabs.mane = 'Mane'
gui.ppm2.editor.tabs.details = 'Details'
gui.ppm2.editor.tabs.url_details = 'URL Details'
gui.ppm2.editor.tabs.url_separated_details = 'URL Separated Details'
gui.ppm2.editor.tabs.ears = 'Ears'
gui.ppm2.editor.tabs.horn = 'Ears'
gui.ppm2.editor.tabs.back = 'Back'
gui.ppm2.editor.tabs.wings = 'Wings'
gui.ppm2.editor.tabs.left = 'Left'
gui.ppm2.editor.tabs.right = 'Right'
gui.ppm2.editor.tabs.neck = 'Neck'
gui.ppm2.editor.tabs.body = 'Pony body'
gui.ppm2.editor.tabs.tattoos = 'Tattoos'
gui.ppm2.editor.tabs.tail = 'Tail'
gui.ppm2.editor.tabs.hooves = 'Hooves anatomy'
gui.ppm2.editor.tabs.bottom_hoof = 'Bottom hoof'
gui.ppm2.editor.tabs.legs = 'Legs'
gui.ppm2.editor.tabs.socks = 'Legs'
gui.ppm2.editor.tabs.newsocks = 'New Legs'

gui.ppm2.editor.cutiemark.display = 'Display cutiemark'
gui.ppm2.editor.cutiemark.type = 'Cutiemark type'
gui.ppm2.editor.cutiemark.size = 'Cutiemark size'
gui.ppm2.editor.cutiemark.color = 'Cutiemark color'
gui.ppm2.editor.cutiemark.input = 'Cutiemark URL image input field\nShould be PNG or JPEG (works same as\nPAC3 URL texture)'

gui.ppm2.editor.face.eyelashes = 'Eyelashes'
gui.ppm2.editor.face.eyelashes_color = 'Eyelashes Color'
gui.ppm2.editor.face.eyebrows_color = 'Eyebrows Color'
gui.ppm2.editor.face.new_muzzle = 'Use new muzzle for male model'

gui.ppm2.editor.face.nose = 'Nose Color'
gui.ppm2.editor.face.lips = 'Lips Color'
gui.ppm2.editor.face.eyelashes_separate_phong = 'Separate Eyelashes Phong'
gui.ppm2.editor.face.eyebrows_glow = 'Glowing eyebrows'
gui.ppm2.editor.face.eyebrows_glow_strength = 'Eyebrows glow strength'
gui.ppm2.editor.face.inherit.lips = 'Inherit Lips Color from body'
gui.ppm2.editor.face.inherit.nose = 'Inherit Nose Color from body'

gui.ppm2.editor.mouth.fangs = 'Fangs'
gui.ppm2.editor.mouth.alt_fangs = 'Alternative Fangs'
gui.ppm2.editor.mouth.claw = 'Claw teeth'

gui.ppm2.editor.mouth.teeth = 'Teeth color'
gui.ppm2.editor.mouth.mouth = 'Mouth color'
gui.ppm2.editor.mouth.tongue = 'Tongue color'

gui.ppm2.editor.mane.type = 'Mane Type'
gui.ppm2.editor.mane.phong = 'Separate mane phong settings from body'
gui.ppm2.editor.mane.phong_sep = 'Separate upper and lower mane colors'
gui.ppm2.editor.mane.up.phong = 'Upper Mane Phong Settings'
gui.ppm2.editor.mane.down.phong = 'Lower Mane Phong Settings'
gui.ppm2.editor.mane.newnotice = 'Next options have effect only on new model'

for i = 1, 2
	gui.ppm2.editor.mane['color' .. i] = "Mane color #{i}"
	gui.ppm2.editor.mane.up['color' .. i] = "Upper mane color #{i}"
	gui.ppm2.editor.mane.down['color' .. i] = "Lower mane color #{i}"

for i = 1, 6
	gui.ppm2.editor.mane['detail_color' .. i] = "Mane detail color #{i}"
	gui.ppm2.editor.mane.up['detail_color' .. i] = "Upper mane color #{i}"
	gui.ppm2.editor.mane.down['detail_color' .. i] = "Lower mane color #{i}"

	gui.ppm2.editor.url_mane['desc' .. i] = "Mane URL Detail #{i} input field"
	gui.ppm2.editor.url_mane['color' .. i] = "Mane URL detail color #{i}"

	gui.ppm2.editor.url_mane.sep.up['desc' .. i] = "Upper mane URL Detail #{i} input field"
	gui.ppm2.editor.url_mane.sep.up['color' .. i] = "Upper Mane URL detail color #{i}"

	gui.ppm2.editor.url_mane.sep.down['desc' .. i] = "Lower mane URL Detail #{i} input field"
	gui.ppm2.editor.url_mane.sep.down['color' .. i] = "Lower Mane URL detail color #{i}"

gui.ppm2.editor.ears.bat = 'Bat pony ears'
gui.ppm2.editor.ears.size = 'Ears size'

gui.ppm2.editor.horn.detail_color = 'Horn detail color'
gui.ppm2.editor.horn.glowing_detail = 'Glowing Horn Detail'
gui.ppm2.editor.horn.glow_strength = 'Horn Glow Strength'
gui.ppm2.editor.horn.separate_color = 'Separate horn color from body'
gui.ppm2.editor.horn.color = 'Horn color'
gui.ppm2.editor.horn.magic = 'Horn magic color'
gui.ppm2.editor.horn.separate_magic_color = 'Separate magic color from eye color'
gui.ppm2.editor.horn.separate = 'Separate horn color from body'
gui.ppm2.editor.horn.separate_phong = 'Separate horn phong settings from body'

for i = 1, 3
	gui.ppm2.editor.horn.detail['desc' .. i] = "Horn URL detail #{i}"
	gui.ppm2.editor.horn.detail['color' .. i] = "URL Detail color #{i}"

gui.ppm2.editor.wings.separate_color = 'Separate wings color from body'
gui.ppm2.editor.wings.color = 'Wings color'
gui.ppm2.editor.wings.separate = 'Separate wings color from body'
gui.ppm2.editor.wings.separate_phong = 'Separate wings phong settings from body'
gui.ppm2.editor.wings.bat_color = 'Bat Wings color'
gui.ppm2.editor.wings.bat_skin_color = 'Bat Wings skin color'

gui.ppm2.editor.wings.normal = 'Normal wings'
gui.ppm2.editor.wings.bat = 'Bat wings'
gui.ppm2.editor.wings.bat_skin = 'Bat wings skin'

gui.ppm2.editor.wings.left.size = 'Left wing size'
gui.ppm2.editor.wings.left.fwd = 'Left Wing Forward'
gui.ppm2.editor.wings.left.up = 'Left Wing Up'
gui.ppm2.editor.wings.left.inside = 'Left Wing Inside'

gui.ppm2.editor.wings.right.size = 'Right wing size'
gui.ppm2.editor.wings.right.fwd = 'Right Wing Forward'
gui.ppm2.editor.wings.right.up = 'Right Wing Up'
gui.ppm2.editor.wings.right.inside = 'Right Wing Inside'

for i = 1, 3
	gui.ppm2.editor.wings.details.def['detail' .. i] = "Wings URL detail #{i}"
	gui.ppm2.editor.wings.details.def['color' .. i] = "URL Detail color #{i}"
	gui.ppm2.editor.wings.details.bat['detail' .. i] = "Bat wing URL detail #{i}"
	gui.ppm2.editor.wings.details.bat['color' .. i] = "Bat wing URL Detail color #{i}"
	gui.ppm2.editor.wings.details.batskin['detail' .. i] = "Bat wing skin URL detail #{i}"
	gui.ppm2.editor.wings.details.batskin['color' .. i] = "Bat wing skin URL Detail color #{i}"

gui.ppm2.editor.neck.height = 'Neck height'

gui.ppm2.editor.body.suit = 'Bodysuit'
gui.ppm2.editor.body.color = 'Body color'
gui.ppm2.editor.body.spine_length = 'Spine length'
gui.ppm2.editor.body.url_desc = 'Body detail URL image input fields\nShould be PNG or JPEG (works same as\nPAC3 URL texture)'

for i = 1, PPM2.MAX_BODY_DETAILS
	gui.ppm2.editor.body.detail['desc' .. i] = "Detail #{i}"
	gui.ppm2.editor.body.detail['color' .. i] = "Detail color #{i}"
	gui.ppm2.editor.body.detail['glow' .. i] = "Detail #{i} is glowing"
	gui.ppm2.editor.body.detail['glow_strength' .. i] = "Detail #{i} glow strength"

	gui.ppm2.editor.body.detail.url['desc' .. i] = "Detail #{i}"
	gui.ppm2.editor.body.detail.url['color' .. i] = "Detail color #{i}"

gui.ppm2.editor.tattoo.edit_keyboard = 'Edit using keyboard'
gui.ppm2.editor.tattoo.type = 'Type'
gui.ppm2.editor.tattoo.over = 'Tattoo over body details'
gui.ppm2.editor.tattoo.glow = 'Tattoo is glowing'
gui.ppm2.editor.tattoo.glow_strength = 'Tattoo glow strength'
gui.ppm2.editor.tattoo.color = 'Color of Tattoo'

gui.ppm2.editor.tattoo.tweak.rotate = 'Rotation'
gui.ppm2.editor.tattoo.tweak.x = 'X Position'
gui.ppm2.editor.tattoo.tweak.y = 'Y Position'
gui.ppm2.editor.tattoo.tweak.width = 'Width Scale'
gui.ppm2.editor.tattoo.tweak.height = 'Height Scale'

for i = 1, PPM2.MAX_TATTOOS
	gui.ppm2.editor.tattoo['layer' .. i] = "Tattoo layer #{i}"

gui.ppm2.editor.tail.type = 'Tail type'
gui.ppm2.editor.tail.size = 'Tail size'
gui.ppm2.editor.tail.separate = 'Separate tail phong settings from body'

for i = 1, 2
	gui.ppm2.editor.tail['color' .. i] = 'Tail color ' .. i

for i = 1, 6
	gui.ppm2.editor.tail['detail' .. i] = "Tail detail color #{i}"
	gui.ppm2.editor.tail.url['detail' .. i] = "Tail URL detail #{i}"
	gui.ppm2.editor.tail.url['color' .. i] = "Tail URL detail #{i}"

gui.ppm2.editor.hoof.fluffers = 'Hoof Fluffers'

gui.ppm2.editor.legs.height = 'Legs height'
gui.ppm2.editor.legs.socks.simple = 'Socks (simple texture)'
gui.ppm2.editor.legs.socks.model = 'Socks (as model)'
gui.ppm2.editor.legs.socks.color = 'Socks model color'
gui.ppm2.editor.legs.socks.texture = 'Socks Texture'
gui.ppm2.editor.legs.socks.url_texture = 'Socks URL texture'

for i = 1, 6
	gui.ppm2.editor.legs.socks['color' .. i] = 'Socks detail color ' .. i

gui.ppm2.editor.legs.newsocks.model = 'Socks (as new model)'

for i = 1, 3
	gui.ppm2.editor.legs.newsocks['color' .. i] = 'New Socks color ' .. i

gui.ppm2.editor.legs.newsocks.url = 'New Socks URL texture'

-- shared editor stuffs

gui.ppm2.editor.tattoo.help = "To exit edit mode, press Escape or click anywhere with mouse
To move tatto use WASD
To Scale higher/lower use Up/Down arrows
To Scale wider/smaller use Right/Left arrows
To rotate left/right use Q/E"

for name, data in pairs PPM2.PonyDataRegistry
	gui.ppm2.editor.reset[data.getFunc\lower()] = 'Reset ' .. data.getFunc

gui.ppm2.editor.phong.info = 'More info about Phong on wiki'
gui.ppm2.editor.phong.exponent = 'Phong Exponent - how strong reflective property\nof pony skin is\nSet near zero to get robotic looking of your\npony skin'
gui.ppm2.editor.phong.exponent_text = 'Phong Exponent'
gui.ppm2.editor.phong.boost.title = 'Phong Boost - multiplies specular map reflections'
gui.ppm2.editor.phong.boost.boost = 'Phong Boost'
gui.ppm2.editor.phong.tint.title = 'Tint color - what colors does reflect specular map\nWhite - Reflects all colors\n(In white room - white specular map)'
gui.ppm2.editor.phong.tint.tint = 'Tint color - what colors does reflect specular map\nWhite - Reflects all colors\n(In white room - white specular map)'
gui.ppm2.editor.phong.frensel.front.title = 'Phong Front - Fresnel 0 degree reflection angle multiplier'
gui.ppm2.editor.phong.frensel.front.front = 'Phong Front'
gui.ppm2.editor.phong.frensel.middle.title = 'Phong Middle - Fresnel 45 degree reflection angle multiplier'
gui.ppm2.editor.phong.frensel.middle.front = 'Phong Middle'
gui.ppm2.editor.phong.frensel.sliding.title = 'Phong Sliding - Fresnel 45 degree reflection angle multiplier'
gui.ppm2.editor.phong.frensel.sliding.front = 'Phong Sliding'
gui.ppm2.editor.phong.lightwarp = 'Lightwarp'
gui.ppm2.editor.phong.url_lightwarp = 'Lightwarp texture URL input\nIt must be 256x16!'
gui.ppm2.editor.phong.bumpmap = 'Bumpmap URL input'

gui.ppm2.editor.info.discord = "Join DBot's Discord!"
gui.ppm2.editor.info.ponyscape = "PPM/2 is a Ponyscape project"
gui.ppm2.editor.info.creator = "PPM/2 were created and being developed by DBot"
gui.ppm2.editor.info.newmodels = "New models were created by Durpy"
gui.ppm2.editor.info.cppmmodels = "CPPM Models (including pony hands) belong to UnkN', 'http://steamcommunity.com/profiles/76561198084938735"
gui.ppm2.editor.info.oldmodels = "Old models belong to Scentus and others"
gui.ppm2.editor.info.bugs = "Found a bug? Report here!"
gui.ppm2.editor.info.sources = "You can find sources here"
gui.ppm2.editor.info.githubsources = "Or on GitHub mirror"
gui.ppm2.editor.info.thanks = "Special thanks to everypony who criticized,\nhelped and tested PPM/2!"

-- other stuff

info.ppm2.fly.pegasus = 'You need to be a Pegasus or an Alicorn to fly!'
info.ppm2.fly.cannot = 'You can not %s right now.'

gui.ppm2.emotes.sad = 'Sad'
gui.ppm2.emotes.wild = 'Wild'
gui.ppm2.emotes.grin = 'Grin'
gui.ppm2.emotes.angry = 'Angry'
gui.ppm2.emotes.tongue = ':P'
gui.ppm2.emotes.angrytongue = '>:P'
gui.ppm2.emotes.pff = 'Pffff!'
gui.ppm2.emotes.kitty = ':3'
gui.ppm2.emotes.owo = 'oWo'
gui.ppm2.emotes.ugh = 'Uuugh'
gui.ppm2.emotes.lips = 'Lips lick'
gui.ppm2.emotes.scrunch = 'Scrunch'
gui.ppm2.emotes.sorry = 'Sorry'
gui.ppm2.emotes.wink = 'Wink'
gui.ppm2.emotes.right_wink = 'Right Wink'
gui.ppm2.emotes.licking = 'Licking'
gui.ppm2.emotes.suggestive_lips = 'Siggestive Lips lick'
gui.ppm2.emotes.suggestive_no_tongue = 'Siggestive w/o tongue'
gui.ppm2.emotes.gulp = 'Gulp'
gui.ppm2.emotes.blah = 'Blah blah blah'
gui.ppm2.emotes.happi = 'Happi'
gui.ppm2.emotes.happi_grin = 'Happi grin'
gui.ppm2.emotes.duck = 'DUCK'
gui.ppm2.emotes.ducks = 'DUCK INSANITY'
gui.ppm2.emotes.quack = 'QUACKS'
gui.ppm2.emotes.suggestive = 'Soggestive w/ tongue'

message.ppm2.emotes.invalid = 'No such emotion with ID: %s'

-- Copyright (C) 2017-2018 DBot

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

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

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

-- editor stuffs

gui.ppm2.editor.eyes.separate = 'Use separated settings for eyes'
gui.ppm2.editor.eyes.url = 'Eye URL texture'
gui.ppm2.editor.eyes.url_desc = 'When uring eye URL texture; options below have no effect'

gui.ppm2.editor.eyes.lightwarp_desc = 'Lightwarp has effect only on EyeRefract eyes'
gui.ppm2.editor.eyes.lightwarp = "Lightwarp"
gui.ppm2.editor.eyes.desc1 = "Lightwarp texture URL input\nIt must be 256x16!"
gui.ppm2.editor.eyes.desc2 = "Glossiness strength\nThis parameters adjucts strength of real time reflections on eye\nTo see changes, set ppm2_cl_reflections convar to 1\nOther players would see reflections only with ppm2_cl_reflections set to 1\n0 - is matted; 1 - is mirror"

for {tprefix, prefix} in *{{'def', ''}, {'left', 'Left '}, {'right', 'Right '}}
	gui.ppm2.editor.eyes[tprefix].lightwarp.shader = "#{prefix}Use EyeRefract shader"
	gui.ppm2.editor.eyes[tprefix].lightwarp.cornera = "#{prefix}Use Eye Cornera diffuse"
	gui.ppm2.editor.eyes[tprefix].lightwarp.glossiness = "#{prefix}Glossiness"

	gui.ppm2.editor.eyes[tprefix].type = "#{prefix}Eye type"
	gui.ppm2.editor.eyes[tprefix].lines = "#{prefix}Eye lines"
	gui.ppm2.editor.eyes[tprefix].derp = "#{prefix}Derp eye"
	gui.ppm2.editor.eyes[tprefix].derp_strength = "#{prefix}Derp eye strength"
	gui.ppm2.editor.eyes[tprefix].iris_size = "#{prefix}Eye size"

	gui.ppm2.editor.eyes[tprefix].points_inside = "#{prefix}Eye lines points inside"
	gui.ppm2.editor.eyes[tprefix].width = "#{prefix}Eye width"
	gui.ppm2.editor.eyes[tprefix].height = "#{prefix}Eye height"

	gui.ppm2.editor.eyes[tprefix].pupil.width = "#{prefix}Pupil width"
	gui.ppm2.editor.eyes[tprefix].pupil.height = "#{prefix}Pupil height"
	gui.ppm2.editor.eyes[tprefix].pupil.size = "#{prefix}Pupil size"

	gui.ppm2.editor.eyes[tprefix].pupil.shift_x = "#{prefix}Pupil Shift X"
	gui.ppm2.editor.eyes[tprefix].pupil.shift_y = "#{prefix}Pupil Shift Y"
	gui.ppm2.editor.eyes[tprefix].pupil.rotation = "#{prefix}Eye rotation"

	gui.ppm2.editor.eyes[tprefix].background = "#{prefix}Eye background"
	gui.ppm2.editor.eyes[tprefix].pupil_size = "#{prefix}Pupil"
	gui.ppm2.editor.eyes[tprefix].top_iris = "#{prefix}Top eye iris"
	gui.ppm2.editor.eyes[tprefix].bottom_iris = "#{prefix}Bottom eye iris"
	gui.ppm2.editor.eyes[tprefix].line1 = "#{prefix}Eye line 1"
	gui.ppm2.editor.eyes[tprefix].line2 = "#{prefix}Eye line 2"
	gui.ppm2.editor.eyes[tprefix].reflection = "#{prefix}Eye reflection effect"
	gui.ppm2.editor.eyes[tprefix].effect = "#{prefix}Eye effect"

gui.ppm2.editor.generic.title = 'PPM/2 Pony Editor'
gui.ppm2.editor.generic.title_file = '%q - PPM/2 Pony Editor'
gui.ppm2.editor.generic.title_file_unsaved = '%q* - PPM/2 Pony Editor (unsaved changes!)'

gui.ppm2.editor.generic.yes = 'Yas!'
gui.ppm2.editor.generic.no = 'Noh!'
gui.ppm2.editor.generic.ohno = 'Onoh!'
gui.ppm2.editor.generic.okay = 'Okai ;w;'
gui.ppm2.editor.generic.datavalue = '%s\nData value: %q'
gui.ppm2.editor.generic.url = '%s\n\nLink goes to: %s'
gui.ppm2.editor.generic.url_field = 'URL Field'
gui.ppm2.editor.generic.spoiler = 'Mysterious spoiler'

gui.ppm2.editor.generic.restart.needed = 'Editor restart required'
gui.ppm2.editor.generic.restart.text = 'You should restart editor for applying change.\nRestart now?\nUnsaved data will lost!'
gui.ppm2.editor.generic.fullbright = 'Fullbright'
gui.ppm2.editor.generic.wtf = 'For some reason, your player has no NetworkedPonyData - Nothing to edit!\nTry ppm2_reload in your console and try to open editor again'

gui.ppm2.editor.io.random.title = 'Randomize!'
gui.ppm2.editor.io.newfile.title = 'New File'
gui.ppm2.editor.io.newfile.confirm = 'Really want to create a new file?'
gui.ppm2.editor.io.newfile.toptext = 'Reset'
gui.ppm2.editor.io.delete.confirm = 'Do you really want to delete that file?\nIt will be gone forever!\n(a long time!)'
gui.ppm2.editor.io.delete.title = 'Really Delete?'

gui.ppm2.editor.io.filename = 'Filename'
gui.ppm2.editor.io.hint = 'Open file by double click'
gui.ppm2.editor.io.reload = 'Reload file list'
gui.ppm2.editor.io.failed = 'Failed to import.'

gui.ppm2.editor.io.warn.oldfile = '!!! It may or may not work. You will be squished.'
gui.ppm2.editor.io.warn.text = "Currently, you did not stated your changes.\nDo you really want to open another file?"
gui.ppm2.editor.io.warn.header = 'Unsaved changes!'
gui.ppm2.editor.io.save.button = 'Save'
gui.ppm2.editor.io.save.text = 'Enter file name without ppm2/ and .dat\nTip: to save as autoload, type "_current" (without ")'
gui.ppm2.editor.io.wear = 'Apply changes (wear)'

gui.ppm2.editor.seq.standing = 'Standing'
gui.ppm2.editor.seq.move = 'Moving'
gui.ppm2.editor.seq.walk = 'Walking'
gui.ppm2.editor.seq.sit = 'Sit'
gui.ppm2.editor.seq.swim = 'Swim'
gui.ppm2.editor.seq.run = 'Run'
gui.ppm2.editor.seq.duckwalk = 'Crouch walk'
gui.ppm2.editor.seq.duck = 'Crouch'
gui.ppm2.editor.seq.jump = 'Jump'

gui.ppm2.editor.misc.race = 'Race'
gui.ppm2.editor.misc.weight = 'Weight'
gui.ppm2.editor.misc.size = 'Pony Size'
gui.ppm2.editor.misc.hide_weapons = 'Should hide weapons'
gui.ppm2.editor.misc.chest = 'Male chest buff'
gui.ppm2.editor.misc.gender = 'Gender'
gui.ppm2.editor.misc.wings = 'Wings Type'
gui.ppm2.editor.misc.flexes = 'Flexes controls'
gui.ppm2.editor.misc.no_flexes2 = 'No flexes on new model'
gui.ppm2.editor.misc.no_flexes_desc = 'You can disable separately any flex state controller\nSo these flexes can be modified with third-party addons (like PAC3)'

gui.ppm2.editor.misc.hide_pac3 = 'Hide entitites when using PAC3 entity'
gui.ppm2.editor.misc.hide_mane = 'Hide mane when using PAC3 entity'
gui.ppm2.editor.misc.hide_tail = 'Hide tail when using PAC3 entity'
gui.ppm2.editor.misc.hide_socks = 'Hide socks when using PAC3 entity'

gui.ppm2.editor.tabs.main = 'General'
gui.ppm2.editor.tabs.files = 'Files'
gui.ppm2.editor.tabs.old_files = 'Old Files'
gui.ppm2.editor.tabs.cutiemark = 'Cutiemark'
gui.ppm2.editor.tabs.head = 'Head anatomy'
gui.ppm2.editor.tabs.eyes = 'Eyes'
gui.ppm2.editor.tabs.face = 'Face'
gui.ppm2.editor.tabs.mouth = 'Mouth'
gui.ppm2.editor.tabs.left_eye = 'Left Eye'
gui.ppm2.editor.tabs.right_eye = 'Right Eye'
gui.ppm2.editor.tabs.mane_horn = 'Mane and Horn'
gui.ppm2.editor.tabs.mane = 'Mane'
gui.ppm2.editor.tabs.details = 'Details'
gui.ppm2.editor.tabs.url_details = 'URL Details'
gui.ppm2.editor.tabs.url_separated_details = 'URL Separated Details'
gui.ppm2.editor.tabs.ears = 'Ears'
gui.ppm2.editor.tabs.horn = 'Ears'
gui.ppm2.editor.tabs.back = 'Back'
gui.ppm2.editor.tabs.wings = 'Wings'
gui.ppm2.editor.tabs.left = 'Left'
gui.ppm2.editor.tabs.right = 'Right'
gui.ppm2.editor.tabs.neck = 'Neck'
gui.ppm2.editor.tabs.body = 'Pony body'
gui.ppm2.editor.tabs.tattoos = 'Tattoos'
gui.ppm2.editor.tabs.tail = 'Tail'
gui.ppm2.editor.tabs.hooves = 'Hooves anatomy'
gui.ppm2.editor.tabs.bottom_hoof = 'Bottom hoof'
gui.ppm2.editor.tabs.legs = 'Legs'
gui.ppm2.editor.tabs.socks = 'Legs'
gui.ppm2.editor.tabs.newsocks = 'New Legs'

gui.ppm2.editor.cutiemark.display = 'Display cutiemark'
gui.ppm2.editor.cutiemark.type = 'Cutiemark type'
gui.ppm2.editor.cutiemark.size = 'Cutiemark size'
gui.ppm2.editor.cutiemark.color = 'Cutiemark color'
gui.ppm2.editor.cutiemark.input = 'Cutiemark URL image input field\nShould be PNG or JPEG (works same as\nPAC3 URL texture)'

gui.ppm2.editor.face.eyelashes = 'Eyelashes'
gui.ppm2.editor.face.eyelashes_color = 'Eyelashes Color'
gui.ppm2.editor.face.eyebrows_color = 'Eyebrows Color'
gui.ppm2.editor.face.new_muzzle = 'Use new muzzle for male model'

gui.ppm2.editor.face.nose = 'Nose Color'
gui.ppm2.editor.face.lips = 'Lips Color'
gui.ppm2.editor.face.eyelashes_separate_phong = 'Separate Eyelashes Phong'
gui.ppm2.editor.face.eyebrows_glow = 'Glowing eyebrows'
gui.ppm2.editor.face.eyebrows_glow_strength = 'Eyebrows glow strength'
gui.ppm2.editor.face.inherit.lips = 'Inherit Lips Color from body'
gui.ppm2.editor.face.inherit.nose = 'Inherit Nose Color from body'

gui.ppm2.editor.mouth.fangs = 'Fangs'
gui.ppm2.editor.mouth.alt_fangs = 'Alternative Fangs'
gui.ppm2.editor.mouth.claw = 'Claw teeth'

gui.ppm2.editor.mouth.teeth = 'Teeth color'
gui.ppm2.editor.mouth.mouth = 'Mouth color'
gui.ppm2.editor.mouth.tongue = 'Tongue color'

gui.ppm2.editor.mane.type = 'Mane Type'
gui.ppm2.editor.mane.phong = 'Separate mane phong settings from body'
gui.ppm2.editor.mane.phong_sep = 'Separate upper and lower mane colors'
gui.ppm2.editor.mane.up.phong = 'Upper Mane Phong Settings'
gui.ppm2.editor.mane.down.phong = 'Lower Mane Phong Settings'
gui.ppm2.editor.mane.newnotice = 'Next options have effect only on new model'

for i = 1, 2
	gui.ppm2.editor.mane['color' .. i] = "Mane color #{i}"
	gui.ppm2.editor.mane.up['color' .. i] = "Upper mane color #{i}"
	gui.ppm2.editor.mane.down['color' .. i] = "Lower mane color #{i}"

for i = 1, 6
	gui.ppm2.editor.mane['detail_color' .. i] = "Mane detail color #{i}"
	gui.ppm2.editor.mane.up['detail_color' .. i] = "Upper mane color #{i}"
	gui.ppm2.editor.mane.down['detail_color' .. i] = "Lower mane color #{i}"

	gui.ppm2.editor.url_mane['desc' .. i] = "Mane URL Detail #{i} input field"
	gui.ppm2.editor.url_mane['color' .. i] = "Mane URL detail color #{i}"

	gui.ppm2.editor.url_mane.sep.up['desc' .. i] = "Upper mane URL Detail #{i} input field"
	gui.ppm2.editor.url_mane.sep.up['color' .. i] = "Upper Mane URL detail color #{i}"

	gui.ppm2.editor.url_mane.sep.down['desc' .. i] = "Lower mane URL Detail #{i} input field"
	gui.ppm2.editor.url_mane.sep.down['color' .. i] = "Lower Mane URL detail color #{i}"

gui.ppm2.editor.ears.bat = 'Bat pony ears'
gui.ppm2.editor.ears.size = 'Ears size'

gui.ppm2.editor.horn.detail_color = 'Horn detail color'
gui.ppm2.editor.horn.glowing_detail = 'Glowing Horn Detail'
gui.ppm2.editor.horn.glow_strength = 'Horn Glow Strength'
gui.ppm2.editor.horn.separate_color = 'Separate horn color from body'
gui.ppm2.editor.horn.color = 'Horn color'
gui.ppm2.editor.horn.magic = 'Horn magic color'
gui.ppm2.editor.horn.separate_magic_color = 'Separate magic color from eye color'
gui.ppm2.editor.horn.separate = 'Separate horn color from body'
gui.ppm2.editor.horn.separate_phong = 'Separate horn phong settings from body'

for i = 1, 3
	gui.ppm2.editor.horn.detail['desc' .. i] = "Horn URL detail #{i}"
	gui.ppm2.editor.horn.detail['color' .. i] = "URL Detail color #{i}"

gui.ppm2.editor.wings.separate_color = 'Separate wings color from body'
gui.ppm2.editor.wings.color = 'Wings color'
gui.ppm2.editor.wings.separate = 'Separate wings color from body'
gui.ppm2.editor.wings.separate_phong = 'Separate wings phong settings from body'
gui.ppm2.editor.wings.bat_color = 'Bat Wings color'
gui.ppm2.editor.wings.bat_skin_color = 'Bat Wings skin color'

gui.ppm2.editor.wings.normal = 'Normal wings'
gui.ppm2.editor.wings.bat = 'Bat wings'
gui.ppm2.editor.wings.bat_skin = 'Bat wings skin'

gui.ppm2.editor.wings.left.size = 'Left wing size'
gui.ppm2.editor.wings.left.fwd = 'Left Wing Forward'
gui.ppm2.editor.wings.left.up = 'Left Wing Up'
gui.ppm2.editor.wings.left.inside = 'Left Wing Inside'

gui.ppm2.editor.wings.right.size = 'Right wing size'
gui.ppm2.editor.wings.right.fwd = 'Right Wing Forward'
gui.ppm2.editor.wings.right.up = 'Right Wing Up'
gui.ppm2.editor.wings.right.inside = 'Right Wing Inside'

for i = 1, 3
	gui.ppm2.editor.wings.details.def['detail' .. i] = "Wings URL detail #{i}"
	gui.ppm2.editor.wings.details.def['color' .. i] = "URL Detail color #{i}"
	gui.ppm2.editor.wings.details.bat['detail' .. i] = "Bat wing URL detail #{i}"
	gui.ppm2.editor.wings.details.bat['color' .. i] = "Bat wing URL Detail color #{i}"
	gui.ppm2.editor.wings.details.batskin['detail' .. i] = "Bat wing skin URL detail #{i}"
	gui.ppm2.editor.wings.details.batskin['color' .. i] = "Bat wing skin URL Detail color #{i}"

gui.ppm2.editor.neck.height = 'Neck height'

gui.ppm2.editor.body.suit = 'Bodysuit'
gui.ppm2.editor.body.color = 'Body color'
gui.ppm2.editor.body.spine_length = 'Spine length'
gui.ppm2.editor.body.url_desc = 'Body detail URL image input fields\nShould be PNG or JPEG (works same as\nPAC3 URL texture)'

for i = 1, PPM2.MAX_BODY_DETAILS
	gui.ppm2.editor.body.detail['desc' .. i] = "Detail #{i}"
	gui.ppm2.editor.body.detail['color' .. i] = "Detail color #{i}"
	gui.ppm2.editor.body.detail['glow' .. i] = "Detail #{i} is glowing"
	gui.ppm2.editor.body.detail['glow_strength' .. i] = "Detail #{i} glow strength"

	gui.ppm2.editor.body.detail.url['desc' .. i] = "Detail #{i}"
	gui.ppm2.editor.body.detail.url['color' .. i] = "Detail color #{i}"

gui.ppm2.editor.tattoo.edit_keyboard = 'Edit using keyboard'
gui.ppm2.editor.tattoo.type = 'Type'
gui.ppm2.editor.tattoo.over = 'Tattoo over body details'
gui.ppm2.editor.tattoo.glow = 'Tattoo is glowing'
gui.ppm2.editor.tattoo.glow_strength = 'Tattoo glow strength'
gui.ppm2.editor.tattoo.color = 'Color of Tattoo'

gui.ppm2.editor.tattoo.tweak.rotate = 'Rotation'
gui.ppm2.editor.tattoo.tweak.x = 'X Position'
gui.ppm2.editor.tattoo.tweak.y = 'Y Position'
gui.ppm2.editor.tattoo.tweak.width = 'Width Scale'
gui.ppm2.editor.tattoo.tweak.height = 'Height Scale'

for i = 1, PPM2.MAX_TATTOOS
	gui.ppm2.editor.tattoo['layer' .. i] = "Tattoo layer #{i}"

gui.ppm2.editor.tail.type = 'Tail type'
gui.ppm2.editor.tail.size = 'Tail size'
gui.ppm2.editor.tail.separate = 'Separate tail phong settings from body'

for i = 1, 2
	gui.ppm2.editor.tail['color' .. i] = 'Tail color ' .. i

for i = 1, 6
	gui.ppm2.editor.tail['detail' .. i] = "Tail detail color #{i}"
	gui.ppm2.editor.tail.url['detail' .. i] = "Tail URL detail #{i}"
	gui.ppm2.editor.tail.url['color' .. i] = "Tail URL detail #{i}"

gui.ppm2.editor.hoof.fluffers = 'Hoof Fluffers'

gui.ppm2.editor.legs.height = 'Legs height'
gui.ppm2.editor.legs.socks.simple = 'Socks (simple texture)'
gui.ppm2.editor.legs.socks.model = 'Socks (as model)'
gui.ppm2.editor.legs.socks.color = 'Socks model color'
gui.ppm2.editor.legs.socks.texture = 'Socks Texture'
gui.ppm2.editor.legs.socks.url_texture = 'Socks URL texture'

for i = 1, 6
	gui.ppm2.editor.legs.socks['color' .. i] = 'Socks detail color ' .. i

gui.ppm2.editor.legs.newsocks.model = 'Socks (as new model)'

for i = 1, 3
	gui.ppm2.editor.legs.newsocks['color' .. i] = 'New Socks color ' .. i

gui.ppm2.editor.legs.newsocks.url = 'New Socks URL texture'

-- shared editor stuffs

gui.ppm2.editor.tattoo.help = "To exit edit mode, press Escape or click anywhere with mouse
To move tatto use WASD
To Scale higher/lower use Up/Down arrows
To Scale wider/smaller use Right/Left arrows
To rotate left/right use Q/E"

for name, data in pairs PPM2.PonyDataRegistry
	gui.ppm2.editor.reset[data.getFunc\lower()] = 'Reset ' .. data.getFunc

gui.ppm2.editor.phong.info = 'More info about Phong on wiki'
gui.ppm2.editor.phong.exponent = 'Phong Exponent - how strong reflective property\nof pony skin is\nSet near zero to get robotic looking of your\npony skin'
gui.ppm2.editor.phong.exponent_text = 'Phong Exponent'
gui.ppm2.editor.phong.boost.title = 'Phong Boost - multiplies specular map reflections'
gui.ppm2.editor.phong.boost.boost = 'Phong Boost'
gui.ppm2.editor.phong.tint.title = 'Tint color - what colors does reflect specular map\nWhite - Reflects all colors\n(In white room - white specular map)'
gui.ppm2.editor.phong.tint.tint = 'Tint color - what colors does reflect specular map\nWhite - Reflects all colors\n(In white room - white specular map)'
gui.ppm2.editor.phong.frensel.front.title = 'Phong Front - Fresnel 0 degree reflection angle multiplier'
gui.ppm2.editor.phong.frensel.front.front = 'Phong Front'
gui.ppm2.editor.phong.frensel.middle.title = 'Phong Middle - Fresnel 45 degree reflection angle multiplier'
gui.ppm2.editor.phong.frensel.middle.front = 'Phong Middle'
gui.ppm2.editor.phong.frensel.sliding.title = 'Phong Sliding - Fresnel 45 degree reflection angle multiplier'
gui.ppm2.editor.phong.frensel.sliding.front = 'Phong Sliding'
gui.ppm2.editor.phong.lightwarp = 'Lightwarp'
gui.ppm2.editor.phong.url_lightwarp = 'Lightwarp texture URL input\nIt must be 256x16!'
gui.ppm2.editor.phong.bumpmap = 'Bumpmap URL input'

gui.ppm2.editor.info.discord = "Join DBot's Discord!"
gui.ppm2.editor.info.ponyscape = "PPM/2 is a Ponyscape project"
gui.ppm2.editor.info.creator = "PPM/2 were created and being developed by DBot"
gui.ppm2.editor.info.newmodels = "New models were created by Durpy"
gui.ppm2.editor.info.cppmmodels = "CPPM Models (including pony hands) belong to UnkN', 'http://steamcommunity.com/profiles/76561198084938735"
gui.ppm2.editor.info.oldmodels = "Old models belong to Scentus and others"
gui.ppm2.editor.info.bugs = "Found a bug? Report here!"
gui.ppm2.editor.info.sources = "You can find sources here"
gui.ppm2.editor.info.githubsources = "Or on GitHub mirror"
gui.ppm2.editor.info.thanks = "Special thanks to everypony who criticized,\nhelped and tested PPM/2!"

-- other stuff

info.ppm2.fly.pegasus = 'You need to be a Pegasus or an Alicorn to fly!'
info.ppm2.fly.cannot = 'You can not %s right now.'

gui.ppm2.emotes.sad = 'Sad'
gui.ppm2.emotes.wild = 'Wild'
gui.ppm2.emotes.grin = 'Grin'
gui.ppm2.emotes.angry = 'Angry'
gui.ppm2.emotes.tongue = ':P'
gui.ppm2.emotes.angrytongue = '>:P'
gui.ppm2.emotes.pff = 'Pffff!'
gui.ppm2.emotes.kitty = ':3'
gui.ppm2.emotes.owo = 'oWo'
gui.ppm2.emotes.ugh = 'Uuugh'
gui.ppm2.emotes.lips = 'Lips lick'
gui.ppm2.emotes.scrunch = 'Scrunch'
gui.ppm2.emotes.sorry = 'Sorry'
gui.ppm2.emotes.wink = 'Wink'
gui.ppm2.emotes.right_wink = 'Right Wink'
gui.ppm2.emotes.licking = 'Licking'
gui.ppm2.emotes.suggestive_lips = 'Siggestive Lips lick'
gui.ppm2.emotes.suggestive_no_tongue = 'Siggestive w/o tongue'
gui.ppm2.emotes.gulp = 'Gulp'
gui.ppm2.emotes.blah = 'Blah blah blah'
gui.ppm2.emotes.happi = 'Happi'
gui.ppm2.emotes.happi_grin = 'Happi grin'
gui.ppm2.emotes.duck = 'DUCK'
gui.ppm2.emotes.ducks = 'DUCK INSANITY'
gui.ppm2.emotes.quack = 'QUACKS'
gui.ppm2.emotes.suggestive = 'Soggestive w/ tongue'

message.ppm2.emotes.invalid = 'No such emotion with ID: %s'


trailing whitespace

trailing whitespace

import Widget from require "lapis.html"
class Album extends Widget
    content: =>
      div class:"album-label", ->
        a href:@albumsurl, title:"Back to album list", ->
          i class:"fa fa-home"
          text " | "
        a href:@albumurl, ->
          i class:"fa fa-camera-retro"
          text " " ..@album.title
      div class:"items",->
        for index, image in pairs @images
          div class:"item", ->
            a href:image\get_huge_url!, id:image.token, class:"lb", ->
              img src:image\get_thumb_url!, title:"#{image.file_name} - #{image.views} views"
              div class:"thumb-label", ->
                p image.file_name, ->
                  span class:"pull-right", "#{image.views } views"

      div id:"spinner", class:"spinner", ->
        text "Loading ..."
      script ->
        raw [[
        $(document).on("ready", function (evt) {
            pnx = photongx($('.items'), $('.item'));
        });
        ]]