weatherunderground: fix mojibake

weatherunderground: fix mojibake

Former-commit-id: 53ad13ed58464f5c0bddbcf8bb14c83fe3dc8a51 [formerly 949677365bc09b381b6400bfeb7fa552f2f5e1ca]
Former-commit-id: 7f006f062f7ca52bd15b378329f4d2a0f5831f3d

-- Weather Underground API module, requires a key set in configuration
-- WeatherUndergroundKey

util = require'util'
os = require'os'
iconv = require'iconv'

iso2utf = iconv.new('latin1', 'utf-8')

key = ivar2.config.WeatherUndergroundKey
unless key
  return {}

geoApiBase = 'http://api.geonames.org/searchJSON?name=%s&featureClass=P&username=haste'
wugeolookupurl = 'http://api.wunderground.com/api/%s/geolookup/q/59.788,5.7218.json'
language = 'NO'

tempColor = (v) ->
  v = tonumber(v)
  if v > 0
    color = util.red
    return color(v)
  else
    color = util.lightblue
    return color(v)

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

findLocation = (source, destination, name, cb) =>
  gurl = geoApiBase\format(util.urlEncode(name))
  util.simplehttp gurl, (data) ->
    json = util.json.decode(data)
    if json.totalResultsCount == 0
      return @Msg('privmsg', destination, source, "Does that place even exist?")
    city = json.geonames[1]
    lat = city.lat
    lon = city.lng
    cb(lon, lat)

lookupConditions = (source, destination, input, pws) =>
  findLocation @, source, destination, input, (lon, lat) ->
    pws = pws or false
    if pws
      pws = 'pws:1/'
    else
      pws = 'pws:0/'
    url = "http://api.wunderground.com/api/#{key}/lang:#{language}/#{pws}conditions/q/#{lat},#{lon}.json"
    @Log 'debug', "Fetching WU URL: %s", url
    util.simplehttp url, (data) ->
      json = util.json.decode(data)
      if json.error ~= nil and json.error.description ~=nil
        return say json.error.description
      current = json.current_observation

      location = current.observation_location
      city = iso2utf\iconv location.city

      weather = current.weather
      temp = current.temp_c
      feelsLike = current.feelslike_c

      windSpeedname = windSpeedToName(current.wind_kph/3.6)

      windDirection = current.wind_dir

      rain = current.precip_today_metric

      time = os.date('%H:%M', tonumber(current.observation_epoch))

      @Msg 'privmsg', destination, source, '%s, %s °C (følt %s °C), %s %s, %s mm, (%s, %s)', weather, tempColor(temp), tempColor(feelsLike), windDirection, windSpeedname, rain, city, time

lookupConditionsPWS = (source, destination, input) =>
  lookupConditions(@, source, destination, input, true)

PRIVMSG:
  '^%pwu (.+)$': lookupConditions
  '^%ppws (.+)$': lookupConditionsPWS

-- Weather Underground API module, requires a key set in configuration
-- WeatherUndergroundKey

util = require'util'
os = require'os'

key = ivar2.config.WeatherUndergroundKey
unless key
  return {}

geoApiBase = 'http://api.geonames.org/searchJSON?name=%s&featureClass=P&username=haste'
wugeolookupurl = 'http://api.wunderground.com/api/%s/geolookup/q/59.788,5.7218.json'
language = 'NO'

tempColor = (v) ->
  v = tonumber(v)
  if v > 0
    color = util.red
    return color(v)
  else
    color = util.lightblue
    return color(v)

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

findLocation = (source, destination, name, cb) =>
  gurl = geoApiBase\format(util.urlEncode(name))
  util.simplehttp gurl, (data) ->
    json = util.json.decode(data)
    if json.totalResultsCount == 0
      return @Msg('privmsg', destination, source, "Does that place even exist?")
    city = json.geonames[1]
    lat = city.lat
    lon = city.lng
    cb(lon, lat)

lookupConditions = (source, destination, input, pws) =>
  findLocation @, source, destination, input, (lon, lat) ->
    pws = pws or false
    if pws
      pws = 'pws:1/'
    else
      pws = 'pws:0/'
    url = "http://api.wunderground.com/api/#{key}/lang:#{language}/#{pws}conditions/q/#{lat},#{lon}.json"
    @Log 'debug', "Fetching WU URL: %s", url
    util.simplehttp url, (data) ->
      json = util.json.decode(data)
      if json.error ~= nil and json.error.description ~=nil
        return say json.error.description
      current = json.current_observation

      location = current.observation_location
      city = location.city

      weather = current.weather
      temp = current.temp_c
      feelsLike = current.feelslike_c

      windSpeedname = windSpeedToName(current.wind_kph/3.6)

      windDirection = current.wind_dir

      rain = current.precip_today_metric

      time = os.date('%H:%M', tonumber(current.observation_epoch))

      @Msg 'privmsg', destination, source, '%s, %s °C (følt %s °C), %s %s, %s mm, (%s, %s)', weather, tempColor(temp), tempColor(feelsLike), windDirection, windSpeedname, rain, city, time

lookupConditionsPWS = (source, destination, input) =>
  lookupConditions(@, source, destination, input, true)

PRIVMSG:
  '^%pwu (.+)$': lookupConditions
  '^%ppws (.+)$': lookupConditionsPWS


utf8: fix syntax

utf8: fix syntax

Former-commit-id: f24e2e1cac3b9e2d7005351db0dfb612f361f04f [formerly 2727c741b737ea178f0378dc261b0f3fccfb2121]
Former-commit-id: 4c3e3269d2922233fea68ccdbf31536ee8bd277f

html2unicode = require'html'

replace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0x1D4A3, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0x246F, arg)

html2unicode = require'html'

replace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

PRIVMSG:
  '^%pwide (.+)$': wide = (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0x1D4A3, arg)
  '^%pcircled (.+)$': (destination, source, arg) =>
    @Msg 'privmsg', destination, source, replace(0x246F, arg)


utf8: fix and add circle

utf8: fix and add circle

html2unicode = require'html'

replace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

PRIVMSG:
  '^%pwide (.+)$': wide = (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0x1D4A3, arg)
  '^%pcircled (.+)$': (destination, source, arg) =>
    @Msg 'privmsg', destination, source, replace(0x246F, arg)

html2unicode = require'html'

