github: add event pull_request_review_comment

util = require 'util'

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

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

handlers = {
  push: (repo, destination, json) ->
    branch = util.bold(json.ref\gsub 'refs/heads/', '')
    for i, c in pairs(json.commits)
      if i > 3
        break
      message = c.message\gsub '\n.*', ''
      if i == 1
        message = message .. ' ' .. json.compare
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{branch}, #{util.nonickalertword c.author.username}: #{message}"
    if #json.commits >= 4
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{branch}, #{#json.commits-3} more commits not displayed."
  watch: (repo, destination, json) ->
    action = json.action
    if action == 'started'
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: +1 ★ (Total: #{json.repository.stargazers_count})"
    else
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} watching"
  ping: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: ping"
  commit_comment: (repo, destination, json) ->
    --commenter = json.comment.user.login
    body = json.comment.body
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: <#{util.nonickalertword json.sender.login}> #{body}"
  pull_request: (repo, destination, json) ->
    action = json.action
    number = util.bold json.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} PR ##{number}: #{json.pull_request.title} #{json.pull_request.html_url}"
  pull_request_review_comment: (repo, destination, json) ->
    body = json.comment.body
    number = util.bold json.pull_request.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: PR ##{number} <#{util.nonickalertword json.sender.login}> #{body}"
  issues: (repo, destination, json) ->
    action = json.action
    nr = util.bold json.issue.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} issue ##{nr}: #{json.issue.title} #{json.issue.html_url}"
  issue_comment: (repo, destination, json) ->
    action = json.action
    nr = util.bold json.issue.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: Issue ##{nr} <#{util.nonickalertword json.sender.login}> #{json.comment.body} #{json.issue.html_url}"
  fork: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: forked to #{json.forkee.html_url}"
  create: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: created #{json.ref_type} #{util.bold json.ref}"
  delete: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: deleted #{json.ref_type} #{util.bold json.ref}"
  gollum: (repo, destination, json) ->
    return
  release: (repo, destination, json) ->
    return
}

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = unescape(destination)
  json = util.json.decode(req.body)

  repo = json.repository.full_name

  event = req.headers['X-GitHub-Event']
  handler = handlers[event]
  if handler
    handler(repo, destination, json)
  else
    ivar2\Log('error', 'Unknown github event: %s', event)

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}

util = require 'util'

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

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

handlers = {
  push: (repo, destination, json) ->
    branch = util.bold(json.ref\gsub 'refs/heads/', '')
    for i, c in pairs(json.commits)
      if i > 3
        break
      message = c.message\gsub '\n.*', ''
      if i == 1
        message = message .. ' ' .. json.compare
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{branch}, #{util.nonickalertword c.author.username}: #{message}"
    if #json.commits >= 4
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{branch}, #{#json.commits-3} more commits not displayed."
  watch: (repo, destination, json) ->
    action = json.action
    if action == 'started'
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: +1 ★ (Total: #{json.repository.stargazers_count})"
    else
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} watching"
  ping: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: ping"
  commit_comment: (repo, destination, json) ->
    --commenter = json.comment.user.login
    body = json.comment.body
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: <#{util.nonickalertword json.sender.login}> #{body}"
  pull_request: (repo, destination, json) ->
    action = json.action
    number = util.bold json.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} PR ##{number}: #{json.pull_request.title} #{json.pull_request.html_url}"
  issues: (repo, destination, json) ->
    action = json.action
    nr = util.bold json.issue.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} issue ##{nr}: #{json.issue.title} #{json.issue.html_url}"
  issue_comment: (repo, destination, json) ->
    action = json.action
    nr = util.bold json.issue.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: Issue ##{nr} <#{util.nonickalertword json.sender.login}> #{json.comment.body} #{json.issue.html_url}"
  fork: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: forked to #{json.forkee.html_url}"
  create: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: created #{json.ref_type} #{util.bold json.ref}"
  delete: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: deleted #{json.ref_type} #{util.bold json.ref}"
  gollum: (repo, destination, json) ->
    return
  release: (repo, destination, json) ->
    return
}

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = unescape(destination)
  json = util.json.decode(req.body)

  repo = json.repository.full_name

  event = req.headers['X-GitHub-Event']
  handler = handlers[event]
  if handler
    handler(repo, destination, json)
  else
    ivar2\Log('error', 'Unknown github event: %s', event)

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}