replace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      if bc == 32
        t[#t + 1] = '\227\128\128'
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

wide = (source, destination, arg) =>
  @Msg 'privmsg', destination, source, replace(0xFEE0, arg)

blackletter = (source, destination, arg) =>
  @Msg 'privmsg', destination, source, replace(0x1D4A3, arg)

PRIVMSG:
  '^%pwide (.+)$': wide
  '^%pblackletter (.+)$': blackletter


Bomb manager

Bomb manager

-- lovelog = require "lib.lovelog"
-- vector = require "hump.vector"
-- colorize = require "lib.colorize"
-- signal = require "hump.signal"
--config = require "config"
HC = require "HCWorld"

BombManager =
  size: 0
  last: 0
  bombs: {}
  addBomb: (b) =>
    @size += 1
    @bombs[b] = true

  removeBomb: (b) =>
    @bullets[b] = nil
    @size -= 1

  update: (dt) =>
    for b, _ in pairs @bullets
      b\update dt

  draw: () =>
    -- lovelog.print "Bomb count: " .. @size
    for b, _ in pairs @bullets
      b\draw!

  removeAllBullets: =>
    for b, _ in pairs(@bullets)
      b\remove!

class Bullet
  new: (args) =>
    @pos = args.pos
    @rad = 30
    @char = args.char or "*"
    @hitbox = HC\circle(@pos.x, @pos.y, @rad)

  update: (dt) =>

  draw: (dt) =>

  {
    :BombManager
    :Bullet
  }

-- lovelog = require "lib.lovelog"
-- vector = require "hump.vector"
-- colorize = require "lib.colorize"
-- signal = require "hump.signal"
--config = require "config"
HC = require "HCWorld"

class Bullet
  new: (args) =>
    @pos = args.pos
    @rad = 30
    @char = args.char or "*"
    @hitbox = HC\circle(@pos.x, @pos.y, @rad)

  update: (dt) =>

  draw: (dt) =>

  {
    :Bullet
  }


alot: Only match once per line.

alot: Only match once per line.

Former-commit-id: b083fee975c2815763858345f88d19cd79e61d1d [formerly 77ef6f4d7eedec4dee6402c2a8e357e4f12057a1]
Former-commit-id: 7abd79b5e43dff5c54251d81915068b180cc7fc7

alot = (source, destination) =>
  say 'http://hyperboleandahalf.blogspot.no/2010/04/alot-is-better-than-you-at-everything.html'
PRIVMSG:
  '.*[aA][lL][oO][tT].*': alot

alot = (source, destination) =>
  say 'http://hyperboleandahalf.blogspot.no/2010/04/alot-is-better-than-you-at-everything.html'
PRIVMSG:
  ' [aA][lL][oO][tT] ': alot
  '^[aA][lL][oO][tT] ': alot
  ' [aA][lL][oO][tT]$': alot
  '[aA][lL][oO][tT]%p': alot
  '%p[aA][lL][oO][tT]%p': alot
  '^[aA][lL][oO][tT]$': alot


list: Get correct pattern

list: Get correct pattern

Former-commit-id: 974e085deffb7aad22b469312c8c5595932ff5d6 [formerly 186df15f6e08c1ed115dda0c88d3995291b3876f]
Former-commit-id: 0e6f6abd8352a2ff4d6ca23d93b28ae7d002c4bd

listModules = (source, destination, arg) =>
  out = {}
  for moduleName, moduleTable in next, @Events!['PRIVMSG']
    unless @IsModuleDisabled moduleName, destination
      table.insert out, moduleName
  if #out > 0
    @Msg 'privmsg', destination, source, "Modules: %s", table.concat(out, ', ')

listPatterns = (source, destination, moduleName) =>
  moduleTable = @Events!['PRIVMSG'][moduleName]
  unless moduleTable
    return

  if @IsModuleDisabled moduleName, destination
    return

  out = {}
  for pattern, callback in next, moduleTable
    patt = @ChannelCommandPattern(pattern, moduleName, destination)
    table.insert out, patt

  if #out > 0
    @Msg 'privmsg', destination, source, "%s patterns: %s", moduleName, table.concat(out, ', ')

PRIVMSG:
  '^%plist$': listModules
  '^%plist (%w+)$': listPatterns

listModules = (source, destination, arg) =>
  out = {}
  for moduleName, moduleTable in next, @Events!['PRIVMSG']
    unless @IsModuleDisabled moduleName, destination
      table.insert out, moduleName
  if #out > 0
    @Msg 'privmsg', destination, source, "Modules: %s", table.concat(out, ', ')

listPatterns = (source, destination, moduleName) =>
  moduleTable = @Events!['PRIVMSG'][moduleName]
  unless moduleTable
    return

  if @IsModuleDisabled moduleName, destination
    return

  out = {}
  for pattern, callback in next, moduleTable
    table.insert out, pattern

  if #out > 0
    @Msg 'privmsg', destination, source, "%s patterns: %s", moduleName, table.concat(out, ', ')

PRIVMSG:
  '%plist$': listModules
  '%plist (%w+)$': listPatterns


Update omdb.moon

Update omdb.moon

added colouring for imdb

Former-commit-id: 29bd50f5965d083ce9e5f4485560d3ab19f4605d [formerly fb8c1b27bfc6a1f1398c508d198aadf940ebab3a]
Former-commit-id: 9b16773d19c1afc6b5d6fd5b8f4f3ee7b5de78b8

util = require 'util'

rtcolor = (score) ->
  if not tonumber(score) then return score
  if tonumber(score) > 59
    return util.red(string.format("%s%%", score))
  else
    return util.green(string.format("%s%%", score))

metacolor = (score) ->
  if not tonumber(score) then return score
  if tonumber(score) >= 60
    return util.green(string.format("%s%%", score))
  elseif tonumber(score) >= 40
    return util.yellow(string.format("%s%%", score))
  else
    return util.red(string.format("%s%%", score))

imdbcolor = (score) ->
  if not tonumber(score) then return score
  if tonumber(score) >= 8.0
    return util.green(string.format("%.1f", score))
  elseif tonumber(score) >= 6.0
    return util.yellow(string.format("%.1f", score))
  elseif tonumber(score) >= 4.0
    return util.orange(string.format("%.1f", score))
  else
    return util.red(string.format("%.1f", score))

omdbfetch = (arg, cb) ->
  util.simplehttp "http://www.omdbapi.com/?t=#{util.urlEncode arg}&y=&plot=short&r=json&tomatoes=true", (data) ->
    js = util.json.decode(data)
    if js and not js.Error
      cb(js)
    else
      say('%s', js.Error)

omdb = (source, destination, arg) =>
  omdbfetch arg, (js) ->
    say "[#{util.bold js.Title}] (#{js.Year}) #{js.Genre} Metacritic: [#{metacolor js.Metascore}] RT: [#{rtcolor js.tomatoMeter} / #{rtcolor js.tomatoUserMeter}] IMDB: [#{imdbcolor js.imdbRating}] http://www.imdb.com/title/#{js.imdbID} Actors: [#{js.Actors}] #{js.Plot}"

plot = (source, destination, arg) =>
  omdbfetch arg, (js) ->
    say "[#{util.bold js.Title}] (#{js.Year}) #{js.Plot}"

PRIVMSG:
  '^%pomdb (.+)': omdb
  '^%pimdb (.+)': omdb
  '^%prt (.+)': omdb
  '^%pmovie (.+)': omdb
  '^%pplot (.+)': plot

util = require 'util'

rtcolor = (score) ->
  if not tonumber(score) then return score
  if tonumber(score) > 59
    return util.red(string.format("%s%%", score))
  else
    return util.green(string.format("%s%%", score))

metacolor = (score) ->
  if not tonumber(score) then return score
  if tonumber(score) >= 60
    return util.green(string.format("%s%%", score))
  elseif tonumber(score) >= 40
    return util.yellow(string.format("%s%%", score))
  else
    return util.red(string.format("%s%%", score))

omdbfetch = (arg, cb) ->
  util.simplehttp "http://www.omdbapi.com/?t=#{util.urlEncode arg}&y=&plot=short&r=json&tomatoes=true", (data) ->
    js = util.json.decode(data)
    if js and not js.Error
      cb(js)
    else
      say('%s', js.Error)

omdb = (source, destination, arg) =>
  omdbfetch arg, (js) ->
    say "[#{util.bold js.Title}] (#{js.Year}) #{js.Genre} Metacritic: [#{metacolor js.Metascore}] RT: [#{rtcolor js.tomatoMeter} / #{rtcolor js.tomatoUserMeter}] IMDB: [#{js.imdbRating}] http://www.imdb.com/title/#{js.imdbID} Actors: [#{js.Actors}] #{js.Plot}"

plot = (source, destination, arg) =>
  omdbfetch arg, (js) ->
    say "[#{util.bold js.Title}] (#{js.Year}) #{js.Plot}"

PRIVMSG:
  '^%pomdb (.+)': omdb
  '^%pimdb (.+)': omdb
  '^%prt (.+)': omdb
  '^%pmovie (.+)': omdb
  '^%pplot (.+)': plot


Added editor icon

Added editor icon

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

MODEL_BOX_PANEL = {
    SEQUENCE_STAND: 22
    SEQUENCE_FORWARD: 316
    SEQUENCE_WALK: 232
    PONY_VEC_Z: 64 * .7

    EDITOR_SEQUENCES: {
        -- idle
        {
            time: 5
            func: (pos, ang, delta) -> pos, ang
        }
    }

    Init: =>
        @animRate = 1
        @seq = @SEQUENCE_STAND
        @targetAngle = Angle(0, 0, 0)
        @angle = Angle(0, 0, 0)
        @distToPony = 100
        @targetDistToPony = 100
        @vectorPos = Vector(@distToPony, 0, @PONY_VEC_Z)
        @hold = false
        @holdLast = 0
        @mouseX, @mouseY = 0, 0
        @SetMouseInputEnabled(true)
        @editorSeq = 1
        @nextSeq = @EDITOR_SEQUENCES[@editorSeq].time + RealTime()
        @playing = true
        @lastTick = RealTime()
        @SetCursor('none')

    OnMousePressed: (code = MOUSE_LEFT) =>
        return if code ~= MOUSE_LEFT
        @hold = true
        @SetCursor('sizeall')
        @holdLast = RealTime() + .1
        @oldPlaying = @playing
        @playing = false
        @mouseX, @mouseY = gui.MousePos()
    OnMouseReleased: (code = MOUSE_LEFT) =>
        return if code ~= MOUSE_LEFT
        @hold = false
        @SetCursor('none')
        if @holdLast > RealTime()
            @playing = true
            if not @oldPlaying
                @editorSeq = 1

    SetController: (val) => @controller = val

    OnMouseWheeled: (wheelDelta = 0) =>
        @playing = false
        @editorSeq = 1
        @targetDistToPony = math.Clamp(@targetDistToPony - wheelDelta * 10, 20, 150)
    GetModel: => @model
    GetSequence: => @seq
    GetSeq: => @seq
    GetAnimRate: => @animRate
    SetAnimRate: (val = 1) => @animRate = val
    SetSeq: (val = @SEQUENCE_STAND) =>
        @seq = val
        @model\SetSequence(@seq) if IsValid(@model)
    SetSequence: (val = @SEQUENCE_STAND) =>
        @seq = val
        @model\SetSequence(@seq) if IsValid(@model)
    ResetSequence: => @SetSequence(@SEQUENCE_STAND)
    ResetSeq: => @SetSequence(@SEQUENCE_STAND)

    ResetModel: (ponydata, model = 'models/ppm/player_default_base.mdl') =>
        @model\Remove() if IsValid(@model)
        @model = ClientsideModel(model)
        with @model
            \SetNoDraw(true)
            .__PPM2_PonyData = ponydata
        @model\SetSequence(@seq)
        @model\FrameAdvance(0)
        return @model
    Think: =>
        rtime = RealTime()
        delta = rtime - @lastTick
        @lastTick = rtime
        if IsValid(@model)
            @model\FrameAdvance(delta * @animRate)

        @hold = @IsHovered() if @hold

        if @playing
            cseq = @EDITOR_SEQUENCES[@editorSeq]
            if @nextSeq < rtime
                @editorSeq += 1
                @editorSeq = 1 if not @EDITOR_SEQUENCES[@editorSeq]
                cseq = @EDITOR_SEQUENCES[@editorSeq]
                @nextSeq = rtime + cseq.time
            @targetDistToPony, @targetAngle = cseq.func(@targetDistToPony, @targetAngle, delta)
        else
            if @hold
                x, y = gui.MousePos()
                deltaX, deltaY = x - @mouseX, y - @mouseY
                @mouseX, @mouseY = x, y
                {:pitch, :yaw, :roll} = @targetAngle
                yaw -= deltaX * .5
                pitch = math.Clamp(pitch - deltaY * .5, -40, 40)
                @targetAngle = Angle(pitch, yaw, roll)

        @angle = LerpAngle(delta * 4, @angle, @targetAngle)
        @distToPony = Lerp(delta * 4, @distToPony, @targetDistToPony)
        @vectorPos = Vector(@distToPony, 0, @PONY_VEC_Z)
        @vectorPos\Rotate(@angle)
        @drawAngle = (Vector(0, 0, @PONY_VEC_Z) - @vectorPos)\Angle()
    Paint: (w = 0, h = 0) =>
        surface.SetDrawColor(0, 0, 0)
        surface.DrawRect(0, 0, w, h)
        return if not IsValid(@model)
        x, y = @LocalToScreen(0, 0)
        cam.Start3D(@vectorPos, @drawAngle, 90, x, y, w, h)
        @controller\GetRenderController()\PreDraw(@model) if @controller
        @model\DrawModel()
        @controller\GetRenderController()\PostDraw(@model) if @controller
        cam.End3D()
}

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

PANEL_SETTINGS_BASE = {
    Init: =>
        @shouldSaveData = false
        @SetMouseInputEnabled(true)
        @SetKeyboardInputEnabled(true)
        @DockPadding(5, 5, 5, 5)
        @unsavedChanges = false
    ValueChanges: (valID, newVal, pnl) =>
        @unsavedChanges = true
        @frame\SetTitle("#{@GetTargetData() and @GetTargetData()\GetFilename() or '%ERRNAME%'} - PPM2 Pony Editor; *Unsaved changes*")
    GetShouldSaveData: => @shouldSaveData
    ShouldSaveData: => @shouldSaveData
    SetShouldSaveData: (val = false) => @shouldSaveData = val
    GetTargetData: => @data
    TargetData: => @data
    SetTargetData: (val) => @data = val
    NumSlider: (name = 'Slider', option = '', decimals = 0) =>
		with vgui.Create('DNumSlider', @)
			\Dock(TOP)
			\DockMargin(2, 0, 2, 0)
			\SetTooltip("#{name}\nData value: #{option}")
			\SetText(name)
			\SetMin(0)
			\SetMax(1)
			\SetMin(@GetTargetData()["GetMin#{option}"](@GetTargetData())) if @GetTargetData()
			\SetMax(@GetTargetData()["GetMax#{option}"](@GetTargetData())) if @GetTargetData()
			\SetDecimals(decimals)
            \SetValue(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
			.TextArea\SetTextColor(color_white)
			.Label\SetTextColor(color_white)
            .OnValueChanged = (pnl, newVal = 1) ->
                return if option == ''
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, newVal, @GetShouldSaveData())
                @ValueChanges(option, newVal, pnl)
    Label: (text = '') =>
        with vgui.Create('DLabel', @)
            \SetText(text)
            \Dock(TOP)
            \SetTextColor(color_white)
            \SizeToContents()
            w, h = \GetSize()
            \SetSize(w, h + 5)
	CheckBox: (name = 'Label', option = '') =>
		with vgui.Create('DCheckBoxLabel', @)
			\Dock(TOP)
			\DockMargin(2, 2, 2, 2)
			\SetText(name)
			\SetTextColor(color_white)
			\SetTooltip("#{name}\nData value: #{option}")
			\SetChecked(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
            .OnChange = (pnl, newVal = false) ->
                return if option == ''
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, newVal and 1 or 0, @GetShouldSaveData())
                @ValueChanges(option, newVal and 1 or 0, pnl)
    ColorBox: (name = 'Colorful Box', option = '') =>
        collapse = vgui.Create('DCollapsibleCategory', @)
        box = vgui.Create('DColorMixer', collapse)
        collapse.box = box
        with box
            \SetSize(250, 180)
			\SetTooltip("#{name}\nData value: #{option}")
            \SetColor(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
            .ValueChanged = (pnl, newVal = Color(0, 0, 0)) ->
                return if option == ''
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, newVal, @GetShouldSaveData())
                @ValueChanges(option, newVal, pnl)
        with collapse
            \SetContents(box)
            \Dock(TOP)
            \DockMargin(2, 2, 2, 2)
            \SetSize(250, 180)
            \SetLabel(name)
            \SetExpanded(false)
        return box, collapse
    ComboBox: (name = 'Combo Box', option = '', choices) =>
        label = vgui.Create('DLabel', @)
        with label
            \SetText(name)
            \SetTextColor(color_white)
            \Dock(TOP)
            \SetSize(0, 20)
            \DockMargin(5, 0, 5, 0)
            \SetMouseInputEnabled(true)
        box = vgui.Create('DComboBox', label)
        with box
            \Dock(RIGHT)
            \SetSize(170, 0)
            \DockMargin(0, 0, 5, 0)
            \SetValue(@GetTargetData()["Get#{option}Enum"](@GetTargetData())) if @GetTargetData()
            if choices
                \AddChoice(choice) for choice in *choices
            else
                \AddChoice(choice) for choice in *@GetTargetData()["Get#{option}Types"](@GetTargetData()) if @GetTargetData() and @GetTargetData()["Get#{option}Types"]
            .OnSelect = (pnl = box, index = 1, value = '', data = value) ->
                index -= 1
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, index, @GetShouldSaveData())
                @ValueChanges(option, index, pnl)
        return box, label
    Paint: (w = 0, h = 0) =>
		surface.SetDrawColor(130, 130, 130)
		surface.DrawRect(0, 0, w, h)
}