imageupload: progess bar

imageupload: progess bar

Former-commit-id: bbddefe2d8fc3183c34efd9b5d7aceaba4eddd23 [formerly cb076c50a9039a882adc93da8912f9f9d0fa65ec]
Former-commit-id: 5e0c572ef7c2fe0a2cb8d86d0bce9d99bbd314db

{: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 yellow buttons to attach image. It will appear instantly on IRC!

    <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;">
      🎥 Upload video! <input type="file" accept="video/*" id="capturevf" 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\sub(1, 100)}> "
        if text ~= ''
          text = text\sub(1, 100) .. ' '
        msg = "[IRCSNAP] #{sender}#{text}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'
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 yellow buttons to attach image. It will appear instantly on IRC!

    <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;">
      🎥 Upload video! <input type="file" accept="video/*" id="capturevf" 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}"


Keeeys

Keeeys

GuiState = require 'lib/states/gui'
WorldState = require 'lib/states/world'

class TitleState extends GuiState
	update: () =>
		super()

		@gui.layout\row(0, 200)

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

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

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

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

	startGame: () =>
		@gamestate.switch(WorldState())

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

GuiState = require 'lib/states/gui'
WorldState = require 'lib/states/world'

class TitleState extends GuiState
	update: () =>
		super()

		@gui.layout\row(0, 200)

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

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

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

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

	startGame: () =>
		@gamestate.switch(WorldState())

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


Fix typo in comm manager

Fix typo in comm manager

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

NAME, T = ...

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

MAX_MESSAGE_LENGTH = 250

local comm, log, misc

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

{localization: L} = T

T.comm_manager = do
    class CommManager extends T.CallbackManager
        prefix = NAME\upper!

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

            return unless @handlers[kind]

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

            @fire kind, channel, sender, unpack args

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

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

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

            SendAddonMessage prefix, message, channel, target

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

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

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

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

    CommManager!

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

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

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

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

NAME, T = ...

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

MAX_MESSAGE_LENGTH = 250

local comm, log, misc

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

{localization: L} = T

T.comm_manager = do
    class CommManager extends T.CallbackManager
        prefix = NAME\upper!

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

            return unless @handlers[kind]

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

            @fire kind, channel, sender, unpack args

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

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

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

            SendAddonMessage @prefix, message, channel, target

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

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

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

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

    CommManager!

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

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

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


Fixed logic problem in copy and calculate_hash.

The boolean expressions are evaluated, so if variable is nil, an error occurs.
Set the values to defaults if not provided and then make promises.

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright 2016 [bk]door.maus

glurg = require "glurg"
Promise = require "glurg.common.Promise"