vgui.Register('PPM2SettingsBase', PANEL_SETTINGS_BASE, 'EditablePanel')

EditorModels = {
    'DEFAULT': 'models/ppm/player_default_base.mdl'
    'CPPM': 'models/cppm/player_default_base.mdl'
    'NEW': 'models/ppm/player_default_base_new.mdl'
}

EditorPages = {
    {
        'name': 'Main'
        'func': (sheet) =>
            @CheckBox('Gender', 'Gender')
            @ComboBox('Race', 'Race')
            @NumSlider('Weight', 'Weight', 2)
            @CheckBox('Socks', 'Socks')
            @ComboBox('Eyelashes', 'EyelashType')
            @ComboBox('Bodysuit', 'Bodysuit')
            @ColorBox('Body color', 'BodyColor')
    }

    {
        'name': 'Eyes'
        'func': (sheet) =>
            @CheckBox('Use separated settings for eyes', 'SeparateEyes')
            for publicName in *{'', 'Left', 'Right'}
                @Label("'#{publicName}' Eye settings")
                @CheckBox("#{publicName} Eye lines", "EyeLines#{publicName}")
                @CheckBox("#{publicName} Derp eye", "DerpEyes#{publicName}")
                @NumSlider("#{publicName} Derp eye strength", "DerpEyesStrength#{publicName}", 2)
                @NumSlider("#{publicName} Eye size", "IrisSize#{publicName}", 2)
                @NumSlider("#{publicName} Eye hole width", "HoleWidth#{publicName}", 2)
                @NumSlider("#{publicName} Eye hole size", "HoleSize#{publicName}", 2)

                @ColorBox("#{publicName} Eye background", "EyeBackground#{publicName}")
                @ColorBox("#{publicName} Eye hole", "EyeHole#{publicName}")
                @ColorBox("#{publicName} Top eye iris", "EyeIrisTop#{publicName}")
                @ColorBox("#{publicName} Bottom eye iris", "EyeIrisBottom#{publicName}")
                @ColorBox("#{publicName} Eye line 1", "EyeIrisLine1#{publicName}")
                @ColorBox("#{publicName} Eye line 2", "EyeIrisLine2#{publicName}")
                @ColorBox("#{publicName} Eye line 2", "EyeIrisLine2#{publicName}")
    }

    {
        'name': 'Mane and tail'
        'func': (sheet) =>
            @Label('"New" types affect only new model')
            @ComboBox('Tail type', 'TailType')
            @ComboBox('New Tail type', 'TailTypeNew')

            @ComboBox('Mane type', 'ManeType')
            @ComboBox('New Mane type', 'ManeTypeNew')

            @ComboBox('Lower Mane type', 'ManeTypeLower')
            @ComboBox('New Lower Mane type', 'ManeTypeLowerNew')

            @Label('Colors higher than 2 are reserved\n - They would not affect anything\n(they are not shown here)')
            for i = 1, 2
                @ColorBox("Mane color #{i}", "ManeColor#{i}")
                @ColorBox("Mane detail color #{i}", "ManeColor#{i}")
                @ColorBox("Tail color #{i}", "TailColor#{i}")
                @ColorBox("Tail detail color #{i}", "TailDetailColor#{i}")
    }

    {
        'name': 'Body details'
        'func': (sheet) =>
            for i = 1, PPM2.MAX_BODY_DETAILS
                @ComboBox("Detail #{i}", "BodyDetail#{i}")
                @ColorBox("Detail color #{i}", "BodyDetailColor#{i}")
    }
}

PPM2.OpenEditor = ->
    return if IsValid(PPM2.EditorFrame)
    frame = vgui.Create('DFrame')
    self = frame
    @SetSize(ScrW() - 25, ScrH() - 25)
    @Center()
    @MakePopup()
    @SetTitle('PPM2 Pony Editor')

    @menus = vgui.Create('DPropertySheet', @)
    @menus\Dock(LEFT)
    @menus\SetSize(370, 0)

    @model = vgui.Create('PPM2ModelPanel', @)
    @model\Dock(FILL)

    copy = PPM2.GetMainData()\Copy()
    ply = LocalPlayer()
    ent = @model\ResetModel(nil, EditorModels.DEFAULT)
    controller = copy\CreateCustomController(ent)
    copy\SetController(controller)
    frame.controller = controller
    frame.data = copy

    @SetTitle("#{copy\GetFilename() or '%ERRNAME%'} - PPM2 Pony Editor")

    @model\SetController(controller)
    controller\SetupEntity(ent)

    for {:name, :func} in *EditorPages
        pnl = vgui.Create('PPM2SettingsBase', @menus)
        @menus\AddSheet(name, pnl)
        pnl\SetTargetData(copy)
        pnl\Dock(FILL)
        pnl.frame = @
        func(pnl, @menus)
    PPM2.EditorFrame = frame

concommand.Add 'ppm2_editor', PPM2.OpenEditor

IconData =
	title: 'PPM V2.0',
	icon: 'gui/pped_icon.png',
	width: 960,
	height: 700,
	onewindow: true,
	init: (icon, window) ->
		window\Remove()
		RunConsoleCommand('ppm2_editor')

list.Set('DesktopWindows', 'PPM2', IconData)
CreateContextMenu() if IsValid(g_ContextMenu)


Added a shortcut for playground.

Added a shortcut for playground.

Audio = require('lib/utils/audio')
GuiScene = require('lib/scenes/gui')
AreaScene = require('lib/scenes/area')
Color = require('lib/display/color')

class TitleScene extends GuiScene

	new: (...) =>
		super(...)
		@music = Audio('vendor/wave/music.wav', 'stream')
		@music\getSound()\setLooping(true)
		@sound = Audio('vendor/wave/sound.wav', 'static')

	enter: (...) =>
		super(...)
		@music\play()

	update: (...) =>
		super(...)

		@gui.layout\row(0, 200)

		self.buttonStart = @gui\Button('Start', @gui.layout\row(200, 20))
		self.buttonPlayground = @gui\Button('Playground', @gui.layout\row(200, 20))
		self.buttonQuit = @gui\Button('Quit', @gui.layout\row(200, 20))

		if self.buttonStart.hit then @\startGame()
		if self.buttonPlayground.hit then @\startPlayground()
		if self.buttonQuit.hit then @\quitGame()

	draw: (...) =>
		super(...)
		love.graphics.print({ Color(0, 150, 200), 'One of these days...' }, 200, 100)

	keypressed: (key) =>
		super(key)
		if key == 'space' then @\startGame()
		if key == 'return' then @\startPlayground()
		if key == 'escape' then @\quitGame()

	startGame: () =>
		@switch(AreaScene('rocket.lua'))
		@sound\play()

	startPlayground: () =>
		@switch(AreaScene('sample_map.lua'))
		@sound\play()

	quitGame: () =>
		love.event.quit()

Audio = require('lib/utils/audio')
GuiScene = require('lib/scenes/gui')
AreaScene = require('lib/scenes/area')
Color = require('lib/display/color')

class TitleScene extends GuiScene

	new: (...) =>
		super(...)
		@music = Audio('vendor/wave/music.wav', 'stream')
		@music\getSound()\setLooping(true)
		@sound = Audio('vendor/wave/sound.wav', 'static')

	enter: (...) =>
		super(...)
		@music\play()

	update: (...) =>
		super(...)

		@gui.layout\row(0, 200)

		self.buttonStart = @gui\Button('Start', @gui.layout\row(200, 20))
		self.buttonPlayground = @gui\Button('Playground', @gui.layout\row(200, 20))
		self.buttonQuit = @gui\Button('Quit', @gui.layout\row(200, 20))

		if self.buttonStart.hit then @\startGame()
		if self.buttonPlayground.hit then @\startPlayground()
		if self.buttonQuit.hit then @\quitGame()

	draw: (...) =>
		super(...)
		love.graphics.print({ Color(0, 150, 200), 'One of these days...' }, 200, 100)

	keypressed: (key) =>
		super(key)
		if key == 'return' then @\startGame()
		if key == 'escape' then @\quitGame()

	startGame: () =>
		@switch(AreaScene('rocket.lua'))
		@sound\play()

	startPlayground: () =>
		@switch(AreaScene('sample_map.lua'))
		@sound\play()

	quitGame: () =>
		love.event.quit()


found translations...

found translations...

-- 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 textur URL eingabe\nEs muss 256x16 sein!"
gui.ppm2.editor.eyes.desc2 = "Glanzstärke\nDieser Parameter erhöht die Stärke der Echtzeitreflexionen auf das Auge\nUm Änderungen zu sehen, setze ppm2_cl_reflections convar auf 1\nAndere Spieler würden Reflexionen nur sehen, wenn ppm2_cl_reflections auf 1 gesetzt ist\n0 - ist matt; 1 - ist spiegelnd"

for {tprefix, prefix} in *{{'def', ''}, {'left', 'Links '}, {'right', 'Rechts '}}
	gui.ppm2.editor.eyes[tprefix].lightwarp.shader = "#{prefix}EyeRefract Shader verwenden"
	gui.ppm2.editor.eyes[tprefix].lightwarp.cornera = "#{prefix}Eye Cornea diffus verwenden"
	gui.ppm2.editor.eyes[tprefix].lightwarp.glossiness = "#{prefix}Glanz"

	gui.ppm2.editor.eyes[tprefix].type = "#{prefix}Augentyp"
	gui.ppm2.editor.eyes[tprefix].lines = "#{prefix}Augenlinien"
	gui.ppm2.editor.eyes[tprefix].derp = "#{prefix}Derp Augen"
	gui.ppm2.editor.eyes[tprefix].derp_strength = "#{prefix}Derp Augen Stärke"
	gui.ppm2.editor.eyes[tprefix].iris_size = "#{prefix}Augengröße"

	gui.ppm2.editor.eyes[tprefix].points_inside = "#{prefix}Augenlinien innen"
	gui.ppm2.editor.eyes[tprefix].width = "#{prefix}Augenbreite"
	gui.ppm2.editor.eyes[tprefix].height = "#{prefix}Augenhöhe"

	gui.ppm2.editor.eyes[tprefix].pupil.width = "#{prefix}Pupillenbreite"
	gui.ppm2.editor.eyes[tprefix].pupil.height = "#{prefix}Pupillenhöhe"
	gui.ppm2.editor.eyes[tprefix].pupil.size = "#{prefix}Pupillengröße"

	gui.ppm2.editor.eyes[tprefix].pupil.shift_x = "#{prefix}Pupillen Verschiebung X"
	gui.ppm2.editor.eyes[tprefix].pupil.shift_y = "#{prefix}Pupillen Verschiebung Y"
	gui.ppm2.editor.eyes[tprefix].pupil.rotation = "#{prefix}Augenrotation"

	gui.ppm2.editor.eyes[tprefix].background = "#{prefix}Augenhintergrund"
	gui.ppm2.editor.eyes[tprefix].pupil_size = "#{prefix}Pupille"
	gui.ppm2.editor.eyes[tprefix].top_iris = "#{prefix}Obere Augeniris"
	gui.ppm2.editor.eyes[tprefix].bottom_iris = "#{prefix}Untere Augeniris"
	gui.ppm2.editor.eyes[tprefix].line1 = "#{prefix}Augenlinie 1"
	gui.ppm2.editor.eyes[tprefix].line2 = "#{prefix}Augenlinie 2"
	gui.ppm2.editor.eyes[tprefix].reflection = "#{prefix}Augenreflexionseffekt"
	gui.ppm2.editor.eyes[tprefix].effect = "#{prefix}Augeneffekt"

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 (ungespeicherte Änderungen!)'

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\nDatenwert: %q'
gui.ppm2.editor.generic.url = '%s\n\nLink geht zu: %s'
gui.ppm2.editor.generic.url_field = 'URL Feld'
gui.ppm2.editor.generic.spoiler = 'Geheimnisvoller Spoiler'

gui.ppm2.editor.generic.restart.needed = 'Neustart des Editors erforderlich'
gui.ppm2.editor.generic.restart.text = 'Sie sollten den Editor neu starten, um die Änderung zu übernehmen.\nJetzt neu starten?\nNicht gespeicherte Daten gehen verloren!'
gui.ppm2.editor.generic.fullbright = 'Volle Helligkeit'
gui.ppm2.editor.generic.wtf = 'Aus irgendeinem Grund hat Dein Player kein NetworkedPonyData. - Nichts zu bearbeiten!\nVersuche ppm2_reload in Deine Konsole einzugeben und versuche es erneut, den Editor zu öffnen.'

gui.ppm2.editor.io.random.title = 'Randomisieren!'
gui.ppm2.editor.io.newfile.title = 'Neue Datei'
gui.ppm2.editor.io.newfile.confirm = 'Willst Du wirklich eine neue Datei erstellen?'
gui.ppm2.editor.io.newfile.toptext = 'Zurücksetzen'
gui.ppm2.editor.io.delete.confirm = 'Willst Du Datei wirklich löschen?\nEs wird für immer weg sein.!\n(eine lange Zeit!)'
gui.ppm2.editor.io.delete.title = 'Really Delete?'

gui.ppm2.editor.io.filename = 'Dateiname'
gui.ppm2.editor.io.hint = 'Datei per Doppelklick öffnen'
gui.ppm2.editor.io.reload = 'Dateiliste neu laden'
gui.ppm2.editor.io.failed = 'Import fehlgeschlagen.'

gui.ppm2.editor.io.warn.oldfile = '!!! Es kann funktionieren oder auch nicht. Du wirst zerquetscht werden.'
gui.ppm2.editor.io.warn.text = "Derzeit hast du deine Änderungen nicht angegeben.\nWillst Du wirklich eine andere Datei öffnen?"
gui.ppm2.editor.io.warn.header = 'Nicht gespeicherte Änderungen!'
gui.ppm2.editor.io.save.button = 'Speichern'
gui.ppm2.editor.io.save.text = 'Dateiname ohne ppm2/ und .dat eingeben\nTipp: Um als Autoload zu speichern, schreibe "_current" (ohne ") ein.'
gui.ppm2.editor.io.wear = 'Änderungen übernehmen (Anziehen)'

gui.ppm2.editor.seq.standing = 'Stehen'
gui.ppm2.editor.seq.move = 'Bewegend'
gui.ppm2.editor.seq.walk = 'Gehen'
gui.ppm2.editor.seq.sit = 'Sitzen'
gui.ppm2.editor.seq.swim = 'Schwimmen'
gui.ppm2.editor.seq.run = 'Laufen'
gui.ppm2.editor.seq.duckwalk = 'Ducken gehen'
gui.ppm2.editor.seq.duck = 'Ducken'
gui.ppm2.editor.seq.jump = 'Springen'

gui.ppm2.editor.misc.race = 'Rasse'
gui.ppm2.editor.misc.weight = 'Gewicht'
gui.ppm2.editor.misc.size = 'Pony Größe'
gui.ppm2.editor.misc.hide_weapons = 'Waffen verstecken'
gui.ppm2.editor.misc.chest = 'Männliche Brust'
gui.ppm2.editor.misc.gender = 'Geschlecht'
gui.ppm2.editor.misc.wings = 'Flügel Typ'
gui.ppm2.editor.misc.flexes = 'Flex steuerung'
gui.ppm2.editor.misc.no_flexes2 = 'Kein Flex beim neuen Modell'
gui.ppm2.editor.misc.no_flexes_desc = 'Du kannst jeden Flex Zustandsregler separat deaktivieren.\nSo können diese Flexe mit Addons von Drittanbietern (wie PAC3) modifiziert werden.'

gui.ppm2.editor.misc.hide_pac3 = 'Entitys ausblenden wenn PAC3 Entity verwendet wird'
gui.ppm2.editor.misc.hide_mane = 'Mähne ausblenden wenn PAC3 Entity verwendet wird'
gui.ppm2.editor.misc.hide_tail = 'Schweif ausblenden wenn PAC3 Entity verwendet wird'
gui.ppm2.editor.misc.hide_socks = 'Socken ausblenden wenn PAC3 Entity verwendet wird'

gui.ppm2.editor.tabs.main = 'Allgemeines'
gui.ppm2.editor.tabs.files = 'Dateien'
gui.ppm2.editor.tabs.old_files = 'Alte Dateien'
gui.ppm2.editor.tabs.cutiemark = 'Schönheitsfleck'
gui.ppm2.editor.tabs.head = 'Kopfanatomie'
gui.ppm2.editor.tabs.eyes = 'Augen'
gui.ppm2.editor.tabs.face = 'Gesicht'
gui.ppm2.editor.tabs.mouth = 'Mund'
gui.ppm2.editor.tabs.left_eye = 'Linkes Auge'
gui.ppm2.editor.tabs.right_eye = 'Rechtes Auge'
gui.ppm2.editor.tabs.mane_horn = 'Mähne und Horn'
gui.ppm2.editor.tabs.mane = 'Mähne'
gui.ppm2.editor.tabs.details = 'Details'
gui.ppm2.editor.tabs.url_details = 'URL Details'
gui.ppm2.editor.tabs.url_separated_details = 'URL Getrennte Details'
gui.ppm2.editor.tabs.ears = 'Ohren'
gui.ppm2.editor.tabs.horn = 'Ohren'
gui.ppm2.editor.tabs.back = 'Rücken'
gui.ppm2.editor.tabs.wings = 'Flügel'
gui.ppm2.editor.tabs.left = 'Links'
gui.ppm2.editor.tabs.right = 'Rechts'
gui.ppm2.editor.tabs.neck = 'Hals'
gui.ppm2.editor.tabs.body = 'Pony Körper'
gui.ppm2.editor.tabs.tattoos = 'Tätowierungen'
gui.ppm2.editor.tabs.tail = 'Schweif'
gui.ppm2.editor.tabs.hooves = 'Huf Anatomie'
gui.ppm2.editor.tabs.bottom_hoof = 'Huf unten'
gui.ppm2.editor.tabs.legs = 'Beine'
gui.ppm2.editor.tabs.socks = 'Beine'
gui.ppm2.editor.tabs.newsocks = 'Neue Legs'

gui.ppm2.editor.cutiemark.display = 'Schönheitsfleck anzeigen'
gui.ppm2.editor.cutiemark.type = 'Schönheitsfleck typ'
gui.ppm2.editor.cutiemark.size = 'Schönheitsfleck größe'
gui.ppm2.editor.cutiemark.color = 'Schönheitsfleck farbe'
gui.ppm2.editor.cutiemark.input = 'Schönheitsfleck URL bild Eingabefeld\nSollte PNG oder JPEG sein (funktioniert wie bei\nPAC3 URL texture)'

gui.ppm2.editor.face.eyelashes = 'Wimpern'
gui.ppm2.editor.face.eyelashes_color = 'Wimpern Farbe'
gui.ppm2.editor.face.eyebrows_color = 'Augenbrauen Farbe'
gui.ppm2.editor.face.new_muzzle = 'Neue Schnauze für das männliche Modell nutzen'

gui.ppm2.editor.face.nose = 'Nasenfarbe'
gui.ppm2.editor.face.lips = 'Lippen Farbe'
gui.ppm2.editor.face.eyelashes_separate_phong = 'Separate Wimpern Phong'
gui.ppm2.editor.face.eyebrows_glow = 'Leuchtende Augenbrauen'
gui.ppm2.editor.face.eyebrows_glow_strength = 'Augenbrauen leuchten Stärke'
gui.ppm2.editor.face.inherit.lips = 'Erbe Lippen Farbe vom Körper'
gui.ppm2.editor.face.inherit.nose = 'Erbe Nasen Farbe vom Körper'

gui.ppm2.editor.mouth.fangs = 'Fangzähne'
gui.ppm2.editor.mouth.alt_fangs = 'Alternative Fangzähne'
gui.ppm2.editor.mouth.claw = 'Klauenzähne'

gui.ppm2.editor.mouth.teeth = 'Zahnfarbe'
gui.ppm2.editor.mouth.mouth = 'Mundfarbe'
gui.ppm2.editor.mouth.tongue = 'Zungenfarbe'

gui.ppm2.editor.mane.type = 'Mähne Typ'
gui.ppm2.editor.mane.phong = 'Trenne Mähne phong Einstellungen vom Körper'
gui.ppm2.editor.mane.phong_sep = 'Trenne obere und untere Mähnenfarbe'
gui.ppm2.editor.mane.up.phong = 'Obere Mähne Phong Einstellungen'
gui.ppm2.editor.mane.down.phong = 'Untere Mähne Phong Einstellungen'
gui.ppm2.editor.mane.newnotice = 'Die nächsten Optionen wirken sich nur auf das neue Modell aus'

for i = 1, 2
	gui.ppm2.editor.mane['color' .. i] = "Mähnefarbe #{i}"
	gui.ppm2.editor.mane.up['color' .. i] = "Obere Mähnefarbe #{i}"
	gui.ppm2.editor.mane.down['color' .. i] = "Untere Mähnefarbe #{i}"

for i = 1, 6
	gui.ppm2.editor.mane['detail_color' .. i] = "Mähne Detail Farbe #{i}"
	gui.ppm2.editor.mane.up['detail_color' .. i] = "Obere Mähnefarbe #{i}"
	gui.ppm2.editor.mane.down['detail_color' .. i] = "Untere Mähnefarbe #{i}"

	gui.ppm2.editor.url_mane['desc' .. i] = "Mähne URL Detail #{i} Eingabefeld"
	gui.ppm2.editor.url_mane['color' .. i] = "Mähne URL detail farbe #{i}"

	gui.ppm2.editor.url_mane.sep.up['desc' .. i] = "Obere Mähne URL Detail #{i} Eingabefeld"
	gui.ppm2.editor.url_mane.sep.up['color' .. i] = "Obere Mähne URL detail farbe #{i}"

	gui.ppm2.editor.url_mane.sep.down['desc' .. i] = "Untere Mähne URL Detail #{i} Eingabefeld"
	gui.ppm2.editor.url_mane.sep.down['color' .. i] = "Untere Mähne URL detail farbe #{i}"

gui.ppm2.editor.ears.bat = 'Bat pony Ohren'
gui.ppm2.editor.ears.size = 'Ohrengröße'

gui.ppm2.editor.horn.detail_color = 'Horn Detailfarbe'
gui.ppm2.editor.horn.glowing_detail = 'Horn leuchten Detail'
gui.ppm2.editor.horn.glow_strength = 'Horn leuchten Stärke'
gui.ppm2.editor.horn.separate_color = 'Trenne Horn farbe vom Körper'
gui.ppm2.editor.horn.color = 'Horn farbe'
gui.ppm2.editor.horn.magic = 'Horn magie farbe'
gui.ppm2.editor.horn.separate_magic_color = 'Trenne magie farbe vom Augen farbe'
gui.ppm2.editor.horn.separate = 'Trenne Horn farbe vom Körper'
gui.ppm2.editor.horn.separate_phong = 'Trenne Horn phong Einstellungen vom Körper'

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

gui.ppm2.editor.wings.separate_color = 'Trenne Flügel farbe vom Körper'
gui.ppm2.editor.wings.color = 'Flügel farbe'
gui.ppm2.editor.wings.separate = 'Trenne Flügel farbe vom Körper'
gui.ppm2.editor.wings.separate_phong = 'Trenne Flügel phong Einstellungen vom Körper'
gui.ppm2.editor.wings.bat_color = 'Bat Flügel farbe'
gui.ppm2.editor.wings.bat_skin_color = 'Bat Flügel Haut farbe'

gui.ppm2.editor.wings.normal = 'Normale Flügel'
gui.ppm2.editor.wings.bat = 'Bat Flügel'
gui.ppm2.editor.wings.bat_skin = 'Bat Flügel Haut'

gui.ppm2.editor.wings.left.size = 'Linker Flügel Größe'
gui.ppm2.editor.wings.left.fwd = 'Linker Flügel Vorwärts'
gui.ppm2.editor.wings.left.up = 'Linker Flügel Hoch'
gui.ppm2.editor.wings.left.inside = 'Linker Flügel Innen'

gui.ppm2.editor.wings.right.size = 'Rechter Flügel Größe'
gui.ppm2.editor.wings.right.fwd = 'Rechter Flügel Vorwärts'
gui.ppm2.editor.wings.right.up = 'Rechter Flügel Hoch'
gui.ppm2.editor.wings.right.inside = 'Rechter Flügel Innen'