class Blob
	new: (blob) =>
		if type(blob) == "userdata"
			Promise.keep("blob", Promise.IsUserdata(blob))

			@_blob = blob
			@data = blob.data
			@length = blob.length
			@is_mutable = false
		elseif type(blob) == "number"
			Promise.keep("blob >= 1", blob >= 1)

			@_blob = false
			@data = glurg.common.create_data(blob)
			@length = blob
			@is_mutable = true
		else
			@_blob = false
			@data = nil
			@length = 0
			@is_mutable = false

	get_mutable: =>
		blob = Blob(nil)
		blob.data = glurg.common.duplicate_data(@data, @length)
		blob.length = @length
		blob.is_mutable = true

		return blob

	slice: (offset) =>
		Promise.keep("offset <= @length", offset <= @length)

		return glurg.common.slice_data(@data, offset)

	copy: (other, num_bytes, offset) =>
		offset = offset or 0

		Promise.keep("other", Promise.IsClass(other, Blob))
		Promise.keep("num_bytes", Promise.IsNumber(num_bytes))
		Promise.keep("num_bytes >= 0", num_bytes >= 0)
		Promise.keep("num_bytes <= @length", num_bytes <= @length)
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("offset + num_bytes <= other.length",
			offset + num_bytes <= other.length)

		glurg.common.copy_data(@data, other\slice(offset), num_bytes)

	calculate_hash: (offset, size) =>
		offset = offset or 0
		size = size or @length

		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("size", Promise.IsNumber(size))
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("size >= 1", size >= 1)
		Promise.keep("offset + size <= @length", offset + size <= @length)

		return glurg.common.hash_data(@\slice(offset), size)\to_string!

	write_byte: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("value", Promise.IsNumber(value))
		Promise.keep("offset + 1 <= @length", offset + 1 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_byte(@data, offset, value)

	write_unsigned_byte: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 1 <= @length", offset + 1 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_unsigned_byte(@data, offset, value)

	write_short: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 2 <= @length", offset + 2 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_short(@data, offset, value)

	write_unsigned_short: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 2 <= @length", offset + 2 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_unsigned_short(@data, offset, value)

	write_integer: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_integer(@data, offset, value)

	write_unsigned_integer: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_unsigned_integer(@data, offset, value)

	write_float: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_float(@data, offset, value)

	read_byte: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 1 <= @length", offset + 1 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_byte(@data, offset)

	read_unsigned_byte: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 1 <= @length", offset + 1 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_unsigned_byte(@data, offset)

	read_short: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 2 <= @length", offset + 2 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_short(@data, offset)

	read_unsigned_short: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 2 <= @length", offset + 2 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_unsigned_short(@data, offset)

	read_integer: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_integer(@data, offset)

	read_unsigned_integer: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_unsigned_integer(@data, offset)

	read_float: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_float(@data, offset)

return Blob

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright 2016 [bk]door.maus

glurg = require "glurg"
Promise = require "glurg.common.Promise"

class Blob
	new: (blob) =>
		if type(blob) == "userdata"
			Promise.keep("blob", Promise.IsUserdata(blob))

			@_blob = blob
			@data = blob.data
			@length = blob.length
			@is_mutable = false
		elseif type(blob) == "number"
			Promise.keep("blob >= 1", blob >= 1)

			@_blob = false
			@data = glurg.common.create_data(blob)
			@length = blob
			@is_mutable = true
		else
			@_blob = false
			@data = nil
			@length = 0
			@is_mutable = false

	get_mutable: =>
		blob = Blob(nil)
		blob.data = glurg.common.duplicate_data(@data, @length)
		blob.length = @length
		blob.is_mutable = true

		return blob

	slice: (offset) =>
		Promise.keep("offset <= @length", offset <= @length)

		return glurg.common.slice_data(@data, offset)

	copy: (other, num_bytes, offset) =>
		Promise.keep("other", Promise.IsClass(other, Blob))
		Promise.keep("num_bytes", Promise.IsNumber(num_bytes))
		Promise.keep("num_bytes >= 0", num_bytes >= 0)
		Promise.keep("num_bytes <= @length", num_bytes <= @length)
		Promise.keep_any("offset", offset == nil, Promise.IsNumber(offset))
		Promise.keep_any("offset >= 0", offset == nil, offset >= 0)
		Promise.keep_any("offset + num_bytes <= other.length",
			offset == nil, offset + num_bytes <= other.length)

		offset = offset or 0
		glurg.common.copy_data(@data, other\slice(offset), num_bytes)

	calculate_hash: (offset, size) =>
		Promise.keep_any("offset", offset == nil, Promise.IsNumber(offset))
		Promise.keep_any("size", size == nil, Promise.IsNumber(size))
		Promise.keep_any("offset >= 0", offset == nil, offset >= 0)
		Promise.keep_any("size >= 1", size == nil, size >= 1)
		Promise.keep_any("offset + size <= @length",
			offset == nil, offset + size <= @length)

		offset = offset or 0
		size = size or @length

		return glurg.common.hash_data(@\slice(offset), size)\to_string!

	write_byte: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("value", Promise.IsNumber(value))
		Promise.keep("offset + 1 <= @length", offset + 1 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_byte(@data, offset, value)

	write_unsigned_byte: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 1 <= @length", offset + 1 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_unsigned_byte(@data, offset, value)

	write_short: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 2 <= @length", offset + 2 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_short(@data, offset, value)

	write_unsigned_short: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 2 <= @length", offset + 2 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_unsigned_short(@data, offset, value)

	write_integer: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_integer(@data, offset, value)

	write_unsigned_integer: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_unsigned_integer(@data, offset, value)

	write_float: (offset, value) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)
		Promise.keep("@is_mutable == true", @is_mutable)

		return glurg.common.data.write_float(@data, offset, value)

	read_byte: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 1 <= @length", offset + 1 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_byte(@data, offset)

	read_unsigned_byte: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 1 <= @length", offset + 1 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_unsigned_byte(@data, offset)

	read_short: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 2 <= @length", offset + 2 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_short(@data, offset)

	read_unsigned_short: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 2 <= @length", offset + 2 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_unsigned_short(@data, offset)

	read_integer: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_integer(@data, offset)

	read_unsigned_integer: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_unsigned_integer(@data, offset)

	read_float: (offset) =>
		Promise.keep("offset", Promise.IsNumber(offset))
		Promise.keep("offset + 4 <= @length", offset + 4 <= @length)
		Promise.keep("offset >= 0", offset >= 0)

		return glurg.common.data.read_float(@data, offset)

return Blob


Fixed weapon draw

Fixed weapon draw

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

timer.Create 'PPM2.ModelChecks', 1, 0, ->
    for ply in *player.GetAll()
        ply.__cachedIsPony = ply\IsPony()

        if ply.__cachedIsPony
            for wep in *ply\GetWeapons()
                wep\SetNoDraw(true)
                wep.__ppm2_weapon_hit = true
        else
            for wep in *ply\GetWeapons()
                continue if not wep.__ppm2_weapon_hit
                wep\SetNoDraw(false)
                ply.__ppm2_weapon_hit = false

PPM2.PostDrawOpaqueRenderables = (a, b) ->
    return if a or b
    lply = LocalPlayer()

    for ply in *player.GetAll()
        alive = ply\Alive()
        ply.__ppm2_last_dead = RealTime() + 2 if not alive
        if ply.__cachedIsPony
            if ply\GetPonyData() and not alive
                data = ply\GetPonyData()
                rag = ply\GetRagdollEntity()
                if IsValid(rag)
                    rag\SetNoDraw(true)
                    renderController = data\GetRenderController()
                    data\DoRagdollMerge()
                    if renderController
                        renderController\PreDraw(rag)
                        rag\DrawModel()
                        renderController\PostDraw(rag)

PPM2.PrePlayerDraw = =>
    return unless @GetPonyData()
    @__cachedIsPony = @IsPony()
    return if not @__cachedIsPony
    return if @__ppm2_last_draw == FrameNumber()
    @__ppm2_last_draw = FrameNumber()
    return if @IsDormant()
    @__ppm2_last_dead = @__ppm2_last_dead or 0
    return if @__ppm2_last_dead > RealTime()
    data = @GetPonyData()
    renderController = data\GetRenderController()
    status = renderController\PreDraw() if renderController
    return status if status ~= nil
PPM2.PostPlayerDraw = =>
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    renderController\PostDraw() if renderController

hook.Add 'PrePlayerDraw', 'PPM2.PlayerDraw', PPM2.PrePlayerDraw, -2
hook.Add 'PostPlayerDraw', 'PPM2.PostPlayerDraw', PPM2.PostPlayerDraw, -2
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.PostDrawOpaqueRenderables', PPM2.PostDrawOpaqueRenderables, 2
hook.Add 'RenderScreenspaceEffects', 'PPM2.RenderScreenspaceEffects', ->
    self = LocalPlayer()

    if @__cachedIsPony and @GetPonyData() and @Alive()
        @GetPonyData()\GetRenderController()\DrawLegs()

SHOULD_DRAW_VIEWMODEL = CreateConVar('cl_ppm2_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
    return unless IsValid(arms)
    return unless ply.__cachedIsPony
    return unless ply\Alive()
    data = ply\GetPonyData()
    return unless data
    status = data\GetRenderController()\PreDrawArms(arms)
    return status if status ~= nil
    arms.__ppm2_draw = true

hook.Add 'PostDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return unless IsValid(arms)
    return unless arms.__ppm2_draw
    data = ply\GetPonyData()
    return unless data
    data\GetRenderController()\PostDrawArms(arms)
    arms.__ppm2_draw = false

PlayerRespawn = ->
    ent = net.ReadEntity()
    return if not IsValid(ent)
    return if not ent\GetPonyData()
    ent\GetPonyData()\PlayerRespawn()

PlayerDeath = ->
    ent = net.ReadEntity()
    return if not IsValid(ent)
    return if not ent\GetPonyData()
    ent\GetPonyData()\PlayerDeath()

lastDataSend = 0
lastDataReceived = 0
net.Receive 'PPM2.RequestPonyData', ->
    return if lastDataReceived > RealTime()
    lastDataReceived = RealTime() + 10
    RunConsoleCommand('ppm2_reload')

net.Receive 'PPM2.PlayerRespawn', PlayerRespawn
net.Receive 'PPM2.PlayerDeath', PlayerDeath

concommand.Add 'ppm2_require', ->
    net.Start('PPM2.Require')
    net.SendToServer()
    PPM2.Message 'Requesting pony data...'

concommand.Add 'ppm2_reload', ->
    return if lastDataSend > RealTime()
    lastDataSend = RealTime() + 10
    instance = PPM2.GetMainData()
    newData = instance\CreateNetworkObject()
    newData\Create()
    instance\SetNetworkData(newData)
    PPM2.Message 'Sending pony data to server...'

hook.Add 'KeyPress', 'PPM2.RequireData', ->
    hook.Remove 'KeyPress', 'PPM2.RequireData'
    RunConsoleCommand('ppm2_reload')
    timer.Simple 3, -> RunConsoleCommand('ppm2_require')

PPM_HINT_COLOR_FIRST = Color(255, 255, 255)
PPM_HINT_COLOR_SECOND = Color(0, 0, 0)

hook.Add 'HUDPaint', 'PPM2.EditorStatus', ->
    lply = LocalPlayer()
    lpos = lply\EyePos()
    for ply in *player.GetAll()
        if ply ~= lply
            if ply\GetNWBool('PPM2.InEditor')
                pos = ply\EyePos()
                dist = pos\Distance(lpos)
                if dist < 250
                    pos.z += 10
                    alpha = math.Clamp(1 - dist / 250, 0.1, 1)
                    {:x, :y} = pos\ToScreen()
                    draw.DrawText('In PPM/2 Editor', 'HudHintTextLarge', x + 1, y + 1, PPM_HINT_COLOR_SECOND, TEXT_ALIGN_CENTER)
                    draw.DrawText('In PPM/2 Editor', 'HudHintTextLarge', x, y, PPM_HINT_COLOR_FIRST, TEXT_ALIGN_CENTER)

concommand.Add 'ppm2_cleanup', ->
    for ent in *ents.GetAll()
        if ent.isPonyPropModel and not IsValid(ent.manePlayer)
            ent\Remove()
    PPM2.Message('All unused models were removed')

timer.Create 'PPM2.ModelCleanup', 60, 0, ->
    for ent in *ents.GetAll()
        if ent.isPonyPropModel and not IsValid(ent.manePlayer)
            ent\Remove()

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

timer.Create 'PPM2.ModelChecks', 1, 0, ->
    for ply in *player.GetAll()
        ply.__cachedIsPony = ply\IsPony()

        if ply.__cachedIsPony
            draw = ply ~= lply or lply\ShouldDrawLocalPlayer()
            for wep in *ply\GetWeapons()
                wep\SetNoDraw(draw)
                wep.__ppm2_weapon_hit = true
        else
            for wep in *ply\GetWeapons()
                continue if not wep.__ppm2_weapon_hit
                wep\SetNoDraw(false)
                print wep
                ply.__ppm2_weapon_hit = false

PPM2.PostDrawOpaqueRenderables = (a, b) ->
    return if a or b
    lply = LocalPlayer()

    for ply in *player.GetAll()
        alive = ply\Alive()
        ply.__ppm2_last_dead = RealTime() + 2 if not alive
        if ply.__cachedIsPony
            if ply\GetPonyData() and not alive
                data = ply\GetPonyData()
                rag = ply\GetRagdollEntity()
                if IsValid(rag)
                    rag\SetNoDraw(true)
                    renderController = data\GetRenderController()
                    data\DoRagdollMerge()
                    if renderController
                        renderController\PreDraw(rag)
                        rag\DrawModel()
                        renderController\PostDraw(rag)

PPM2.PrePlayerDraw = =>
    return unless @GetPonyData()
    @__cachedIsPony = @IsPony()
    return if not @__cachedIsPony
    return if @__ppm2_last_draw == FrameNumber()
    @__ppm2_last_draw = FrameNumber()
    return if @IsDormant()
    @__ppm2_last_dead = @__ppm2_last_dead or 0
    return if @__ppm2_last_dead > RealTime()
    data = @GetPonyData()
    renderController = data\GetRenderController()
    status = renderController\PreDraw() if renderController
    return status if status ~= nil
PPM2.PostPlayerDraw = =>
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    renderController\PostDraw() if renderController

hook.Add 'PrePlayerDraw', 'PPM2.PlayerDraw', PPM2.PrePlayerDraw, -2
hook.Add 'PostPlayerDraw', 'PPM2.PostPlayerDraw', PPM2.PostPlayerDraw, -2
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.PostDrawOpaqueRenderables', PPM2.PostDrawOpaqueRenderables, 2
hook.Add 'RenderScreenspaceEffects', 'PPM2.RenderScreenspaceEffects', ->
    self = LocalPlayer()

    if @__cachedIsPony and @GetPonyData() and @Alive()
        @GetPonyData()\GetRenderController()\DrawLegs()

SHOULD_DRAW_VIEWMODEL = CreateConVar('cl_ppm2_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
    return unless IsValid(arms)
    return unless ply.__cachedIsPony
    return unless ply\Alive()
    data = ply\GetPonyData()
    return unless data
    status = data\GetRenderController()\PreDrawArms(arms)
    return status if status ~= nil
    arms.__ppm2_draw = true

hook.Add 'PostDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return unless IsValid(arms)
    return unless arms.__ppm2_draw
    data = ply\GetPonyData()
    return unless data
    data\GetRenderController()\PostDrawArms(arms)
    arms.__ppm2_draw = false

PlayerRespawn = ->
    ent = net.ReadEntity()
    return if not IsValid(ent)
    return if not ent\GetPonyData()
    ent\GetPonyData()\PlayerRespawn()

PlayerDeath = ->
    ent = net.ReadEntity()
    return if not IsValid(ent)
    return if not ent\GetPonyData()
    ent\GetPonyData()\PlayerDeath()

lastDataSend = 0
lastDataReceived = 0
net.Receive 'PPM2.RequestPonyData', ->
    return if lastDataReceived > RealTime()
    lastDataReceived = RealTime() + 10
    RunConsoleCommand('ppm2_reload')

net.Receive 'PPM2.PlayerRespawn', PlayerRespawn
net.Receive 'PPM2.PlayerDeath', PlayerDeath

concommand.Add 'ppm2_require', ->
    net.Start('PPM2.Require')
    net.SendToServer()
    PPM2.Message 'Requesting pony data...'

concommand.Add 'ppm2_reload', ->
    return if lastDataSend > RealTime()
    lastDataSend = RealTime() + 10
    instance = PPM2.GetMainData()
    newData = instance\CreateNetworkObject()
    newData\Create()
    instance\SetNetworkData(newData)
    PPM2.Message 'Sending pony data to server...'

hook.Add 'KeyPress', 'PPM2.RequireData', ->
    hook.Remove 'KeyPress', 'PPM2.RequireData'
    RunConsoleCommand('ppm2_reload')
    timer.Simple 3, -> RunConsoleCommand('ppm2_require')

PPM_HINT_COLOR_FIRST = Color(255, 255, 255)
PPM_HINT_COLOR_SECOND = Color(0, 0, 0)

hook.Add 'HUDPaint', 'PPM2.EditorStatus', ->
    lply = LocalPlayer()
    lpos = lply\EyePos()
    for ply in *player.GetAll()
        if ply ~= lply
            if ply\GetNWBool('PPM2.InEditor')
                pos = ply\EyePos()
                dist = pos\Distance(lpos)
                if dist < 250
                    pos.z += 10
                    alpha = math.Clamp(1 - dist / 250, 0.1, 1)
                    {:x, :y} = pos\ToScreen()
                    draw.DrawText('In PPM/2 Editor', 'HudHintTextLarge', x + 1, y + 1, PPM_HINT_COLOR_SECOND, TEXT_ALIGN_CENTER)
                    draw.DrawText('In PPM/2 Editor', 'HudHintTextLarge', x, y, PPM_HINT_COLOR_FIRST, TEXT_ALIGN_CENTER)

concommand.Add 'ppm2_cleanup', ->
    for ent in *ents.GetAll()
        if ent.isPonyPropModel and not IsValid(ent.manePlayer)
            ent\Remove()
    PPM2.Message('All unused models were removed')

timer.Create 'PPM2.ModelCleanup', 60, 0, ->
    for ent in *ents.GetAll()
        if ent.isPonyPropModel and not IsValid(ent.manePlayer)
            ent\Remove()


Add slash command to prime bot.

Add slash command to prime bot.

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

import markov from T

T.init = () =>
    T.db\init!

T.generate_local = (seed, max) =>
    print markov\create seed, max

export SLASH_MARKOVBOT1 = '/markov'
export SLASH_MARKOVBOT2 = '/mb'

SlashCmdList['MARKOVBOT'] = (msg, editbox) ->
    markov\save msg
    T\generate_local msg

export MarkovBot = T

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

import markov from T

T.init = () =>
    T.db\init!

T.generate_local = (seed, max) =>
    print markov\create seed, max

export MarkovBot = T


Add Tween test for cancel

Add Tween test for cancel

require "busted"
require "tests.mock_love"

require "LunoPunk.Tween"

-- This is how the Tweener updates
class tween extends Tween
	update: =>
		super!
		@finish! if @__finish

describe "Tween", ->
	setup ->
		-- We used fixed time for testing
		LP.fixed = true

	it "construct", ->
		assert.has.errors -> tween!
		twn = tween 500
		assert.is.False twn.active
		assert.is.Nil twn.ease
		assert.are.equal TweenType.Persist, twn.type
		assert.are.equal 0, twn.t

	it "update (persist)", ->
		twn = tween 5
		twn\start!
		assert.is.True twn.active
		twn\update!
		assert.are.equal 1/5, twn.t
		twn\update!
		assert.are.equal 2/5, twn.t
		-- Finish up the twn
		twn\update!
		twn\update!
		twn\update!
		-- t loops back to 0 to start again
		assert.are.equal 0, twn.t
		assert.is.False twn.active

		twn = tween 2, TweenType.Persist, nil, (t) => t + 1
		twn\update!
		assert.is.False twn.active

	it "update (looping)", ->
		twn = tween 2, TweenType.Looping
		twn\start!
		for i=1, 33
			assert.is.True twn.active
			assert.are.equal (i-1)%2/2, twn.t
			twn\update!

	it "update (oneshot)", ->
		twn = tween 2, TweenType.OneShot
		twn\start!

		assert.is.True twn.active
		assert.are.equal 0, twn.t
		twn\update!

		assert.is.True twn.active
		assert.are.equal 1/2, twn.t
		twn\update!

		assert.is.False twn.active

	it "complete callback", ->

	it "cancel", ->
		-- Assert if the complete callback is called
		twn = tween 2, TweenType.Persist, -> assert false, "Tween callback called"
		twn\start!

		assert.is.True twn.active
		twn\update!
		assert.is.True twn.active
		twn\cancel!
		assert.is.False twn.active
		assert.has_no.errors -> twn\update!