for i = 1, 3
	gui.ppm2.editor.wings.details.def['detail' .. i] = "Flügel URL detail #{i}"
	gui.ppm2.editor.wings.details.def['color' .. i] = "URL Detail farbe #{i}"
	gui.ppm2.editor.wings.details.bat['detail' .. i] = "Bat Flügel URL detail #{i}"
	gui.ppm2.editor.wings.details.bat['color' .. i] = "Bat Flügel URL Detail farbe #{i}"
	gui.ppm2.editor.wings.details.batskin['detail' .. i] = "Bat Flügel Haut URL detail #{i}"
	gui.ppm2.editor.wings.details.batskin['color' .. i] = "Bat Flügel Haut URL Detail farbe #{i}"

gui.ppm2.editor.neck.height = 'Halshöhe'

gui.ppm2.editor.body.suit = 'Körperanzug'
gui.ppm2.editor.body.color = 'Korper farbe'
gui.ppm2.editor.body.spine_length = 'Rückenlänge'
gui.ppm2.editor.body.url_desc = 'Körper detail URL Bild Eingabefeld\nSollte PNG oder JPEG sein (funktioniert wie bei\nPAC3 URL textur)'

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

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

gui.ppm2.editor.tattoo.edit_keyboard = 'Bearbeiten mit der Tastatur'
gui.ppm2.editor.tattoo.type = 'Typ'
gui.ppm2.editor.tattoo.over = 'Tattoo über Körperdetails'
gui.ppm2.editor.tattoo.glow = 'Tattoo leuchtet'
gui.ppm2.editor.tattoo.glow_strength = 'Tattoo leuchtkraft'
gui.ppm2.editor.tattoo.color = 'Farbe der Tätowierung'

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 = 'Breite Skalierung'
gui.ppm2.editor.tattoo.tweak.height = 'Länge Skalierung'

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

gui.ppm2.editor.tail.type = 'Schweif typ'
gui.ppm2.editor.tail.size = 'Schweif größe'
gui.ppm2.editor.tail.separate = 'Trenne Schweif phong Einstellungen vom Körper'

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

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

gui.ppm2.editor.hoof.fluffers = 'Huf flausch'

gui.ppm2.editor.legs.height = 'Bein höhe'
gui.ppm2.editor.legs.socks.simple = 'Socken (einfache Textur)'
gui.ppm2.editor.legs.socks.model = 'Socken (als Modell)'
gui.ppm2.editor.legs.socks.color = 'Socken Modell Farbe'
gui.ppm2.editor.legs.socks.texture = 'Socken Textur'
gui.ppm2.editor.legs.socks.url_texture = 'Socken URL textur'

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

gui.ppm2.editor.legs.newsocks.model = 'Socken (als neues Modell)'

for i = 1, 3
	gui.ppm2.editor.legs.newsocks['color' .. i] = 'Neue Socken Farbe ' .. i

gui.ppm2.editor.legs.newsocks.url = 'Neue Socken URL textur'

-- shared editor stuffs

gui.ppm2.editor.tattoo.help = "Um den Bearbeitungsmodus zu verlassen, drücke Escape oder klicke irgendwo mit der Maus
Um das Tattoo zu bewegen, verwende WASD
Um höher/niedriger zu skalieren, verwende die Hoch/Runter Pfeiltasten
Um breiter/kleiner zu skalieren, verwende die Rechts/Links Pfeiltasten
Um links/rechts zu Drehen, verwende die Q/E Tasten"

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

gui.ppm2.editor.phong.info = 'Mehr Infos über Phong im Wiki'
gui.ppm2.editor.phong.exponent = 'Phong Exponent - wie stark die reflektierende Eigenschaft\nvon der Ponyhaut ist\nSetze den Wert nahe Null, um dem Roboterlook zu bekommen\nPonyhaut'
gui.ppm2.editor.phong.exponent_text = 'Phong Exponent'
gui.ppm2.editor.phong.boost.title = 'Phong Verstärkung - multipliziert spiegelnde Kartenreflexionen'
gui.ppm2.editor.phong.boost.boost = 'Phong Verstärkung'
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 Grad ReflexionswinkelMultiplikator'
gui.ppm2.editor.phong.frensel.front.front = 'Phong Front'
gui.ppm2.editor.phong.frensel.middle.title = 'Phong Mitte - FFresnel 45 Grad ReflexionswinkelMultiplikator'
gui.ppm2.editor.phong.frensel.middle.front = 'Phong Mitte'
gui.ppm2.editor.phong.frensel.sliding.title = 'Phong Gleitend - Fresnel 45 Grad ReflexionswinkelMultiplikator'
gui.ppm2.editor.phong.frensel.sliding.front = 'Phong Gleitend'
gui.ppm2.editor.phong.lightwarp = 'Lightwarp'
gui.ppm2.editor.phong.url_lightwarp = 'Lightwarp textur URL eingabe\nEs muss 256x16 sein!'
gui.ppm2.editor.phong.bumpmap = 'Bumpmap URL eingabe'

gui.ppm2.editor.info.discord = "Trete DBot's Discord bei!"
gui.ppm2.editor.info.ponyscape = "PPM/2 ist ein Ponyscape projekt"
gui.ppm2.editor.info.creator = "PPM/2 wurde von DBot erstellt und entwickelt"
gui.ppm2.editor.info.newmodels = "Neue Modelle wurden von Durpy erstellt"
gui.ppm2.editor.info.cppmmodels = "CPPM Modelle (einschließlich Ponyhände) gehören zu UnkN', 'http://steamcommunity.com/profiles/76561198084938735"
gui.ppm2.editor.info.oldmodels = "Alte Modelle gehören zu Scentus und den anderen."
gui.ppm2.editor.info.bugs = "Einen Fehler gefunden? Melde es hier!"
gui.ppm2.editor.info.sources = "Quellen findest du hier"
gui.ppm2.editor.info.githubsources = "Oder im GitHub mirror"
gui.ppm2.editor.info.thanks = "Besonderer Dank geht an alle, die kritisiert,\ngeholfen und PPM/2 getestet haben!"

-- other stuff

info.ppm2.fly.pegasus = 'Du musst eine Pegasus oder ein Alicorn sein, um zu fliegen!'
info.ppm2.fly.cannot = 'Du kannst nicht %s im Augenblick.'

gui.ppm2.emotes.sad = 'Traurig'
gui.ppm2.emotes.wild = 'Wild'
gui.ppm2.emotes.grin = 'Grinsen'
gui.ppm2.emotes.angry = 'Wütend'
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 = 'Lippen lecken'
gui.ppm2.emotes.scrunch = 'Scrunch'
gui.ppm2.emotes.sorry = 'Entschuldigend'
gui.ppm2.emotes.wink = 'Zwinkern'
gui.ppm2.emotes.right_wink = 'Rechts Zwinkern'
gui.ppm2.emotes.licking = 'Lecken'
gui.ppm2.emotes.suggestive_lips = 'Anzüglich Lippen Lecken'
gui.ppm2.emotes.suggestive_no_tongue = 'Anzüglich ohne Zunge'
gui.ppm2.emotes.gulp = 'Schlucken'
gui.ppm2.emotes.blah = 'Blah blah blah'
gui.ppm2.emotes.happi = 'Glücklich'
gui.ppm2.emotes.happi_grin = 'Glückliches Grinsen'
gui.ppm2.emotes.duck = 'ENTE'
gui.ppm2.emotes.ducks = 'ENTEN WAHNSINN'
gui.ppm2.emotes.quack = 'QUACK'
gui.ppm2.emotes.suggestive = 'Anzüglich mit Zunge'

message.ppm2.emotes.invalid = 'Keine Emotion mit 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 = '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 textur URL eingabe\nEs muss 256x16 sein!"
gui.ppm2.editor.eyes.desc2 = "Glanzstärke\nDieser Parameter erhöht die Stärke der Echtzeitreflexionen auf das Auge\nUm Änderungen zu sehen, setze ppm2_cl_reflections convar auf 1\nAndere Spieler würden Reflexionen nur sehen, wenn ppm2_cl_reflections auf 1 gesetzt ist\n0 - ist matt; 1 - ist spiegelnd"

for {tprefix, prefix} in *{{'def', ''}, {'left', 'Links '}, {'right', 'Rechts '}}
	gui.ppm2.editor.eyes[tprefix].lightwarp.shader = "#{prefix}EyeRefract Shader verwenden"
	gui.ppm2.editor.eyes[tprefix].lightwarp.cornera = "#{prefix}Eye Cornea diffus verwenden"
	gui.ppm2.editor.eyes[tprefix].lightwarp.glossiness = "#{prefix}Glanz"

	gui.ppm2.editor.eyes[tprefix].type = "#{prefix}Augentyp"
	gui.ppm2.editor.eyes[tprefix].lines = "#{prefix}Augenlinien"
	gui.ppm2.editor.eyes[tprefix].derp = "#{prefix}Derp Augen"
	gui.ppm2.editor.eyes[tprefix].derp_strength = "#{prefix}Derp Augen Stärke"
	gui.ppm2.editor.eyes[tprefix].iris_size = "#{prefix}Augengröße"

	gui.ppm2.editor.eyes[tprefix].points_inside = "#{prefix}Augenlinien innen"
	gui.ppm2.editor.eyes[tprefix].width = "#{prefix}Augenbreite"
	gui.ppm2.editor.eyes[tprefix].height = "#{prefix}Augenhöhe"

	gui.ppm2.editor.eyes[tprefix].pupil.width = "#{prefix}Pupillenbreite"
	gui.ppm2.editor.eyes[tprefix].pupil.height = "#{prefix}Pupillenhöhe"
	gui.ppm2.editor.eyes[tprefix].pupil.size = "#{prefix}Pupillengröße"

	gui.ppm2.editor.eyes[tprefix].pupil.shift_x = "#{prefix}Pupillen Verschiebung X"
	gui.ppm2.editor.eyes[tprefix].pupil.shift_y = "#{prefix}Pupillen Verschiebung Y"
	gui.ppm2.editor.eyes[tprefix].pupil.rotation = "#{prefix}Augenrotation"

	gui.ppm2.editor.eyes[tprefix].background = "#{prefix}Augenhintergrund"
	gui.ppm2.editor.eyes[tprefix].pupil_size = "#{prefix}Pupille"
	gui.ppm2.editor.eyes[tprefix].top_iris = "#{prefix}Obere Augeniris"
	gui.ppm2.editor.eyes[tprefix].bottom_iris = "#{prefix}Untere Augeniris"
	gui.ppm2.editor.eyes[tprefix].line1 = "#{prefix}Augenlinie 1"
	gui.ppm2.editor.eyes[tprefix].line2 = "#{prefix}Augenlinie 2"
	gui.ppm2.editor.eyes[tprefix].reflection = "#{prefix}Augenreflexionseffekt"
	gui.ppm2.editor.eyes[tprefix].effect = "#{prefix}Augeneffekt"

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 (ungespeicherte Änderungen!)'

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\nDatenwert: %q'
gui.ppm2.editor.generic.url = '%s\n\nLink geht zu: %s'
gui.ppm2.editor.generic.url_field = 'URL Feld'
gui.ppm2.editor.generic.spoiler = 'Geheimnisvoller Spoiler'

gui.ppm2.editor.generic.restart.needed = 'Neustart des Editors erforderlich'
gui.ppm2.editor.generic.restart.text = 'Sie sollten den Editor neu starten, um die Änderung zu übernehmen.\nJetzt neu starten?\nNicht gespeicherte Daten gehen verloren!'
gui.ppm2.editor.generic.fullbright = 'Volle Helligkeit'
gui.ppm2.editor.generic.wtf = 'Aus irgendeinem Grund hat Dein Player kein NetworkedPonyData. - Nichts zu bearbeiten!\nVersuche ppm2_reload in Deine Konsole einzugeben und versuche es erneut, den Editor zu öffnen.'

gui.ppm2.editor.io.random.title = 'Randomisieren!'
gui.ppm2.editor.io.newfile.title = 'Neue Datei'
gui.ppm2.editor.io.newfile.confirm = 'Willst Du wirklich eine neue Datei erstellen?'
gui.ppm2.editor.io.newfile.toptext = 'Zurücksetzen'
gui.ppm2.editor.io.delete.confirm = 'Willst Du Datei wirklich löschen?\nEs wird für immer weg sein.!\n(eine lange Zeit!)'
gui.ppm2.editor.io.delete.title = 'Really Delete?'

gui.ppm2.editor.io.filename = 'Dateiname'
gui.ppm2.editor.io.hint = 'Datei per Doppelklick öffnen'
gui.ppm2.editor.io.reload = 'Dateiliste neu laden'
gui.ppm2.editor.io.failed = 'Import fehlgeschlagen.'

gui.ppm2.editor.io.warn.oldfile = '!!! Es kann funktionieren oder auch nicht. Du wirst zerquetscht werden.'
gui.ppm2.editor.io.warn.text = "Derzeit hast du deine Änderungen nicht angegeben.\nWillst Du wirklich eine andere Datei öffnen?"
gui.ppm2.editor.io.warn.header = 'Nicht gespeicherte Änderungen!'
gui.ppm2.editor.io.save.button = 'Speichern'
gui.ppm2.editor.io.save.text = 'Dateiname ohne ppm2/ und .dat eingeben\nTipp: Um als Autoload zu speichern, schreibe "_current" (ohne ") ein.'
gui.ppm2.editor.io.wear = 'Änderungen übernehmen (Anziehen)'

gui.ppm2.editor.seq.standing = 'Stehen'
gui.ppm2.editor.seq.move = 'Bewegend'
gui.ppm2.editor.seq.walk = 'Gehen'
gui.ppm2.editor.seq.sit = 'Sitzen'
gui.ppm2.editor.seq.swim = 'Schwimmen'
gui.ppm2.editor.seq.run = 'Laufen'
gui.ppm2.editor.seq.duckwalk = 'Ducken gehen'
gui.ppm2.editor.seq.duck = 'Ducken'
gui.ppm2.editor.seq.jump = 'Springen'

gui.ppm2.editor.misc.race = 'Rasse'
gui.ppm2.editor.misc.weight = 'Gewicht'
gui.ppm2.editor.misc.size = 'Pony Größe'
gui.ppm2.editor.misc.hide_weapons = 'Waffen verstecken'
gui.ppm2.editor.misc.chest = 'Männliche Brust'
gui.ppm2.editor.misc.gender = 'Geschlecht'
gui.ppm2.editor.misc.wings = 'Flügel Typ'
gui.ppm2.editor.misc.flexes = 'Flex steuerung'
gui.ppm2.editor.misc.no_flexes2 = 'Kein Flex beim neuen Modell'
gui.ppm2.editor.misc.no_flexes_desc = 'Du kannst jeden Flex Zustandsregler separat deaktivieren.\nSo können diese Flexe mit Addons von Drittanbietern (wie PAC3) modifiziert werden.'

gui.ppm2.editor.misc.hide_pac3 = 'Entitys ausblenden wenn PAC3 Entity verwendet wird'
gui.ppm2.editor.misc.hide_mane = 'Mähne ausblenden wenn PAC3 Entity verwendet wird'
gui.ppm2.editor.misc.hide_tail = 'Schweif ausblenden wenn PAC3 Entity verwendet wird'
gui.ppm2.editor.misc.hide_socks = 'Socken ausblenden wenn PAC3 Entity verwendet wird'

gui.ppm2.editor.tabs.main = 'Allgemeines'
gui.ppm2.editor.tabs.files = 'Dateien'
gui.ppm2.editor.tabs.old_files = 'Alte Dateien'
gui.ppm2.editor.tabs.cutiemark = 'Schönheitsfleck'
gui.ppm2.editor.tabs.head = 'Kopfanatomie'
gui.ppm2.editor.tabs.eyes = 'Augen'
gui.ppm2.editor.tabs.face = 'Gesicht'
gui.ppm2.editor.tabs.mouth = 'Mund'
gui.ppm2.editor.tabs.left_eye = 'Linkes Auge'
gui.ppm2.editor.tabs.right_eye = 'Rechtes Auge'
gui.ppm2.editor.tabs.mane_horn = 'Mähne und Horn'
gui.ppm2.editor.tabs.mane = 'Mähne'
gui.ppm2.editor.tabs.details = 'Details'
gui.ppm2.editor.tabs.url_details = 'URL Details'
gui.ppm2.editor.tabs.url_separated_details = 'URL Getrennte Details'
gui.ppm2.editor.tabs.ears = 'Ohren'
gui.ppm2.editor.tabs.horn = 'Ohren'
gui.ppm2.editor.tabs.back = 'Rücken'
gui.ppm2.editor.tabs.wings = 'Flügel'
gui.ppm2.editor.tabs.left = 'Links'
gui.ppm2.editor.tabs.right = 'Rechts'
gui.ppm2.editor.tabs.neck = 'Hals'
gui.ppm2.editor.tabs.body = 'Pony Körper'
gui.ppm2.editor.tabs.tattoos = 'Tätowierungen'
gui.ppm2.editor.tabs.tail = 'Schweif'
gui.ppm2.editor.tabs.hooves = 'Huf Anatomie'
gui.ppm2.editor.tabs.bottom_hoof = 'Huf unten'
gui.ppm2.editor.tabs.legs = 'Beine'
gui.ppm2.editor.tabs.socks = 'Beine'
gui.ppm2.editor.tabs.newsocks = 'Neue Legs'

gui.ppm2.editor.cutiemark.display = 'Schönheitsfleck anzeigen'
gui.ppm2.editor.cutiemark.type = 'Schönheitsfleck typ'
gui.ppm2.editor.cutiemark.size = 'Schönheitsfleck größe'
gui.ppm2.editor.cutiemark.color = 'Schönheitsfleck farbe'
gui.ppm2.editor.cutiemark.input = 'Schönheitsfleck URL bild Eingabefeld\nSollte PNG oder JPEG sein (funktioniert wie bei\nPAC3 URL texture)'

gui.ppm2.editor.face.eyelashes = 'Wimpern'
gui.ppm2.editor.face.eyelashes_color = 'Wimpern Farbe'
gui.ppm2.editor.face.eyebrows_color = 'Augenbrauen Farbe'
gui.ppm2.editor.face.new_muzzle = 'Neue Schnauze für das männliche Modell nutzen'

gui.ppm2.editor.face.nose = 'Nasenfarbe'
gui.ppm2.editor.face.lips = 'Lippen Farbe'
gui.ppm2.editor.face.eyelashes_separate_phong = 'Separate Wimpern Phong'
gui.ppm2.editor.face.eyebrows_glow = 'Leuchtende Augenbrauen'
gui.ppm2.editor.face.eyebrows_glow_strength = 'Augenbrauen leuchten Stärke'
gui.ppm2.editor.face.inherit.lips = 'Erbe Lippen Farbe vom Körper'
gui.ppm2.editor.face.inherit.nose = 'Erbe Nasen Farbe vom Körper'

gui.ppm2.editor.mouth.fangs = 'Fangzähne'
gui.ppm2.editor.mouth.alt_fangs = 'Alternative Fangzähne'
gui.ppm2.editor.mouth.claw = 'Klauenzähne'

gui.ppm2.editor.mouth.teeth = 'Zahnfarbe'
gui.ppm2.editor.mouth.mouth = 'Mundfarbe'
gui.ppm2.editor.mouth.tongue = 'Zungenfarbe'

gui.ppm2.editor.mane.type = 'Mähne Typ'
gui.ppm2.editor.mane.phong = 'Trenne Mähne phong Einstellungen vom Körper'
gui.ppm2.editor.mane.phong_sep = 'Trenne obere und untere Mähnenfarbe'
gui.ppm2.editor.mane.up.phong = 'Obere Mähne Phong Einstellungen'
gui.ppm2.editor.mane.down.phong = 'Untere Mähne Phong Einstellungen'
gui.ppm2.editor.mane.newnotice = 'Die nächsten Optionen wirken sich nur auf das neue Modell aus'

for i = 1, 2
	gui.ppm2.editor.mane['color' .. i] = "Mähnefarbe #{i}"
	gui.ppm2.editor.mane.up['color' .. i] = "Obere Mähnefarbe #{i}"
	gui.ppm2.editor.mane.down['color' .. i] = "Untere Mähnefarbe #{i}"

for i = 1, 6
	gui.ppm2.editor.mane['detail_color' .. i] = "Mähne Detail Farbe #{i}"
	gui.ppm2.editor.mane.up['detail_color' .. i] = "Obere Mähnefarbe #{i}"
	gui.ppm2.editor.mane.down['detail_color' .. i] = "Untere Mähnefarbe #{i}"

	gui.ppm2.editor.url_mane['desc' .. i] = "Mähne URL Detail #{i} Eingabefeld"
	gui.ppm2.editor.url_mane['color' .. i] = "Mähne URL detail farbe #{i}"

	gui.ppm2.editor.url_mane.sep.up['desc' .. i] = "Obere Mähne URL Detail #{i} Eingabefeld"
	gui.ppm2.editor.url_mane.sep.up['color' .. i] = "Obere Mähne URL detail farbe #{i}"

	gui.ppm2.editor.url_mane.sep.down['desc' .. i] = "Untere Mähne URL Detail #{i} Eingabefeld"
	gui.ppm2.editor.url_mane.sep.down['color' .. i] = "Untere Mähne URL detail farbe #{i}"

gui.ppm2.editor.ears.bat = 'Bat pony Ohren'
gui.ppm2.editor.ears.size = 'Ohrengröße'

gui.ppm2.editor.horn.detail_color = 'Horn Detailfarbe'
gui.ppm2.editor.horn.glowing_detail = 'Horn leuchten Detail'
gui.ppm2.editor.horn.glow_strength = 'Horn leuchten Stärke'
gui.ppm2.editor.horn.separate_color = 'Trenne Horn farbe vom Körper'
gui.ppm2.editor.horn.color = 'Horn farbe'
gui.ppm2.editor.horn.magic = 'Horn magie farbe'
gui.ppm2.editor.horn.separate_magic_color = 'Trenne magie farbe vom Augen farbe'
gui.ppm2.editor.horn.separate = 'Trenne Horn farbe vom Körper'
gui.ppm2.editor.horn.separate_phong = 'Trenne Horn phong Einstellungen vom Körper'

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

gui.ppm2.editor.wings.separate_color = 'Trenne Flügel farbe vom Körper'
gui.ppm2.editor.wings.color = 'Flügel farbe'
gui.ppm2.editor.wings.separate = 'Trenne Flügel farbe vom Körper'
gui.ppm2.editor.wings.separate_phong = 'Trenne Flügel phong Einstellungen vom Körper'
gui.ppm2.editor.wings.bat_color = 'Bat Flügel farbe'
gui.ppm2.editor.wings.bat_skin_color = 'Bat Flügel Haut farbe'

gui.ppm2.editor.wings.normal = 'Normale Flügel'
gui.ppm2.editor.wings.bat = 'Bat Flügel'
gui.ppm2.editor.wings.bat_skin = 'Bat Flügel Haut'

gui.ppm2.editor.wings.left.size = 'Linker Flügel Größe'
gui.ppm2.editor.wings.left.fwd = 'Linker Flügel Vorwärts'
gui.ppm2.editor.wings.left.up = 'Linker Flügel Hoch'
gui.ppm2.editor.wings.left.inside = 'Linker Flügel Innen'

gui.ppm2.editor.wings.right.size = 'Rechter Flügel Größe'
gui.ppm2.editor.wings.right.fwd = 'Rechter Flügel Vorwärts'
gui.ppm2.editor.wings.right.up = 'Rechter Flügel Hoch'
gui.ppm2.editor.wings.right.inside = 'Rechter Flügel Innen'

for i = 1, 3
	gui.ppm2.editor.wings.details.def['detail' .. i] = "Flügel URL detail #{i}"
	gui.ppm2.editor.wings.details.def['color' .. i] = "URL Detail farbe #{i}"
	gui.ppm2.editor.wings.details.bat['detail' .. i] = "Bat Flügel URL detail #{i}"
	gui.ppm2.editor.wings.details.bat['color' .. i] = "Bat Flügel URL Detail farbe #{i}"
	gui.ppm2.editor.wings.details.batskin['detail' .. i] = "Bat Flügel Haut URL detail #{i}"
	gui.ppm2.editor.wings.details.batskin['color' .. i] = "Bat Flügel Haut URL Detail farbe #{i}"

gui.ppm2.editor.neck.height = 'Halshöhe'

gui.ppm2.editor.body.suit = 'Körperanzug'
gui.ppm2.editor.body.color = 'Korper farbe'
gui.ppm2.editor.body.spine_length = 'Rückenlänge'
gui.ppm2.editor.body.url_desc = 'Körper detail URL Bild Eingabefeld\nSollte PNG oder JPEG sein (funktioniert wie bei\nPAC3 URL textur)'

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

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

gui.ppm2.editor.tattoo.edit_keyboard = 'Bearbeiten mit der Tastatur'
gui.ppm2.editor.tattoo.type = 'Typ'
gui.ppm2.editor.tattoo.over = 'Tattoo über Körperdetails'
gui.ppm2.editor.tattoo.glow = 'Tattoo leuchtet'
gui.ppm2.editor.tattoo.glow_strength = 'Tattoo leuchtkraft'
gui.ppm2.editor.tattoo.color = 'Farbe der Tätowierung'

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 = 'Breite Skalierung'
gui.ppm2.editor.tattoo.tweak.height = 'Länge Skalierung'

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

gui.ppm2.editor.tail.type = 'Schweif typ'
gui.ppm2.editor.tail.size = 'Schweif größe'
gui.ppm2.editor.tail.separate = 'Trenne Schweif phong Einstellungen vom Körper'

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

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

gui.ppm2.editor.hoof.fluffers = 'Huf flausch'

gui.ppm2.editor.legs.height = 'Bein höhe'
gui.ppm2.editor.legs.socks.simple = 'Socken (einfache Textur)'
gui.ppm2.editor.legs.socks.model = 'Socken (als Modell)'
gui.ppm2.editor.legs.socks.color = 'Socken Modell Farbe'
gui.ppm2.editor.legs.socks.texture = 'Socken Textur'
gui.ppm2.editor.legs.socks.url_texture = 'Socken URL textur'

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

gui.ppm2.editor.legs.newsocks.model = 'Socken (als neues Modell)'

for i = 1, 3
	gui.ppm2.editor.legs.newsocks['color' .. i] = 'Neue Socken Farbe ' .. i

gui.ppm2.editor.legs.newsocks.url = 'Neue Socken URL textur'

-- shared editor stuffs

gui.ppm2.editor.tattoo.help = "Um den Bearbeitungsmodus zu verlassen, drücke Escape oder klicke irgendwo mit der Maus
Um das Tattoo zu bewegen, verwende WASD
Um höher/niedriger zu skalieren, verwende die Hoch/Runter Pfeiltasten
Um breiter/kleiner zu skalieren, verwende die Rechts/Links Pfeiltasten
Um links/rechts zu Drehen, verwende die Q/E Tasten"

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

gui.ppm2.editor.phong.info = 'Mehr Infos über Phong im Wiki'
gui.ppm2.editor.phong.exponent = 'Phong Exponent - wie stark die reflektierende Eigenschaft\nvon der Ponyhaut ist\nSetze den Wert nahe Null, um dem Roboterlook zu bekommen\nPonyhaut'
gui.ppm2.editor.phong.exponent_text = 'Phong Exponent'
gui.ppm2.editor.phong.boost.title = 'Phong Verstärkung - multipliziert spiegelnde Kartenreflexionen'
gui.ppm2.editor.phong.boost.boost = 'Phong Verstärkung'
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 textur URL eingabe\nEs muss 256x16 sein!'
gui.ppm2.editor.phong.bumpmap = 'Bumpmap URL eingabe'

gui.ppm2.editor.info.discord = "Trete DBot's Discord bei!"
gui.ppm2.editor.info.ponyscape = "PPM/2 ist ein Ponyscape projekt"
gui.ppm2.editor.info.creator = "PPM/2 wurde von DBot erstellt und entwickelt"
gui.ppm2.editor.info.newmodels = "Neue Modelle wurden von Durpy erstellt"
gui.ppm2.editor.info.cppmmodels = "CPPM Modelle (einschließlich Ponyhände) gehören zu UnkN', 'http://steamcommunity.com/profiles/76561198084938735"
gui.ppm2.editor.info.oldmodels = "Alte Modelle gehören zu Scentus und den anderen."
gui.ppm2.editor.info.bugs = "Einen Fehler gefunden? Melde es hier!"
gui.ppm2.editor.info.sources = "Quellen findest du hier"
gui.ppm2.editor.info.githubsources = "Oder im GitHub mirror"
gui.ppm2.editor.info.thanks = "Besonderer Dank geht an alle, die kritisiert,\ngeholfen und PPM/2 getestet haben!"

-- other stuff

info.ppm2.fly.pegasus = 'Du musst eine Pegasus oder ein Alicorn sein, um zu fliegen!'
info.ppm2.fly.cannot = 'Du kannst nicht %s im Augenblick.'

gui.ppm2.emotes.sad = 'Traurig'
gui.ppm2.emotes.wild = 'Wild'
gui.ppm2.emotes.grin = 'Grinsen'
gui.ppm2.emotes.angry = 'Wütend'
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 = 'Lippen lecken'
gui.ppm2.emotes.scrunch = 'Scrunch'
gui.ppm2.emotes.sorry = 'Entschuldigend'
gui.ppm2.emotes.wink = 'Zwinkern'
gui.ppm2.emotes.right_wink = 'Rechts Zwinkern'
gui.ppm2.emotes.licking = 'Lecken'
gui.ppm2.emotes.suggestive_lips = 'Anzüglich Lippen Lecken'
gui.ppm2.emotes.suggestive_no_tongue = 'Anzüglich ohne Zunge'
gui.ppm2.emotes.gulp = 'Schlucken'
gui.ppm2.emotes.blah = 'Blah blah blah'
gui.ppm2.emotes.happi = 'Glücklich'
gui.ppm2.emotes.happi_grin = 'Glückliches Grinsen'
gui.ppm2.emotes.duck = 'ENTE'
gui.ppm2.emotes.ducks = 'ENTEN WAHNSINN'
gui.ppm2.emotes.quack = 'QUACK'
gui.ppm2.emotes.suggestive = 'Anzüglich mit Zunge'

message.ppm2.emotes.invalid = 'Keine Emotion mit ID: %s'


aunesand: update to new URL

aunesand: update to new URL

Former-commit-id: 2df6867bc367e6421ce531c9d5375d4d6e658dcf [formerly 6f8258cc0a13a92fc17a3d35e6aa2600cbf49694]
Former-commit-id: ad3ac2d61e80db9a08602bd85b1223c972352f9d

html2unicode = require'html'

hex_to_char = (x) ->
  string.char(tonumber(x, 16))

unescape = (url) ->
  url\gsub("%%(%x%x)", hex_to_char)

PRIVMSG:
  '^%paunesand': (source, destination) =>
    rnd = math.random(1,3000)
    ivar2.util.simplehttp 'http://p3.no/uv/aune-sands-poesigenerator/index_new.php?cachebuster='..rnd, (html) ->
      match = html\match[[<a title="Del diktet på Facebook" href="(.*)"]]
      if match
        poem = match\match[[facebookdescription=(.-)-]]
        poem = poem\gsub '+', ' '
        poem = unescape(poem)
        if poem
          say html2unicode(poem)

html2unicode = require'html'

hex_to_char = (x) ->
  string.char(tonumber(x, 16))

unescape = (url) ->
  url\gsub("%%(%x%x)", hex_to_char)

PRIVMSG:
  '^%paunesand': (source, destination) =>
    rnd = math.random(1,3000)
    ivar2.util.simplehttp 'http://p3.no/uv/aune-sands-poesigenerator/?cachebuster='..rnd, (html) ->
      match = html\match[[<a title="Del diktet på Facebook" href="(.*)"]]
      if match
        poem = match\match[[facebookdescription=(.-)-]]
        poem = poem\gsub '+', ' '
        poem = unescape(poem)
        if poem
          say html2unicode(poem)


Decreased view offset

Decreased view offset

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

PPM2.PLAYER_VIEW_OFFSET = Vector(0, 0, 64 * .66)
PPM2.PLAYER_VIEW_OFFSET_DUCK = Vector(0, 0, 28 * 1.2)

PPM2.PLAYER_VIEW_OFFSET_ORIGINAL = Vector(0, 0, 64)
PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL = Vector(0, 0, 28)

hook.Add 'PlayerSpawn', 'PPM2.Hooks', =>
    if IsValid(@__ppm2_ragdoll)
        @__ppm2_ragdoll\Remove()
        @UnSpectate()
    timer.Simple 0, ->
        return unless @IsValid()
        if @GetPonyData()
            @GetPonyData()\PlayerRespawn()
            net.Start('PPM2.PlayerRespawn')
            net.WriteEntity(@)
            net.Broadcast()

        if @IsPony()
            @__ppm2_pony_view_offset = true
            @SetViewOffset(PPM2.PLAYER_VIEW_OFFSET)
            @SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK)

            return if @GetPonyData()
            timer.Simple 0.5, ->
                net.Start('PPM2.RequestPonyData')
                net.Send(@)
        else
            if @__ppm2_pony_view_offset
                @__ppm2_pony_view_offset = false
                @SetViewOffset(PPM2.PLAYER_VIEW_OFFSET_ORIGINAL)
                @SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL)

hook.Add 'PlayerInitialSpawn', 'PPM2.Hooks', =>
    timer.Simple 0, ->
        return unless @IsValid()
        return unless @IsPony()

        timer.Simple 10, ->
            net.Start('PPM2.RequestPonyData')
            net.Send(@)

do
    REQUIRE_CLIENTS = {}

    safeSendFunction = ->
        for ply, data in pairs REQUIRE_CLIENTS
            if not IsValid(ply)
                REQUIRE_CLIENTS[ply] = nil
                continue

            ent = table.remove(data, 1)
            if not ent
                REQUIRE_CLIENTS[ply] = nil
                continue

            data = ent\GetPonyData()
            continue if not data
            data\NetworkTo(ply)

    errorTrack = (err) ->
        PPM2.Message 'Networking Error: ', err
        PPM2.Message debug.traceback()

    timer.Create 'PPM2.Require', 0.25, 0, -> xpcall(safeSendFunction, errorTrack)
    net.Receive 'PPM2.Require', (len = 0, ply = NULL) ->
        return if not IsValid(ply)
        REQUIRE_CLIENTS[ply] = for ent in *ents.GetAll()
            continue if ent == ply
            data = ent\GetPonyData()
            continue if not data
            ent

net.Receive 'PPM2.EditorStatus', (len = 0, ply = NULL) ->
    return if not IsValid(ply)
    ply\SetNWBool('PPM2.InEditor', net.ReadBool())

ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')

createPlayerRagdoll = =>
    @__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
    @__ppm2_ragdoll = ents.Create('prop_ragdoll')
    rag = @GetRagdollEntity()
    rag\Remove() if IsValid(rag)
    with @__ppm2_ragdoll
        \SetModel(@GetModel())
        \SetPos(@GetPos())
        \SetAngles(@EyeAngles())
        \SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
        \Spawn()
        \Activate()
        hook.Run 'PlayerSpawnedRagdoll', @, @GetModel(), @__ppm2_ragdoll
        .__ppm2_ragdoll_parent = @
        \SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
        \SetNWBool('PPM2.IsDeathRagdoll', true)
        vel = @GetVelocity()
        \SetVelocity(vel)
        \SetAngles(@EyeAngles())
        @Spectate(OBS_MODE_CHASE)
        @SpectateEntity(@__ppm2_ragdoll)
        for boneID = 0, @__ppm2_ragdoll\GetBoneCount() - 1
            physobjID = @__ppm2_ragdoll\TranslateBoneToPhysBone(boneID)
            pos, ang = @GetBonePosition(boneID)
            physobj = @__ppm2_ragdoll\GetPhysicsObjectNum(physobjID)
            physobj\SetVelocity(vel)
            physobj\SetMass(300) -- lol
            physobj\SetPos(pos, true) if pos
            physobj\SetAngles(ang) if ang
        copy = @GetPonyData()\Clone(@__ppm2_ragdoll)
        timer.Simple 0.5, -> copy\Create() if IsValid(@__ppm2_ragdoll)

ALLOW_RAGDOLL_DAMAGE = CreateConVar('ppm2_sv_ragdoll_damage', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Should death ragdoll cause damage?')

hook.Add 'EntityTakeDamage', 'PPM2.DeathRagdoll', (dmg) =>
    attacker = dmg\GetAttacker()
    return if not IsValid(attacker)
    if attacker.__ppm2_ragdoll_parent
        dmg\SetAttacker(attacker.__ppm2_ragdoll_parent)
        if not ALLOW_RAGDOLL_DAMAGE\GetBool()
            dmg\SetDamage(0)
            dmg\SetMaxDamage(0)

hook.Add 'PostPlayerDeath', 'PPM2.Hooks', =>
    return if not @GetPonyData()
    @GetPonyData()\PlayerDeath()
    net.Start('PPM2.PlayerDeath')
    net.WriteEntity(@)
    net.Broadcast()
    if ENABLE_NEW_RAGDOLLS\GetBool() and @IsPony()
        createPlayerRagdoll(@)

hook.Add 'EntityRemoved', 'PPM2.PonyDataRemove', =>
    return if @IsPlayer()
    return if not @GetPonyData()
    with @GetPonyData()
        net.Start('PPM2.PonyDataRemove')
        net.WriteUInt(.netID, 16)
        net.Broadcast()
        \Remove()

hook.Add 'PlayerDisconnected', 'PPM2.NotifyClients', =>
    @__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
    data = @GetPonyData()
    return if not data
    net.Start('PPM2.NotifyDisconnect')
    net.WriteUInt(data.netID, 16)
    net.Broadcast()

BOTS_ARE_PONIES = CreateConVar('ppm2_bots', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Whatever spawn bots as ponies')

hook.Add 'PlayerSetModel', 'PPM2.Bots', =>
    return if not BOTS_ARE_PONIES\GetBool()
    return if not @IsBot()
    @SetModel('models/ppm/player_default_base_new.mdl')
    return true

hook.Add 'PlayerSpawn', 'PPM2.Bots', =>
    return if not BOTS_ARE_PONIES\GetBool()
    return if not @IsBot()
    timer.Simple 1, ->
        return if not IsValid(@)
        @SetViewOffset(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET))
        @SetViewOffsetDucked(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET_DUCK))
        if not @GetPonyData()
            data = PPM2.NetworkedPonyData(nil, @)
            PPM2.Randomize(data)
            data\Create()

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

PPM2.PLAYER_VIEW_OFFSET = Vector(0, 0, 64 * .7)
PPM2.PLAYER_VIEW_OFFSET_DUCK = Vector(0, 0, 28 * 1.2)

PPM2.PLAYER_VIEW_OFFSET_ORIGINAL = Vector(0, 0, 64)
PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL = Vector(0, 0, 28)

hook.Add 'PlayerSpawn', 'PPM2.Hooks', =>
    if IsValid(@__ppm2_ragdoll)
        @__ppm2_ragdoll\Remove()
        @UnSpectate()
    timer.Simple 0, ->
        return unless @IsValid()
        if @GetPonyData()
            @GetPonyData()\PlayerRespawn()
            net.Start('PPM2.PlayerRespawn')
            net.WriteEntity(@)
            net.Broadcast()

        if @IsPony()
            @__ppm2_pony_view_offset = true
            @SetViewOffset(PPM2.PLAYER_VIEW_OFFSET)
            @SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK)

            return if @GetPonyData()
            timer.Simple 0.5, ->
                net.Start('PPM2.RequestPonyData')
                net.Send(@)
        else
            if @__ppm2_pony_view_offset
                @__ppm2_pony_view_offset = false
                @SetViewOffset(PPM2.PLAYER_VIEW_OFFSET_ORIGINAL)
                @SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL)

hook.Add 'PlayerInitialSpawn', 'PPM2.Hooks', =>
    timer.Simple 0, ->
        return unless @IsValid()
        return unless @IsPony()

        timer.Simple 10, ->
            net.Start('PPM2.RequestPonyData')
            net.Send(@)

do
    REQUIRE_CLIENTS = {}

    safeSendFunction = ->
        for ply, data in pairs REQUIRE_CLIENTS
            if not IsValid(ply)
                REQUIRE_CLIENTS[ply] = nil
                continue

            ent = table.remove(data, 1)
            if not ent
                REQUIRE_CLIENTS[ply] = nil
                continue

            data = ent\GetPonyData()
            continue if not data
            data\NetworkTo(ply)

    errorTrack = (err) ->
        PPM2.Message 'Networking Error: ', err
        PPM2.Message debug.traceback()

    timer.Create 'PPM2.Require', 0.25, 0, -> xpcall(safeSendFunction, errorTrack)
    net.Receive 'PPM2.Require', (len = 0, ply = NULL) ->
        return if not IsValid(ply)
        REQUIRE_CLIENTS[ply] = for ent in *ents.GetAll()
            continue if ent == ply
            data = ent\GetPonyData()
            continue if not data
            ent

net.Receive 'PPM2.EditorStatus', (len = 0, ply = NULL) ->
    return if not IsValid(ply)
    ply\SetNWBool('PPM2.InEditor', net.ReadBool())

ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')

createPlayerRagdoll = =>
    @__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
    @__ppm2_ragdoll = ents.Create('prop_ragdoll')
    rag = @GetRagdollEntity()
    rag\Remove() if IsValid(rag)
    with @__ppm2_ragdoll
        \SetModel(@GetModel())
        \SetPos(@GetPos())
        \SetAngles(@EyeAngles())
        \SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
        \Spawn()
        \Activate()
        hook.Run 'PlayerSpawnedRagdoll', @, @GetModel(), @__ppm2_ragdoll
        .__ppm2_ragdoll_parent = @
        \SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
        \SetNWBool('PPM2.IsDeathRagdoll', true)
        vel = @GetVelocity()
        \SetVelocity(vel)
        \SetAngles(@EyeAngles())
        @Spectate(OBS_MODE_CHASE)
        @SpectateEntity(@__ppm2_ragdoll)
        for boneID = 0, @__ppm2_ragdoll\GetBoneCount() - 1
            physobjID = @__ppm2_ragdoll\TranslateBoneToPhysBone(boneID)
            pos, ang = @GetBonePosition(boneID)
            physobj = @__ppm2_ragdoll\GetPhysicsObjectNum(physobjID)
            physobj\SetVelocity(vel)
            physobj\SetMass(300) -- lol
            physobj\SetPos(pos, true) if pos
            physobj\SetAngles(ang) if ang
        copy = @GetPonyData()\Clone(@__ppm2_ragdoll)
        timer.Simple 0.5, -> copy\Create() if IsValid(@__ppm2_ragdoll)

ALLOW_RAGDOLL_DAMAGE = CreateConVar('ppm2_sv_ragdoll_damage', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Should death ragdoll cause damage?')

hook.Add 'EntityTakeDamage', 'PPM2.DeathRagdoll', (dmg) =>
    attacker = dmg\GetAttacker()
    return if not IsValid(attacker)
    if attacker.__ppm2_ragdoll_parent
        dmg\SetAttacker(attacker.__ppm2_ragdoll_parent)
        if not ALLOW_RAGDOLL_DAMAGE\GetBool()
            dmg\SetDamage(0)
            dmg\SetMaxDamage(0)

hook.Add 'PostPlayerDeath', 'PPM2.Hooks', =>
    return if not @GetPonyData()
    @GetPonyData()\PlayerDeath()
    net.Start('PPM2.PlayerDeath')
    net.WriteEntity(@)
    net.Broadcast()
    if ENABLE_NEW_RAGDOLLS\GetBool() and @IsPony()
        createPlayerRagdoll(@)

hook.Add 'EntityRemoved', 'PPM2.PonyDataRemove', =>
    return if @IsPlayer()
    return if not @GetPonyData()
    with @GetPonyData()
        net.Start('PPM2.PonyDataRemove')
        net.WriteUInt(.netID, 16)
        net.Broadcast()
        \Remove()

hook.Add 'PlayerDisconnected', 'PPM2.NotifyClients', =>
    @__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
    data = @GetPonyData()
    return if not data
    net.Start('PPM2.NotifyDisconnect')
    net.WriteUInt(data.netID, 16)
    net.Broadcast()

BOTS_ARE_PONIES = CreateConVar('ppm2_bots', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Whatever spawn bots as ponies')

hook.Add 'PlayerSetModel', 'PPM2.Bots', =>
    return if not BOTS_ARE_PONIES\GetBool()
    return if not @IsBot()
    @SetModel('models/ppm/player_default_base_new.mdl')
    return true

hook.Add 'PlayerSpawn', 'PPM2.Bots', =>
    return if not BOTS_ARE_PONIES\GetBool()
    return if not @IsBot()
    timer.Simple 1, ->
        return if not IsValid(@)
        @SetViewOffset(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET))
        @SetViewOffsetDucked(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET_DUCK))
        if not @GetPonyData()
            data = PPM2.NetworkedPonyData(nil, @)
            PPM2.Randomize(data)
            data\Create()


New "Hello World" script

New "Hello World" script

-- Use with for easier usage and prettier code
with non
  .draw = ->
    -- Draw the text on specified coordinates
    .graphics.print "This is #{.project.name}", 10, 10

  .keypressed = (key) ->
    -- Quit application if Q or Escape key was pressed
    if key == "q" or key == "escape"
      .system.quit!

-- Import non.graphics and non.system for easier usage
import graphics from non
import system from non

-- Pre-declaring x and y allows to use them in draw callback
-- and to modify them in resize callback
local x, y

non.draw = ->
  -- Draw the text on specified coordinates
  graphics.print "What hath deathbeam wrought?", x, y

non.resize = (w, h) ->
  -- Calculate center of the window for text position
  x = (w - 165) / 2
  y = (h - 12) / 2

non.keypressed = (key) ->
  -- Quit application if Q or Escape key was pressed
  if key == "q" or key == "escape"
    system.quit!


imageupload: chunked upload, video

imageupload: chunked upload, video

{: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}"


Fix log not aborting if log level below saved log level.

-- Copyright (c) 2015 by Adam Hellberg.
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.

NAME, T = ...

T.log =
    levels:
        DEBUG: 0
        INFO: 1
        WARN: 2
        ERROR: 3
        FATAL: 4

import log from T

log.prefixes =
    [log.levels.DEBUG]: 'DEBUG'
    [log.levels.INFO]: 'INFO'
    [log.levels.WARN]: 'WARN'
    [log.levels.ERROR]: 'ERROR'
    [log.levels.FATAL]: 'FATAL'

msgtemplate = '[%s] [%%s]: %%s'\format NAME

log.log = (level, text, ...) =>
    return if level < T.db.main.loglevel
    msg = msgtemplate\format (@.prefixes[level] or 'UNKNOWN'), text\format ...
    DEFAULT_CHAT_FRAME\AddMessage msg
    msg\len!

for level, value in pairs log.levels
    log[level\lower!] = (text, ...) => @log value, text, ...

-- Copyright (c) 2015 by Adam Hellberg.
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.

NAME, T = ...

T.log =
    levels:
        DEBUG: 0
        INFO: 1
        WARN: 2
        ERROR: 3
        FATAL: 4

import log from T

log.prefixes =
    [log.levels.DEBUG]: 'DEBUG'
    [log.levels.INFO]: 'INFO'
    [log.levels.WARN]: 'WARN'
    [log.levels.ERROR]: 'ERROR'
    [log.levels.FATAL]: 'FATAL'

msgtemplate = '[%s] [%%s]: %%s'\format NAME

log.log = (level, text, ...) =>
    msg = msgtemplate\format (@.prefixes[level] or 'UNKNOWN'), text\format ...
    DEFAULT_CHAT_FRAME\AddMessage msg
    msg\len!

for level, value in pairs log.levels
    log[level\lower!] = (text, ...) => @log value, text, ...