Disable console in production

Disable console in production

It disables itself, but let's be cautious

lapis = require "lapis"
config = (require "lapis.config").get!
content = require "content"
logger = require "lapis.logging"
console = require "lapis.console" if config._name == 'development' or config._name == 'development-perftest'
csrf = require "lapis.csrf"
submit = require "submit_application"

import after_dispatch from require "lapis.nginx.context"
import to_json from require "lapis.util"
import capture_errors, assert_error, yield_error, respond_to from require "lapis.application"
import json_requested from require "utils"

capture_form_errors = (fn) ->
    capture_errors {
        on_error: (err, trace) =>
            err or= @errors
            if @json
                resp = if @m and @m.form and @m.form.responses and @m.form.responses.default
                    @m.form.responses.default
                else
                    '
A server error has occurred, and the error text could not be fetched.
'

                print to_json err
                { status: 500, json: { response: resp, errors: { } } }
            else
                @app.handle_error self, err, trace, 500

        fn
    }

safe_route = (fn) ->
    capture_errors {
        on_error: =>
            @app.handle_error self, @errors

        =>
            fn self
    }

class CSWeek extends lapis.Application
    layout: require "views.layout"
    error_page: require "views.error"

    handle_error: (err, trace, code = 500) =>
        r = @app.Request @app, @req, @res

        with r
            .m, errpg = content\get "error"
            if errpg then
                .m = { }
                .m[code] = "Could not load the error page (code #{code}): #{errpg}
When handling #{err}
#{trace}"

            .m[code] = .m[code] or .m.default or { }
            .page_id = "error"
            .status = code
            .error = err
            .traceback = trace
            .m.title = .m[code].title

            \write {
                status: code
                content_type: "text/html"
                render: "error"
            }
            \render!
            logger.request r
        r

    try_render: (template, context) =>
        context = context or self
        with context
            .nav = assert_error content\get "nav"
            .footer = assert_error content\get "footer"
        render: template

    respond_to_form: (err, model, page_id, template) =>
        resp = { }

        status = if not err
                200
            elseif err[1] == 'internal_error'
                500
            elseif err[1] == 'duplicate_application'
                409
            elseif err[1] == 'too_frequent'
                403
            elseif err[1] == 'file_too_big'
                419
            else
                400

        resp.response = model.form.responses[status] or model.form.responses.default if model.form.responses

        if status == 400
            resp.errors = { }
            for e in *err
                table.insert resp.errors, model.form.validation_errors[e] or 'unknown error'
        if @json
            { :status, json: resp }
        else
            @resp = resp
            @page_id = page_id
            ret = @app\try_render template, self
            ret.status = status
            ret

    @before_filter =>
        if #[n for n in *{'production-perftest', 'development-perftest'} when n == config._name] > 0
            after_dispatch ->
                print to_json(ngx.ctx.performance)

    [home: "/"]: safe_route =>
        @page_id = "home"
        @m = assert_error content\get "home"
        @lecturers = assert_error content\get "lecturers"
        @app\try_render "home", self

    [about: "/o-nedelji"]: safe_route =>
        @page_id = "about"
        @m = assert_error content\get "about"
        @app\try_render "about", self

    [lecturers: "/predavaci"]: safe_route =>
        @page_id = "lecturers"
        @m = assert_error content\get "lecturers_page"
        @lecturers = assert_error content\get "lecturers"
        @app\try_render "lecturers", self

    [lecturer: "/predavaci/:name"]: safe_route =>
        @lecturers_page = assert_error content\get "lecturers_page"
        @page_id = "lecturer"
        lecturers = assert_error content\get "lecturers"
        results = [l for l in *lecturers when l.id == @params.name]
        if #results > 1
            yield_error "Multiple lecturer id's found for id `#{@params.name}`"
        else if #results == 0
            @app.handle_error self, "No such lecturer: `#{@params.name}`", nil, 404
            return

        @m = results[1]
        @app\try_render "lecturer", self

    [apply: "/prijava"]: respond_to {
        GET: safe_route =>
            @page_id = "apply"
            @m = assert_error content\get "apply"
            @csrf_token = csrf.generate_token @
            @app\try_render "apply", self

        POST: capture_form_errors =>
                @json = json_requested @
                model = assert_error content\get "apply"
                @m = model

                this = self
                succ, ret, err = pcall ->
                    submit\submit this.res.req.params_post, model, (...) ->
                        this\build_url ...

                yield_error ret if not succ
                @app.respond_to_form self, err, model, 'apply', 'apply-result'
    }

    [apply_upload: "/prijava/upload/*"]: respond_to {
        GET: safe_route =>
            @page_id = "apply"
            @m = assert_error content\get "apply-upload"
            @a = assert_error content\get "apply"
            @csrf_token = csrf.generate_token @
            if @application = submit\get_application @params.splat
                @app\try_render "apply-upload", self
            else
                redirect_to: '/prijava'

        POST: capture_form_errors =>
            @json = json_requested @
            model = assert_error content\get 'apply-upload'
            apply_model = assert_error content\get 'apply'
            @m = model

            this = self
            succ, ret, err = pcall ->
                submit\upload this.res.req.params_post, @params.splat, model, apply_model.tasks

            yield_error ret if not succ

            @app.respond_to_form self, err, model, 'apply', 'apply-result'
    }

    [console: "/console"]: console and console.make! or nil

    [materials: "/materijali"]: safe_route =>
        @page_id = "materials"
        @m = assert_error content\get "materials"
        @app\try_render "materials", self

    handle_404: =>
        @app.handle_error self, "Route `#{self.req.parsed_url.path or 'unknown'}` not found", nil, 404

lapis = require "lapis"
config = (require "lapis.config").get!
content = require "content"
logger = require "lapis.logging"
console = require "lapis.console" if config._name == 'development' or config._name == 'development-perftest'
csrf = require "lapis.csrf"
submit = require "submit_application"

import after_dispatch from require "lapis.nginx.context"
import to_json from require "lapis.util"
import capture_errors, assert_error, yield_error, respond_to from require "lapis.application"
import json_requested from require "utils"

capture_form_errors = (fn) ->
    capture_errors {
        on_error: (err, trace) =>
            err or= @errors
            if @json
                resp = if @m and @m.form and @m.form.responses and @m.form.responses.default
                    @m.form.responses.default
                else
                    '
A server error has occurred, and the error text could not be fetched.
'

                print to_json err
                { status: 500, json: { response: resp, errors: { } } }
            else
                @app.handle_error self, err, trace, 500

        fn
    }

safe_route = (fn) ->
    capture_errors {
        on_error: =>
            @app.handle_error self, @errors

        =>
            fn self
    }

class CSWeek extends lapis.Application
    layout: require "views.layout"
    error_page: require "views.error"

    handle_error: (err, trace, code = 500) =>
        r = @app.Request @app, @req, @res

        with r
            .m, errpg = content\get "error"
            if errpg then
                .m = { }
                .m[code] = "Could not load the error page (code #{code}): #{errpg}
When handling #{err}
#{trace}"

            .m[code] = .m[code] or .m.default or { }
            .page_id = "error"
            .status = code
            .error = err
            .traceback = trace
            .m.title = .m[code].title

            \write {
                status: code
                content_type: "text/html"
                render: "error"
            }
            \render!
            logger.request r
        r

    try_render: (template, context) =>
        context = context or self
        with context
            .nav = assert_error content\get "nav"
            .footer = assert_error content\get "footer"
        render: template

    respond_to_form: (err, model, page_id, template) =>
        resp = { }

        status = if not err
                200
            elseif err[1] == 'internal_error'
                500
            elseif err[1] == 'duplicate_application'
                409
            elseif err[1] == 'too_frequent'
                403
            elseif err[1] == 'file_too_big'
                419
            else
                400

        resp.response = model.form.responses[status] or model.form.responses.default if model.form.responses

        if status == 400
            resp.errors = { }
            for e in *err
                table.insert resp.errors, model.form.validation_errors[e] or 'unknown error'
        if @json
            { :status, json: resp }
        else
            @resp = resp
            @page_id = page_id
            ret = @app\try_render template, self
            ret.status = status
            ret

    @before_filter =>
        if #[n for n in *{'production-perftest', 'development-perftest'} when n == config._name] > 0
            after_dispatch ->
                print to_json(ngx.ctx.performance)

    [home: "/"]: safe_route =>
        @page_id = "home"
        @m = assert_error content\get "home"
        @lecturers = assert_error content\get "lecturers"
        @app\try_render "home", self

    [about: "/o-nedelji"]: safe_route =>
        @page_id = "about"
        @m = assert_error content\get "about"
        @app\try_render "about", self

    [lecturers: "/predavaci"]: safe_route =>
        @page_id = "lecturers"
        @m = assert_error content\get "lecturers_page"
        @lecturers = assert_error content\get "lecturers"
        @app\try_render "lecturers", self

    [lecturer: "/predavaci/:name"]: safe_route =>
        @lecturers_page = assert_error content\get "lecturers_page"
        @page_id = "lecturer"
        lecturers = assert_error content\get "lecturers"
        results = [l for l in *lecturers when l.id == @params.name]
        if #results > 1
            yield_error "Multiple lecturer id's found for id `#{@params.name}`"
        else if #results == 0
            @app.handle_error self, "No such lecturer: `#{@params.name}`", nil, 404
            return

        @m = results[1]
        @app\try_render "lecturer", self

    [apply: "/prijava"]: respond_to {
        GET: safe_route =>
            @page_id = "apply"
            @m = assert_error content\get "apply"
            @csrf_token = csrf.generate_token @
            @app\try_render "apply", self

        POST: capture_form_errors =>
                @json = json_requested @
                model = assert_error content\get "apply"
                @m = model

                this = self
                succ, ret, err = pcall ->
                    submit\submit this.res.req.params_post, model, (...) ->
                        this\build_url ...

                yield_error ret if not succ
                @app.respond_to_form self, err, model, 'apply', 'apply-result'
    }

    [apply_upload: "/prijava/upload/*"]: respond_to {
        GET: safe_route =>
            @page_id = "apply"
            @m = assert_error content\get "apply-upload"
            @a = assert_error content\get "apply"
            @csrf_token = csrf.generate_token @
            if @application = submit\get_application @params.splat
                @app\try_render "apply-upload", self
            else
                redirect_to: '/prijava'

        POST: capture_form_errors =>
            @json = json_requested @
            model = assert_error content\get 'apply-upload'
            apply_model = assert_error content\get 'apply'
            @m = model

            this = self
            succ, ret, err = pcall ->
                submit\upload this.res.req.params_post, @params.splat, model, apply_model.tasks

            yield_error ret if not succ

            @app.respond_to_form self, err, model, 'apply', 'apply-result'
    }

    [console: "/console"]: console.make!

    [materials: "/materijali"]: safe_route =>
        @page_id = "materials"
        @m = assert_error content\get "materials"
        @app\try_render "materials", self

    handle_404: =>
        @app.handle_error self, "Route `#{self.req.parsed_url.path or 'unknown'}` not found", nil, 404


stonks: memes

stonks: memes

util = require'util'
simplehttp = util.simplehttp
json = util.json
urlEncode = util.urlEncode

quote = (a) ->
  data = simplehttp "https://query1.finance.yahoo.com/v10/finance/quoteSummary/#{a}?modules=price"
  data = json.decode(data)
  res = data['quoteSummary']['result'][1]
  price = res.price

  out = {}

  c =  price.currency
  if price.currencySymbol
    c = price.currencySymbol
  out[#out+1] = c

  current_price = price.regularMarketPrice.fmt
  current_per = price.regularMarketChangePercent.fmt
  current_raw_per = price.regularMarketChangePercent.raw
  regularmarket = true
  premarket = false
  postmarket = false
  if price.preMarketTime
    if price.preMarketTime > price.regularMarketTime
      current_price = price.preMarketPrice.fmt
      current_per = price.preMarketChangePercent.fmt
      current_raw_per = price.preMarketChangePercent.raw
      premarket = true
    elseif price.postMarketTime and price.postMarketTime > price.regularMarketTime
      current_price = price.postMarketPrice.fmt
      current_per = price.postMarketChangePercent.fmt
      current_raw_per = price.postMarketChangePercent.raw
      postmarket = true

  out[#out+1] = current_price
  if current_raw_per > 0
    current_per = util.green current_per
  else
    current_per = util.red current_per
  out[#out+1] = "("..current_per ..")"

  --if price.preMarketPrice.fmt
  --  out[#out+1] = "("..price.preMarketPrice.fmt..")"

  --if price.postMarketPrice.fmt
  --  out[#out+1] = "("..price.postMarketPrice.fmt..")"

  return table.concat(out, ' ')

sayquote = (s, d, a) =>
  say quote(a)

meme = (s, d, a) =>
  stonks = {'GME', 'AMC', 'BB', 'NOK'}
  out = {}
  for stonk in *stonks
    out[#out+1] = stonk .. ' ' .. quote(stonk)

  say table.concat(out, ' ')

PRIVMSG:
  '^%pq (.*)$': sayquote
  '^%pstonks': meme

util = require'util'
simplehttp = util.simplehttp
json = util.json
urlEncode = util.urlEncode

quote = (s, d, a) =>
  data = simplehttp "https://query1.finance.yahoo.com/v10/finance/quoteSummary/#{a}?modules=price"
  data = json.decode(data)
  res = data['quoteSummary']['result'][1]
  price = res.price

  out = {}

  c =  price.currency
  if price.currencySymbol
    c = price.currencySymbol
  out[#out+1] = c

  current_price = price.regularMarketPrice.fmt
  current_per = price.regularMarketChangePercent.fmt
  current_raw_per = price.regularMarketChangePercent.raw
  regularmarket = true
  premarket = false
  postmarket = false
  if price.preMarketTime
    if price.preMarketTime > price.regularMarketTime
      current_price = price.preMarketPrice.fmt
      current_per = price.preMarketChangePercent.fmt
      current_raw_per = price.preMarketChangePercent.raw
      premarket = true
    elseif price.postMarketTime and price.postMarketTime > price.regularMarketTime
      current_price = price.postMarketPrice.fmt
      current_per = price.postMarketChangePercent.fmt
      current_raw_per = price.postMarketChangePercent.raw
      postmarket = true

  out[#out+1] = current_price
  if current_raw_per > 0
    current_per = util.green current_per
  else
    current_per = util.red current_per
  out[#out+1] = "("..current_per ..")"

  --if price.preMarketPrice.fmt
  --  out[#out+1] = "("..price.preMarketPrice.fmt..")"

  --if price.postMarketPrice.fmt
  --  out[#out+1] = "("..price.postMarketPrice.fmt..")"

  say table.concat(out, ' ')

PRIVMSG:
  '^%pq (.*)$': quote


textutils: lower chans, add hex func

textutils: lower chans, add hex func

diff --git a/modules/textutils.moon b/modules/textutils.moon
index 73bb5d8..5d1bc21 100644
--- a/modules/textutils.moon
+++ b/modules/textutils.moon
@@ -1,4 +1,3 @@
-
 PRIVMSG:
   '^%pupper (.+)$': (source, destination, arg) =>
     say arg\upper!
@@ -9,7 +8,7 @@ PRIVMSG:
   '^%plen (.+)$': (source, destination, arg) =>
     say tostring(arg\len!)
   '^%pnicks$': (source, destination) =>
-    say table.concat([ivar2.util.nonickalert(ivar2.channels[destination].nicks, n) for n,k in pairs(ivar2.channels[destination].nicks)], ' ')
+    say table.concat([ivar2.util.nonickalert(ivar2.channels[destination\lower!].nicks, n) for n,k in pairs(ivar2.channels[destination\lower!].nicks)], ' ')
   '^%prandom (.+)$': (source, destination, arg) =>
     words = [word for word in arg\gmatch('%S+')]
     say words[math.random(1, #words)]
@@ -44,3 +43,9 @@ PRIVMSG:
         return w)
   '^%prot13 (.+)$': (source, destination, arg) =>
     say ivar2.util.rot13(arg)
+  '^%phex (.+)$': (source, destination, arg) =>
+    say arg\gsub '.', (b) ->
+      ('%02x ')\format(b\byte!)
+  --'^%prepeat (%d+) (.*)$': (source, destination, nr, command) =>
+  --  for i=1, nr
+  --    @DispatchCommand 'PRIVMSG', command, source, destination

Former-commit-id: 5fb80c48cff076700ecf31424e41c4e70e2672e0
Former-commit-id: c0f68c0c666650424a5d1350622f63685ddae132

PRIVMSG:
  '^%pupper (.+)$': (source, destination, arg) =>
    say arg\upper!
  '^%plower (.+)$': (source, destination, arg) =>
    say arg\lower!
  '^%preverse (.+)$': (source, destination, arg) =>
    say arg\reverse!
  '^%plen (.+)$': (source, destination, arg) =>
    say tostring(arg\len!)
  '^%pnicks$': (source, destination) =>
    say table.concat([ivar2.util.nonickalert(ivar2.channels[destination\lower!].nicks, n) for n,k in pairs(ivar2.channels[destination\lower!].nicks)], ' ')
  '^%prandom (.+)$': (source, destination, arg) =>
    words = [word for word in arg\gmatch('%S+')]
    say words[math.random(1, #words)]
  '^%pbold (.+)$': (source, destination, arg) =>
    say ivar2.util.bold(arg)
  '^%punderline (.+)$': (source, destination, arg) =>
    say ivar2.util.underline(arg)
  '^%pitalic (.+)$': (source, destination, arg) =>
    say ivar2.util.italic(arg)
  '^%pinvert (.+)$': (source, destination, arg) =>
    say ivar2.util.reverse(arg)
  '^%ptrim (.+)$': (source, destination, arg) =>
    say ivar2.util.trim(arg)
  '^%pcolor ([0-9]+) (.+)$': (source, destination, color, arg) =>
    say ivar2.util.color(arg, color)
  '^%pfirst (.+)$': (source, destination, arg) =>
    say ivar2.util.split(arg, '%s')[1]
  '^%psplit (.-) (.*)$': (source, destination, arg, args) =>
    say table.concat(ivar2.util.split(args, arg), ' ')
  '^%preplace (.-) (.-) (.*)$': (source, destination, pat, repl, arg) =>
    new, n = string.gsub(arg, pat, repl)
    say(new)
  '^%pnocolor (.*)$': (source, destination, arg) =>
    say ivar2.util.stripformatting(arg)
  '^%pstutter (.*)$': (source, destination, arg) =>
    -- Stutter by anders from luabot
    s_senpai = 0.65
    say arg\gsub("(%a[%w%p]+)", (w) =>
      if math.random! >= s_senpai
        return (w\sub(1, 1).."-")\rep(math.random(1, 3))..w
      else
        return w)
  '^%prot13 (.+)$': (source, destination, arg) =>
    say ivar2.util.rot13(arg)
  '^%phex (.+)$': (source, destination, arg) =>
    say arg\gsub '.', (b) ->
      ('%02x ')\format(b\byte!)
  --'^%prepeat (%d+) (.*)$': (source, destination, nr, command) =>
  --  for i=1, nr
  --    @DispatchCommand 'PRIVMSG', command, source, destination

PRIVMSG:
  '^%pupper (.+)$': (source, destination, arg) =>
    say arg\upper!
  '^%plower (.+)$': (source, destination, arg) =>
    say arg\lower!
  '^%preverse (.+)$': (source, destination, arg) =>
    say arg\reverse!
  '^%plen (.+)$': (source, destination, arg) =>
    say tostring(arg\len!)
  '^%pnicks$': (source, destination) =>
    say table.concat([ivar2.util.nonickalert(ivar2.channels[destination].nicks, n) for n,k in pairs(ivar2.channels[destination].nicks)], ' ')
  '^%prandom (.+)$': (source, destination, arg) =>
    words = [word for word in arg\gmatch('%S+')]
    say words[math.random(1, #words)]
  '^%pbold (.+)$': (source, destination, arg) =>
    say ivar2.util.bold(arg)
  '^%punderline (.+)$': (source, destination, arg) =>
    say ivar2.util.underline(arg)
  '^%pitalic (.+)$': (source, destination, arg) =>
    say ivar2.util.italic(arg)
  '^%pinvert (.+)$': (source, destination, arg) =>
    say ivar2.util.reverse(arg)
  '^%ptrim (.+)$': (source, destination, arg) =>
    say ivar2.util.trim(arg)
  '^%pcolor ([0-9]+) (.+)$': (source, destination, color, arg) =>
    say ivar2.util.color(arg, color)
  '^%pfirst (.+)$': (source, destination, arg) =>
    say ivar2.util.split(arg, '%s')[1]
  '^%psplit (.-) (.*)$': (source, destination, arg, args) =>
    say table.concat(ivar2.util.split(args, arg), ' ')
  '^%preplace (.-) (.-) (.*)$': (source, destination, pat, repl, arg) =>
    new, n = string.gsub(arg, pat, repl)
    say(new)
  '^%pnocolor (.*)$': (source, destination, arg) =>
    say ivar2.util.stripformatting(arg)
  '^%pstutter (.*)$': (source, destination, arg) =>
    -- Stutter by anders from luabot
    s_senpai = 0.65
    say arg\gsub("(%a[%w%p]+)", (w) =>
      if math.random! >= s_senpai
        return (w\sub(1, 1).."-")\rep(math.random(1, 3))..w
      else
        return w)
  '^%prot13 (.+)$': (source, destination, arg) =>
    say ivar2.util.rot13(arg)


Fixed pony sounds playing on non pony

Fixed pony sounds playing on non pony

-- Copyright (C) 2017-2018 DBot

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

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

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

net.pool('ppm2_workaround_emitsound') if SERVER

SOUND_STRINGS_POOL = {}
SOUND_STRINGS_POOL_INV = {}
AddSoundString = (sound) ->
	nextid = #SOUND_STRINGS_POOL_INV + 1
	SOUND_STRINGS_POOL[sound] = nextid
	SOUND_STRINGS_POOL_INV[nextid] = sound

class PPM2.MaterialSoundEntry
	@REGISTRIES = {}

	@Ask = (matType = MAT_DEFAULT) =>
		for reg in *@REGISTRIES
			if reg.material == matType
				return reg

		return false

	new: (name, material, variantsWalk = 0, variantsRun = 0, variantsWander = 0) =>
		table.insert(@@REGISTRIES, @)
		@name = name
		@material = material
		@variantsWalk = variantsWalk
		@variantsRun = variantsRun
		@variantsWander = variantsWander
		@variantsLand = 0
		@playHoofclap = true
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. i .. '.ogg') for i = 1, @variantsWalk
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_run' .. i .. '.ogg') for i = 1, @variantsRun
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_wander' .. i .. '.ogg') for i = 1, @variantsWander

	ShouldPlayHoofclap: => @playHoofclap

	DisableHoofclap: =>
		@playHoofclap = false
		return @

	GetWalkSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. math.random(1, @variantsWalk) .. '.ogg' if @variantsWalk ~= 0
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_run' .. math.random(1, @variantsRun) .. '.ogg' if @variantsRun ~= 0

	GetRunSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_run' .. math.random(1, @variantsRun) .. '.ogg' if @variantsRun ~= 0
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. math.random(1, @variantsWalk) .. '.ogg' if @variantsWalk ~= 0

	GetWanderSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_wander' .. math.random(1, @variantsWander) .. '.ogg' if @variantsWander ~= 0

	GetLandSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_land' .. math.random(1, @variantsLand) .. '.ogg' if @variantsLand ~= 0

	AddLandSounds: (variants) =>
		@variantsLand = variants
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_land' .. i .. '.ogg') for i = 1, variants
		return @

AddSoundString('player/ppm2/hooves' .. i .. '.ogg') for i = 1, 3
AddSoundString('player/ppm2/falldown.ogg')
AddSoundString('player/ppm2/jump.ogg')

LEmitSound = (ply, name, level = 75, volume = 1, levelIfOnServer = level) ->
	if CLIENT
		ply\EmitSound(name, level, 100, volume) if not game.SinglePlayer() -- Some mods fix this globally (PAC3 for example)
		-- so lets try to avoid problems
		return

	if game.SinglePlayer()
		ply\EmitSound(name, level, 100, volume)
		return

	error('Tried to play unpooled sound: ' .. name) if not SOUND_STRINGS_POOL[name]

	filter = RecipientFilter()
	filter\AddPAS(ply\GetPos())
	filter\RemovePlayer(ply)

	for ply2 in *filter\GetPlayers()
		if ply2\GetInfoBool('ppm2_cl_no_hoofsound', false)
			filter\RemovePlayer(ply2)

	return if filter\GetCount() == 0

	net.Start('ppm2_workaround_emitsound')
	net.WritePlayer(ply)
	net.WriteUInt8(SOUND_STRINGS_POOL[name])
	net.WriteUInt8(levelIfOnServer)
	net.WriteUInt8((volume * 100)\floor())
	net.Send(filter)

class PPM2.PlayerFootstepsListener
	new: (ply) =>
		ply.__ppm2_walkc = @
		@ply = ply
		@walkSpeed = ply\GetWalkSpeed()
		@runSpeed = ply\GetRunSpeed()
		@playedWanderSound = false
		@running = false
		@lastVelocity = @ply\GetVelocity()
		@initialVel = @ply\GetVelocity()
		@lastTrace = @TraceNow()
		@onGround = @ply\OnGround()
		@Validate() if @onGround
		hook.Add 'PlayerFootstep', @, @PlayerFootstep, 8
		hook.Add 'Think', @, @Think

		@lambdaEmitWander = ->
			return if not @ply\IsValid()
			return if not @onGround
			return if not @lastEntry
			sound = @lastEntry\GetWanderSound()
			return if not sound
			@EmitSound(sound, 50, 1, 70)

		@lambdaEmitWalk = ->
			return if not @ply\IsValid()
			return if not @onGround

			if not @lastEntry
				@EmitSound(@@RandHoof(), 50, 0.8, 75)
				return

			@EmitSound(@@RandHoof(), 50, 0.8, 75) if @lastEntry\ShouldPlayHoofclap()
			sound = @lastEntry\GetWalkSound()
			return if not sound
			@EmitSound(sound, 40, 0.8, 75)
			return true

		@lambdaEmitRun = ->
			return if not @ply\IsValid()
			return if not @onGround

			if not @lastEntry
				@EmitSound(@@RandHoof(), 60, 1, 95)
				return

			@EmitSound(@@RandHoof(), 60, 1, 95) if @lastEntry\ShouldPlayHoofclap()
			sound = @lastEntry\GetRunSound()
			return if not sound
			@EmitSound(sound, 40, 0.7, 95)
			return true

	IsValid: => @ply\IsValid() and not @playedWanderSound

	Validate: =>
		newMatType = @lastTrace.MatType == 0 and MAT_DEFAULT or @lastTrace.MatType
		return if @lastMatType == newMatType
		@lastMatType = newMatType
		@lastEntry = PPM2.MaterialSoundEntry\Ask(@lastMatType)

	@RandHoof: => 'player/ppm2/hooves' .. math.random(1, 3) .. '.ogg'

	EmitSound: (name, level = 75, volume = 1, levelIfOnServer = level) => LEmitSound(@ply, name, level, volume, levelIfOnServer)

	PlayWalk: =>
		timer.Simple 0.13, @lambdaEmitWalk
		timer.Simple 0.21, @lambdaEmitWalk
		--timer.Simple 0.19, @lambdaEmitWalk
		return @lambdaEmitWalk()

	PlayWander: =>
		@playedWanderSound = true
		timer.Simple 0.13, @lambdaEmitWander
		timer.Simple 0.17, @lambdaEmitWander
		--timer.Simple 0.27, @lambdaEmitWander
		@lambdaEmitWander()

	PlayRun: =>
		timer.Simple 0.18, @lambdaEmitRun
		--timer.Simple 0.13, @lambdaEmitRun
		--timer.Simple 0.17, @lambdaEmitRun
		return @lambdaEmitRun()

	TraceNow: => @@TraceNow(@ply)

	@TraceNow: (ply, dropToGround) =>
		mins, maxs = ply\GetHull()

		trData = {
			start: ply\GetPos()
			endpos: ply\GetPos() - Vector(0, 0, not dropToGround and 5 or 15)
			:mins, :maxs
			filter: ply
		}

		return util.TraceHull(trData)

	PlayerFootstep: (ply) =>
		return if ply ~= @ply
		return true if CLIENT and @ply ~= LocalPlayer()
		@lastTrace = @TraceNow()
		@Validate()

		if @running
			return @PlayRun()
		else
			return @PlayWalk()

	Think: =>
		@lastVelocity = @ply\GetVelocity()
		vlen = @lastVelocity\Length()
		@onGround = @ply\OnGround()
		@running = vlen >= @runSpeed

		if vlen < @walkSpeed * 0.2
			@ply.__ppm2_walkc = nil
			@PlayWander()

PPM2.MaterialSoundEntry('concrete', MAT_CONCRETE, 11, 11, 5)
PPM2.MaterialSoundEntry('dirt', MAT_DIRT, 11, 11, 5)\AddLandSounds(4)\DisableHoofclap()
PPM2.MaterialSoundEntry('grass', MAT_GRASS, 10, 4, 6)\DisableHoofclap()
PPM2.MaterialSoundEntry('gravel', MAT_DEFAULT, 11, 11, 3)\DisableHoofclap() -- e
PPM2.MaterialSoundEntry('metalbar', MAT_METAL, 11, 11, 6)
PPM2.MaterialSoundEntry('metalbox', MAT_VENT, 10, 9, 4)
PPM2.MaterialSoundEntry('mud', MAT_SLOSH, 10, 9, 4)\DisableHoofclap()
PPM2.MaterialSoundEntry('sand', MAT_SAND, 11, 11, 0)\DisableHoofclap()
PPM2.MaterialSoundEntry('snow', MAT_SNOW, 11, 11, 5)\DisableHoofclap()
PPM2.MaterialSoundEntry('squeakywood', MAT_WOOD, 11, 0, 7)

net.receive 'ppm2_workaround_emitsound', ->
	ply, sound, level, volume = net.ReadPlayer(), SOUND_STRINGS_POOL_INV[net.ReadUInt8()], net.ReadUInt8(), net.ReadUInt8() / 100
	return if not IsValid(ply)
	ply\EmitSound(sound, level, 100, volume)

hook.Add 'PlayerFootstep', 'PPM2.Hoofstep', (pos, foot, sound, volume, filter) =>
	return if CLIENT and game.SinglePlayer()
	return if not @IsPonyCached() or CLIENT and DISABLE_HOOFSTEP_SOUND_CLIENT\GetBool() or DISABLE_HOOFSTEP_SOUND\GetBool()
	return if @__ppm2_walkc
	return PPM2.PlayerFootstepsListener(@)\PlayerFootstep(@)

ProcessFalldownEvents = (cmd) =>
	return if not @IsPonyCached()
	return if CLIENT and DISABLE_HOOFSTEP_SOUND_CLIENT\GetBool() or DISABLE_HOOFSTEP_SOUND\GetBool()

	if @GetMoveType() ~= MOVETYPE_WALK
		@__ppm2_jump = false
		return

	self2 = @GetTable()
	ground = @OnGround()
	jump = cmd\KeyDown(IN_JUMP)

	if @__ppm2_jump and ground
		@__ppm2_jump = false

		tr = PPM2.PlayerFootstepsListener\TraceNow(@)
		entry = PPM2.MaterialSoundEntry\Ask(tr.MatType == 0 and MAT_DEFAULT or tr.MatType)

		if entry
			if sound = entry\GetLandSound()
				LEmitSound(@, sound, 85, 1, 105)
			elseif not @__ppm2_walkc
				if sound = entry\GetWalkSound()
					LEmitSound(@, sound, 45, 0.2, 55)
					timer.Simple 0.04, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)
					timer.Simple 0.07, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)
					timer.Simple 0.1, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)

			if not entry\ShouldPlayHoofclap()
				return

		LEmitSound(@, 'player/ppm2/falldown.ogg', 85, 1, 105)
	elseif jump and not ground and not @__ppm2_jump
		@__ppm2_jump = true
		LEmitSound(@, 'player/ppm2/jump.ogg', 75, 1, 85)

		tr = PPM2.PlayerFootstepsListener\TraceNow(@, true)
		entry = PPM2.MaterialSoundEntry\Ask(tr.MatType == 0 and MAT_DEFAULT or tr.MatType)

		if not entry or entry\ShouldPlayHoofclap()
			LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 55, 0.4, 75)
			timer.Simple 0.04, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 55, 0.4, 75)
			timer.Simple 0.07, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 55, 0.4, 75)
			timer.Simple 0.1, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 55, 0.4, 75)

hook.Add 'StartCommand', 'PPM2.Hoofsteps', ProcessFalldownEvents

-- Copyright (C) 2017-2018 DBot

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

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

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

net.pool('ppm2_workaround_emitsound') if SERVER

SOUND_STRINGS_POOL = {}
SOUND_STRINGS_POOL_INV = {}
AddSoundString = (sound) ->
	nextid = #SOUND_STRINGS_POOL_INV + 1
	SOUND_STRINGS_POOL[sound] = nextid
	SOUND_STRINGS_POOL_INV[nextid] = sound

class PPM2.MaterialSoundEntry
	@REGISTRIES = {}

	@Ask = (matType = MAT_DEFAULT) =>
		for reg in *@REGISTRIES
			if reg.material == matType
				return reg

		return false

	new: (name, material, variantsWalk = 0, variantsRun = 0, variantsWander = 0) =>
		table.insert(@@REGISTRIES, @)
		@name = name
		@material = material
		@variantsWalk = variantsWalk
		@variantsRun = variantsRun
		@variantsWander = variantsWander
		@variantsLand = 0
		@playHoofclap = true
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. i .. '.ogg') for i = 1, @variantsWalk
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_run' .. i .. '.ogg') for i = 1, @variantsRun
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_wander' .. i .. '.ogg') for i = 1, @variantsWander

	ShouldPlayHoofclap: => @playHoofclap

	DisableHoofclap: =>
		@playHoofclap = false
		return @

	GetWalkSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. math.random(1, @variantsWalk) .. '.ogg' if @variantsWalk ~= 0
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_run' .. math.random(1, @variantsRun) .. '.ogg' if @variantsRun ~= 0

	GetRunSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_run' .. math.random(1, @variantsRun) .. '.ogg' if @variantsRun ~= 0
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. math.random(1, @variantsWalk) .. '.ogg' if @variantsWalk ~= 0

	GetWanderSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_wander' .. math.random(1, @variantsWander) .. '.ogg' if @variantsWander ~= 0

	GetLandSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_land' .. math.random(1, @variantsLand) .. '.ogg' if @variantsLand ~= 0

	AddLandSounds: (variants) =>
		@variantsLand = variants
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_land' .. i .. '.ogg') for i = 1, variants
		return @

AddSoundString('player/ppm2/hooves' .. i .. '.ogg') for i = 1, 3
AddSoundString('player/ppm2/falldown.ogg')
AddSoundString('player/ppm2/jump.ogg')

LEmitSound = (ply, name, level = 75, volume = 1, levelIfOnServer = level) ->
	if CLIENT
		ply\EmitSound(name, level, 100, volume) if not game.SinglePlayer() -- Some mods fix this globally (PAC3 for example)
		-- so lets try to avoid problems
		return

	if game.SinglePlayer()
		ply\EmitSound(name, level, 100, volume)
		return

	error('Tried to play unpooled sound: ' .. name) if not SOUND_STRINGS_POOL[name]

	filter = RecipientFilter()
	filter\AddPAS(ply\GetPos())
	filter\RemovePlayer(ply)

	for ply2 in *filter\GetPlayers()
		if ply2\GetInfoBool('ppm2_cl_no_hoofsound', false)
			filter\RemovePlayer(ply2)

	return if filter\GetCount() == 0

	net.Start('ppm2_workaround_emitsound')
	net.WritePlayer(ply)
	net.WriteUInt8(SOUND_STRINGS_POOL[name])
	net.WriteUInt8(levelIfOnServer)
	net.WriteUInt8((volume * 100)\floor())
	net.Send(filter)

class PPM2.PlayerFootstepsListener
	new: (ply) =>
		ply.__ppm2_walkc = @
		@ply = ply
		@walkSpeed = ply\GetWalkSpeed()
		@runSpeed = ply\GetRunSpeed()
		@playedWanderSound = false
		@running = false
		@lastVelocity = @ply\GetVelocity()
		@initialVel = @ply\GetVelocity()
		@lastTrace = @TraceNow()
		@onGround = @ply\OnGround()
		@Validate() if @onGround
		hook.Add 'PlayerFootstep', @, @PlayerFootstep, 8
		hook.Add 'Think', @, @Think

		@lambdaEmitWander = ->
			return if not @ply\IsValid()
			return if not @onGround
			return if not @lastEntry
			sound = @lastEntry\GetWanderSound()
			return if not sound
			@EmitSound(sound, 50, 1, 70)

		@lambdaEmitWalk = ->
			return if not @ply\IsValid()
			return if not @onGround

			if not @lastEntry
				@EmitSound(@@RandHoof(), 50, 0.8, 75)
				return

			@EmitSound(@@RandHoof(), 50, 0.8, 75) if @lastEntry\ShouldPlayHoofclap()
			sound = @lastEntry\GetWalkSound()
			return if not sound
			@EmitSound(sound, 40, 0.8, 75)
			return true

		@lambdaEmitRun = ->
			return if not @ply\IsValid()
			return if not @onGround

			if not @lastEntry
				@EmitSound(@@RandHoof(), 60, 1, 95)
				return

			@EmitSound(@@RandHoof(), 60, 1, 95) if @lastEntry\ShouldPlayHoofclap()
			sound = @lastEntry\GetRunSound()
			return if not sound
			@EmitSound(sound, 40, 0.7, 95)
			return true

	IsValid: => @ply\IsValid() and not @playedWanderSound

	Validate: =>
		newMatType = @lastTrace.MatType == 0 and MAT_DEFAULT or @lastTrace.MatType
		return if @lastMatType == newMatType
		@lastMatType = newMatType
		@lastEntry = PPM2.MaterialSoundEntry\Ask(@lastMatType)

	@RandHoof: => 'player/ppm2/hooves' .. math.random(1, 3) .. '.ogg'

	EmitSound: (name, level = 75, volume = 1, levelIfOnServer = level) => LEmitSound(@ply, name, level, volume, levelIfOnServer)

	PlayWalk: =>
		timer.Simple 0.13, @lambdaEmitWalk
		timer.Simple 0.21, @lambdaEmitWalk
		--timer.Simple 0.19, @lambdaEmitWalk
		return @lambdaEmitWalk()

	PlayWander: =>
		@playedWanderSound = true
		timer.Simple 0.13, @lambdaEmitWander
		timer.Simple 0.17, @lambdaEmitWander
		--timer.Simple 0.27, @lambdaEmitWander
		@lambdaEmitWander()

	PlayRun: =>
		timer.Simple 0.18, @lambdaEmitRun
		--timer.Simple 0.13, @lambdaEmitRun
		--timer.Simple 0.17, @lambdaEmitRun
		return @lambdaEmitRun()

	TraceNow: => @@TraceNow(@ply)

	@TraceNow: (ply, dropToGround) =>
		mins, maxs = ply\GetHull()

		trData = {
			start: ply\GetPos()
			endpos: ply\GetPos() - Vector(0, 0, not dropToGround and 5 or 15)
			:mins, :maxs
			filter: ply
		}

		return util.TraceHull(trData)

	PlayerFootstep: (ply) =>
		return if ply ~= @ply
		return true if CLIENT and @ply ~= LocalPlayer()
		@lastTrace = @TraceNow()
		@Validate()

		if @running
			return @PlayRun()
		else
			return @PlayWalk()

	Think: =>
		@lastVelocity = @ply\GetVelocity()
		vlen = @lastVelocity\Length()
		@onGround = @ply\OnGround()
		@running = vlen >= @runSpeed

		if vlen < @walkSpeed * 0.2
			@ply.__ppm2_walkc = nil
			@PlayWander()

PPM2.MaterialSoundEntry('concrete', MAT_CONCRETE, 11, 11, 5)
PPM2.MaterialSoundEntry('dirt', MAT_DIRT, 11, 11, 5)\AddLandSounds(4)\DisableHoofclap()
PPM2.MaterialSoundEntry('grass', MAT_GRASS, 10, 4, 6)\DisableHoofclap()
PPM2.MaterialSoundEntry('gravel', MAT_DEFAULT, 11, 11, 3)\DisableHoofclap() -- e
PPM2.MaterialSoundEntry('metalbar', MAT_METAL, 11, 11, 6)
PPM2.MaterialSoundEntry('metalbox', MAT_VENT, 10, 9, 4)
PPM2.MaterialSoundEntry('mud', MAT_SLOSH, 10, 9, 4)\DisableHoofclap()
PPM2.MaterialSoundEntry('sand', MAT_SAND, 11, 11, 0)\DisableHoofclap()
PPM2.MaterialSoundEntry('snow', MAT_SNOW, 11, 11, 5)\DisableHoofclap()
PPM2.MaterialSoundEntry('squeakywood', MAT_WOOD, 11, 0, 7)

net.receive 'ppm2_workaround_emitsound', ->
	ply, sound, level, volume = net.ReadPlayer(), SOUND_STRINGS_POOL_INV[net.ReadUInt8()], net.ReadUInt8(), net.ReadUInt8() / 100
	return if not IsValid(ply)
	ply\EmitSound(sound, level, 100, volume)

hook.Add 'PlayerFootstep', 'PPM2.Hoofstep', (pos, foot, sound, volume, filter) =>
	return if CLIENT and game.SinglePlayer()
	return if not @IsPonyCached() or CLIENT and DISABLE_HOOFSTEP_SOUND_CLIENT\GetBool() or DISABLE_HOOFSTEP_SOUND\GetBool()
	return if @__ppm2_walkc
	return PPM2.PlayerFootstepsListener(@)\PlayerFootstep(@)

ProcessFalldownEvents = (cmd) =>
	return if CLIENT and DISABLE_HOOFSTEP_SOUND_CLIENT\GetBool() or DISABLE_HOOFSTEP_SOUND\GetBool()

	if @GetMoveType() ~= MOVETYPE_WALK
		@__ppm2_jump = false
		return

	self2 = @GetTable()
	ground = @OnGround()
	jump = cmd\KeyDown(IN_JUMP)

	if @__ppm2_jump and ground
		@__ppm2_jump = false

		tr = PPM2.PlayerFootstepsListener\TraceNow(@)
		entry = PPM2.MaterialSoundEntry\Ask(tr.MatType == 0 and MAT_DEFAULT or tr.MatType)

		if entry
			if sound = entry\GetLandSound()
				LEmitSound(@, sound, 85, 1, 105)
			elseif not @__ppm2_walkc
				if sound = entry\GetWalkSound()
					LEmitSound(@, sound, 45, 0.2, 55)
					timer.Simple 0.04, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)
					timer.Simple 0.07, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)
					timer.Simple 0.1, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)

			if not entry\ShouldPlayHoofclap()
				return

		LEmitSound(@, 'player/ppm2/falldown.ogg', 85, 1, 105)
	elseif jump and not ground and not @__ppm2_jump
		@__ppm2_jump = true
		LEmitSound(@, 'player/ppm2/jump.ogg', 75, 1, 85)

		tr = PPM2.PlayerFootstepsListener\TraceNow(@, true)
		entry = PPM2.MaterialSoundEntry\Ask(tr.MatType == 0 and MAT_DEFAULT or tr.MatType)

		if not entry or entry\ShouldPlayHoofclap()
			LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 55, 0.4, 75)
			timer.Simple 0.04, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 55, 0.4, 75)
			timer.Simple 0.07, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 55, 0.4, 75)
			timer.Simple 0.1, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 55, 0.4, 75)

hook.Add 'StartCommand', 'PPM2.Hoofsteps', ProcessFalldownEvents


weatherunderground: fix lang for wind

weatherunderground: fix lang for wind

Former-commit-id: dfcd5dee26809b7fb9e272ddcbc995e2079eb72b
Former-commit-id: 986d01968d9e11e95d6e55afbc4ea100fd40b5db

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

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

_ = (s, lang) ->
 if lang == 'EN'
   return s

 if s == 'feels like'
   if lang == 'NO'
     return 'følt'

 if s == 'Variable' and lang == 'NO'
   return 'Vekslende'

 return s

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/'
    language = 'EN'
    cl = self\DestinationLocale(destination)
    if cl\match'^nn'
      language = 'NO'
    elseif cl\match'^nb'
      language = 'NO'
    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

      windString = "Wind " .. current.wind_string

      if language == 'NO'
        windSpeedname = windSpeedToName(current.wind_kph/3.6)
        windDirection = _(current.wind_dir, language)
        windString = windDirection .. " " .. windSpeedname

      rain = current.precip_today_metric

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

      @Msg 'privmsg', destination, source, '%s, %s °C (%s %s °C), %s, %s mm, (%s, %s)', weather, tempColor(temp), _('feels like', language), tempColor(feelsLike), windString, 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'
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'

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

_ = (s, lang) ->
 if lang == 'EN'
   return s

 if s == 'feels like'
   if lang == 'NO'
     return 'følt'

 if s == 'Variable' and lang == 'NO'
   return 'Vekslende'

 return s

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/'
    language = 'EN'
    cl = self\DestinationLocale(destination)
    if cl\match'^nn'
      language = 'NO'
    elseif cl\match'^nb'
      language = 'NO'
    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, language)

      rain = current.precip_today_metric

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

      @Msg 'privmsg', destination, source, '%s, %s °C (%s %s °C), %s %s, %s mm, (%s, %s)', weather, tempColor(temp), _('feels like', language), tempColor(feelsLike), windDirection, windSpeedname, rain, city, time

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

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


Don't play sounds when not walking

Don't play sounds when not walking

-- Copyright (C) 2017-2018 DBot

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

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

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

net.pool('ppm2_workaround_emitsound') if SERVER

SOUND_STRINGS_POOL = {}
SOUND_STRINGS_POOL_INV = {}
AddSoundString = (sound) ->
	nextid = #SOUND_STRINGS_POOL_INV + 1
	SOUND_STRINGS_POOL[sound] = nextid
	SOUND_STRINGS_POOL_INV[nextid] = sound

class PPM2.MaterialSoundEntry
	@REGISTRIES = {}

	@Ask = (matType = MAT_DEFAULT) =>
		for reg in *@REGISTRIES
			if reg.material == matType
				return reg

		return false

	new: (name, material, variantsWalk = 0, variantsRun = 0, variantsWander = 0) =>
		table.insert(@@REGISTRIES, @)
		@name = name
		@material = material
		@variantsWalk = variantsWalk
		@variantsRun = variantsRun
		@variantsWander = variantsWander
		@variantsLand = 0
		@playHoofclap = true
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. i .. '.ogg') for i = 1, @variantsWalk
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_run' .. i .. '.ogg') for i = 1, @variantsRun
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_wander' .. i .. '.ogg') for i = 1, @variantsWander

	ShouldPlayHoofclap: => @playHoofclap

	DisableHoofclap: =>
		@playHoofclap = false
		return @

	GetWalkSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. math.random(1, @variantsWalk) .. '.ogg' if @variantsWalk ~= 0
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_run' .. math.random(1, @variantsRun) .. '.ogg' if @variantsRun ~= 0

	GetRunSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_run' .. math.random(1, @variantsRun) .. '.ogg' if @variantsRun ~= 0
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. math.random(1, @variantsWalk) .. '.ogg' if @variantsWalk ~= 0

	GetWanderSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_wander' .. math.random(1, @variantsWander) .. '.ogg' if @variantsWander ~= 0

	GetLandSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_land' .. math.random(1, @variantsLand) .. '.ogg' if @variantsLand ~= 0

	AddLandSounds: (variants) =>
		@variantsLand = variants
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_land' .. i .. '.ogg') for i = 1, variants
		return @

AddSoundString('player/ppm2/hooves' .. i .. '.ogg') for i = 1, 3
AddSoundString('player/ppm2/falldown.ogg')
AddSoundString('player/ppm2/jump.ogg')

LEmitSound = (ply, name, level = 75, volume = 1, levelIfOnServer = level) ->
	if CLIENT
		ply\EmitSound(name, level, 100, volume) if not game.SinglePlayer() -- Some mods fix this globally (PAC3 for example)
		-- so lets try to avoid problems
		return

	if game.SinglePlayer()
		ply\EmitSound(name, level, 100, volume)
		return

	error('Tried to play unpooled sound: ' .. name) if not SOUND_STRINGS_POOL[name]

	filter = RecipientFilter()
	filter\AddPAS(ply\GetPos())
	filter\RemovePlayer(ply)

	return if filter\GetCount() == 0

	net.Start('ppm2_workaround_emitsound')
	net.WritePlayer(ply)
	net.WriteUInt8(SOUND_STRINGS_POOL[name])
	net.WriteUInt8(levelIfOnServer)
	net.WriteUInt8((volume * 100)\floor())
	net.Send(filter)

class PPM2.PlayerFootstepsListener
	new: (ply) =>
		ply.__ppm2_walkc = @
		@ply = ply
		@walkSpeed = ply\GetWalkSpeed()
		@runSpeed = ply\GetRunSpeed()
		@playedWanderSound = false
		@running = false
		@lastVelocity = @ply\GetVelocity()
		@initialVel = @ply\GetVelocity()
		@lastTrace = @TraceNow()
		@onGround = @ply\OnGround()
		@Validate() if @onGround
		hook.Add 'PlayerFootstep', @, @PlayerFootstep, 8
		hook.Add 'Think', @, @Think

		@lambdaEmitWander = ->
			return if not @ply\IsValid()
			return if not @onGround
			return if not @lastEntry
			sound = @lastEntry\GetWanderSound()
			return if not sound
			@EmitSound(sound, 50, 1, 70)

		@lambdaEmitWalk = ->
			return if not @ply\IsValid()
			return if not @onGround

			if not @lastEntry
				@EmitSound(@@RandHoof(), 50, 0.8, 75)
				return

			@EmitSound(@@RandHoof(), 50, 0.8, 75) if @lastEntry\ShouldPlayHoofclap()
			sound = @lastEntry\GetWalkSound()
			return if not sound
			@EmitSound(sound, 40, 0.8, 75)
			return true

		@lambdaEmitRun = ->
			return if not @ply\IsValid()
			return if not @onGround

			if not @lastEntry
				@EmitSound(@@RandHoof(), 60, 1, 95)
				return

			@EmitSound(@@RandHoof(), 60, 1, 95) if @lastEntry\ShouldPlayHoofclap()
			sound = @lastEntry\GetRunSound()
			return if not sound
			@EmitSound(sound, 40, 0.7, 95)
			return true

	IsValid: => @ply\IsValid() and not @playedWanderSound

	Validate: =>
		newMatType = @lastTrace.MatType == 0 and MAT_DEFAULT or @lastTrace.MatType
		return if @lastMatType == newMatType
		@lastMatType = newMatType
		@lastEntry = PPM2.MaterialSoundEntry\Ask(@lastMatType)

	@RandHoof: => 'player/ppm2/hooves' .. math.random(1, 3) .. '.ogg'

	EmitSound: (name, level = 75, volume = 1, levelIfOnServer = level) => LEmitSound(@ply, name, level, volume, levelIfOnServer)

	PlayWalk: =>
		timer.Simple 0.13, @lambdaEmitWalk
		timer.Simple 0.21, @lambdaEmitWalk
		--timer.Simple 0.19, @lambdaEmitWalk
		return @lambdaEmitWalk()

	PlayWander: =>
		@playedWanderSound = true
		timer.Simple 0.13, @lambdaEmitWander
		timer.Simple 0.17, @lambdaEmitWander
		--timer.Simple 0.27, @lambdaEmitWander
		@lambdaEmitWander()

	PlayRun: =>
		timer.Simple 0.18, @lambdaEmitRun
		--timer.Simple 0.13, @lambdaEmitRun
		--timer.Simple 0.17, @lambdaEmitRun
		return @lambdaEmitRun()

	TraceNow: => @@TraceNow(@ply)

	@TraceNow: (ply, dropToGround) =>
		mins, maxs = ply\GetHull()

		trData = {
			start: ply\GetPos()
			endpos: ply\GetPos() - Vector(0, 0, not dropToGround and 5 or 15)
			:mins, :maxs
			filter: ply
		}

		return util.TraceHull(trData)

	PlayerFootstep: (ply) =>
		return if ply ~= @ply
		return true if CLIENT and @ply ~= LocalPlayer()
		@lastTrace = @TraceNow()
		@Validate()

		if @running
			return @PlayRun()
		else
			return @PlayWalk()

	Think: =>
		@lastVelocity = @ply\GetVelocity()
		vlen = @lastVelocity\Length()
		@onGround = @ply\OnGround()
		@running = vlen >= @runSpeed

		if vlen < @walkSpeed * 0.2
			@ply.__ppm2_walkc = nil
			@PlayWander()

PPM2.MaterialSoundEntry('concrete', MAT_CONCRETE, 11, 11, 5)
PPM2.MaterialSoundEntry('dirt', MAT_DIRT, 11, 11, 5)\AddLandSounds(4)\DisableHoofclap()
PPM2.MaterialSoundEntry('grass', MAT_GRASS, 10, 4, 6)\DisableHoofclap()
PPM2.MaterialSoundEntry('gravel', MAT_DEFAULT, 11, 11, 3)\DisableHoofclap() -- e
PPM2.MaterialSoundEntry('metalbar', MAT_METAL, 11, 11, 6)
PPM2.MaterialSoundEntry('metalbox', MAT_VENT, 10, 9, 4)
PPM2.MaterialSoundEntry('mud', MAT_SLOSH, 10, 9, 4)\DisableHoofclap()
PPM2.MaterialSoundEntry('sand', MAT_SAND, 11, 11, 0)\DisableHoofclap()
PPM2.MaterialSoundEntry('snow', MAT_SNOW, 11, 11, 5)\DisableHoofclap()
PPM2.MaterialSoundEntry('squeakywood', MAT_WOOD, 11, 0, 7)

net.receive 'ppm2_workaround_emitsound', ->
	ply, sound, level, volume = net.ReadPlayer(), SOUND_STRINGS_POOL_INV[net.ReadUInt8()], net.ReadUInt8(), net.ReadUInt8() / 100
	return if not IsValid(ply)
	ply\EmitSound(sound, level, 100, volume)

hook.Add 'PlayerFootstep', 'PPM2.Hoofstep', (pos, foot, sound, volume, filter) =>
	return if CLIENT and game.SinglePlayer()
	return if not @IsPonyCached() or CLIENT and DISABLE_HOOFSTEP_SOUND_CLIENT\GetBool() or DISABLE_HOOFSTEP_SOUND\GetBool()
	return if @__ppm2_walkc
	return PPM2.PlayerFootstepsListener(@)\PlayerFootstep(@)

ProcessFalldownEvents = (cmd) =>
	if @GetMoveType() ~= MOVETYPE_WALK
		@@__ppm2_jump = false
		return

	self2 = @GetTable()
	ground = @OnGround()
	jump = cmd\KeyDown(IN_JUMP)

	if @__ppm2_jump and ground
		@__ppm2_jump = false

		tr = PPM2.PlayerFootstepsListener\TraceNow(@)
		entry = PPM2.MaterialSoundEntry\Ask(tr.MatType == 0 and MAT_DEFAULT or tr.MatType)

		if entry
			if sound = entry\GetLandSound()
				LEmitSound(@, sound, 85, 1, 105)
			elseif not @__ppm2_walkc
				if sound = entry\GetWalkSound()
					LEmitSound(@, sound, 45, 0.2, 55)
					timer.Simple 0.04, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)
					timer.Simple 0.07, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)
					timer.Simple 0.1, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)

			if not entry\ShouldPlayHoofclap()
				return

		LEmitSound(@, 'player/ppm2/falldown.ogg', 85, 1, 105)
	elseif jump and not ground and not @__ppm2_jump
		@__ppm2_jump = true
		LEmitSound(@, 'player/ppm2/jump.ogg', 75, 1, 85)

		tr = PPM2.PlayerFootstepsListener\TraceNow(@, true)
		entry = PPM2.MaterialSoundEntry\Ask(tr.MatType == 0 and MAT_DEFAULT or tr.MatType)

		if not entry or entry\ShouldPlayHoofclap()
			LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 75, 1, 85)
			timer.Simple 0.04, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 75, 1, 85)
			timer.Simple 0.07, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 75, 1, 85)
			timer.Simple 0.1, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 75, 1, 85)

hook.Add 'StartCommand', 'PPM2.Hoofsteps', ProcessFalldownEvents

-- Copyright (C) 2017-2018 DBot

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

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

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

net.pool('ppm2_workaround_emitsound') if SERVER

SOUND_STRINGS_POOL = {}
SOUND_STRINGS_POOL_INV = {}
AddSoundString = (sound) ->
	nextid = #SOUND_STRINGS_POOL_INV + 1
	SOUND_STRINGS_POOL[sound] = nextid
	SOUND_STRINGS_POOL_INV[nextid] = sound

class PPM2.MaterialSoundEntry
	@REGISTRIES = {}

	@Ask = (matType = MAT_DEFAULT) =>
		for reg in *@REGISTRIES
			if reg.material == matType
				return reg

		return false

	new: (name, material, variantsWalk = 0, variantsRun = 0, variantsWander = 0) =>
		table.insert(@@REGISTRIES, @)
		@name = name
		@material = material
		@variantsWalk = variantsWalk
		@variantsRun = variantsRun
		@variantsWander = variantsWander
		@variantsLand = 0
		@playHoofclap = true
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. i .. '.ogg') for i = 1, @variantsWalk
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_run' .. i .. '.ogg') for i = 1, @variantsRun
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_wander' .. i .. '.ogg') for i = 1, @variantsWander

	ShouldPlayHoofclap: => @playHoofclap

	DisableHoofclap: =>
		@playHoofclap = false
		return @

	GetWalkSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. math.random(1, @variantsWalk) .. '.ogg' if @variantsWalk ~= 0
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_run' .. math.random(1, @variantsRun) .. '.ogg' if @variantsRun ~= 0

	GetRunSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_run' .. math.random(1, @variantsRun) .. '.ogg' if @variantsRun ~= 0
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_walk' .. math.random(1, @variantsWalk) .. '.ogg' if @variantsWalk ~= 0

	GetWanderSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_wander' .. math.random(1, @variantsWander) .. '.ogg' if @variantsWander ~= 0

	GetLandSound: =>
		return 'player/ppm2/' .. @name .. '/' .. @name .. '_land' .. math.random(1, @variantsLand) .. '.ogg' if @variantsLand ~= 0

	AddLandSounds: (variants) =>
		@variantsLand = variants
		AddSoundString('player/ppm2/' .. @name .. '/' .. @name .. '_land' .. i .. '.ogg') for i = 1, variants
		return @

AddSoundString('player/ppm2/hooves' .. i .. '.ogg') for i = 1, 3
AddSoundString('player/ppm2/falldown.ogg')
AddSoundString('player/ppm2/jump.ogg')

LEmitSound = (ply, name, level = 75, volume = 1, levelIfOnServer = level) ->
	if CLIENT
		ply\EmitSound(name, level, 100, volume) if not game.SinglePlayer() -- Some mods fix this globally (PAC3 for example)
		-- so lets try to avoid problems
		return

	if game.SinglePlayer()
		ply\EmitSound(name, level, 100, volume)
		return

	error('Tried to play unpooled sound: ' .. name) if not SOUND_STRINGS_POOL[name]

	filter = RecipientFilter()
	filter\AddPAS(ply\GetPos())
	filter\RemovePlayer(ply)

	return if filter\GetCount() == 0

	net.Start('ppm2_workaround_emitsound')
	net.WritePlayer(ply)
	net.WriteUInt8(SOUND_STRINGS_POOL[name])
	net.WriteUInt8(levelIfOnServer)
	net.WriteUInt8((volume * 100)\floor())
	net.Send(filter)

class PPM2.PlayerFootstepsListener
	new: (ply) =>
		ply.__ppm2_walkc = @
		@ply = ply
		@walkSpeed = ply\GetWalkSpeed()
		@runSpeed = ply\GetRunSpeed()
		@playedWanderSound = false
		@running = false
		@lastVelocity = @ply\GetVelocity()
		@initialVel = @ply\GetVelocity()
		@lastTrace = @TraceNow()
		@onGround = @ply\OnGround()
		@Validate() if @onGround
		hook.Add 'PlayerFootstep', @, @PlayerFootstep, 8
		hook.Add 'Think', @, @Think

		@lambdaEmitWander = ->
			return if not @ply\IsValid()
			return if not @onGround
			return if not @lastEntry
			sound = @lastEntry\GetWanderSound()
			return if not sound
			@EmitSound(sound, 50, 1, 70)

		@lambdaEmitWalk = ->
			return if not @ply\IsValid()
			return if not @onGround

			if not @lastEntry
				@EmitSound(@@RandHoof(), 50, 0.8, 75)
				return

			@EmitSound(@@RandHoof(), 50, 0.8, 75) if @lastEntry\ShouldPlayHoofclap()
			sound = @lastEntry\GetWalkSound()
			return if not sound
			@EmitSound(sound, 40, 0.8, 75)
			return true

		@lambdaEmitRun = ->
			return if not @ply\IsValid()
			return if not @onGround

			if not @lastEntry
				@EmitSound(@@RandHoof(), 60, 1, 95)
				return

			@EmitSound(@@RandHoof(), 60, 1, 95) if @lastEntry\ShouldPlayHoofclap()
			sound = @lastEntry\GetRunSound()
			return if not sound
			@EmitSound(sound, 40, 0.7, 95)
			return true

	IsValid: => @ply\IsValid() and not @playedWanderSound

	Validate: =>
		newMatType = @lastTrace.MatType == 0 and MAT_DEFAULT or @lastTrace.MatType
		return if @lastMatType == newMatType
		@lastMatType = newMatType
		@lastEntry = PPM2.MaterialSoundEntry\Ask(@lastMatType)

	@RandHoof: => 'player/ppm2/hooves' .. math.random(1, 3) .. '.ogg'

	EmitSound: (name, level = 75, volume = 1, levelIfOnServer = level) => LEmitSound(@ply, name, level, volume, levelIfOnServer)

	PlayWalk: =>
		timer.Simple 0.13, @lambdaEmitWalk
		timer.Simple 0.21, @lambdaEmitWalk
		--timer.Simple 0.19, @lambdaEmitWalk
		return @lambdaEmitWalk()

	PlayWander: =>
		@playedWanderSound = true
		timer.Simple 0.13, @lambdaEmitWander
		timer.Simple 0.17, @lambdaEmitWander
		--timer.Simple 0.27, @lambdaEmitWander
		@lambdaEmitWander()

	PlayRun: =>
		timer.Simple 0.18, @lambdaEmitRun
		--timer.Simple 0.13, @lambdaEmitRun
		--timer.Simple 0.17, @lambdaEmitRun
		return @lambdaEmitRun()

	TraceNow: => @@TraceNow(@ply)

	@TraceNow: (ply, dropToGround) =>
		mins, maxs = ply\GetHull()

		trData = {
			start: ply\GetPos()
			endpos: ply\GetPos() - Vector(0, 0, not dropToGround and 5 or 15)
			:mins, :maxs
			filter: ply
		}

		return util.TraceHull(trData)

	PlayerFootstep: (ply) =>
		return if ply ~= @ply
		return true if CLIENT and @ply ~= LocalPlayer()
		@lastTrace = @TraceNow()
		@Validate()

		if @running
			return @PlayRun()
		else
			return @PlayWalk()

	Think: =>
		@lastVelocity = @ply\GetVelocity()
		vlen = @lastVelocity\Length()
		@onGround = @ply\OnGround()
		@running = vlen >= @runSpeed

		if vlen < @walkSpeed * 0.2
			@ply.__ppm2_walkc = nil
			@PlayWander()

PPM2.MaterialSoundEntry('concrete', MAT_CONCRETE, 11, 11, 5)
PPM2.MaterialSoundEntry('dirt', MAT_DIRT, 11, 11, 5)\AddLandSounds(4)\DisableHoofclap()
PPM2.MaterialSoundEntry('grass', MAT_GRASS, 10, 4, 6)\DisableHoofclap()
PPM2.MaterialSoundEntry('gravel', MAT_DEFAULT, 11, 11, 3)\DisableHoofclap() -- e
PPM2.MaterialSoundEntry('metalbar', MAT_METAL, 11, 11, 6)
PPM2.MaterialSoundEntry('metalbox', MAT_VENT, 10, 9, 4)
PPM2.MaterialSoundEntry('mud', MAT_SLOSH, 10, 9, 4)\DisableHoofclap()
PPM2.MaterialSoundEntry('sand', MAT_SAND, 11, 11, 0)\DisableHoofclap()
PPM2.MaterialSoundEntry('snow', MAT_SNOW, 11, 11, 5)\DisableHoofclap()
PPM2.MaterialSoundEntry('squeakywood', MAT_WOOD, 11, 0, 7)

net.receive 'ppm2_workaround_emitsound', ->
	ply, sound, level, volume = net.ReadPlayer(), SOUND_STRINGS_POOL_INV[net.ReadUInt8()], net.ReadUInt8(), net.ReadUInt8() / 100
	return if not IsValid(ply)
	ply\EmitSound(sound, level, 100, volume)

hook.Add 'PlayerFootstep', 'PPM2.Hoofstep', (pos, foot, sound, volume, filter) =>
	return if CLIENT and game.SinglePlayer()
	return if not @IsPonyCached() or CLIENT and DISABLE_HOOFSTEP_SOUND_CLIENT\GetBool() or DISABLE_HOOFSTEP_SOUND\GetBool()
	return if @__ppm2_walkc
	return PPM2.PlayerFootstepsListener(@)\PlayerFootstep(@)

ProcessFalldownEvents = (cmd) =>
	self2 = @GetTable()
	ground = @OnGround()
	jump = cmd\KeyDown(IN_JUMP)

	if @__ppm2_jump and ground
		@__ppm2_jump = false

		tr = PPM2.PlayerFootstepsListener\TraceNow(@)
		entry = PPM2.MaterialSoundEntry\Ask(tr.MatType == 0 and MAT_DEFAULT or tr.MatType)

		if entry
			if sound = entry\GetLandSound()
				LEmitSound(@, sound, 85, 1, 105)
			elseif not @__ppm2_walkc
				if sound = entry\GetWalkSound()
					LEmitSound(@, sound, 45, 0.2, 55)
					timer.Simple 0.04, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)
					timer.Simple 0.07, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)
					timer.Simple 0.1, -> LEmitSound(@, entry\GetWalkSound(), 45, 0.2, 55)

			if not entry\ShouldPlayHoofclap()
				return

		LEmitSound(@, 'player/ppm2/falldown.ogg', 85, 1, 105)
	elseif jump and not ground and not @__ppm2_jump
		@__ppm2_jump = true
		LEmitSound(@, 'player/ppm2/jump.ogg', 75, 1, 85)

		tr = PPM2.PlayerFootstepsListener\TraceNow(@, true)
		entry = PPM2.MaterialSoundEntry\Ask(tr.MatType == 0 and MAT_DEFAULT or tr.MatType)

		if not entry or entry\ShouldPlayHoofclap()
			LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 75, 1, 85)
			timer.Simple 0.04, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 75, 1, 85)
			timer.Simple 0.07, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 75, 1, 85)
			timer.Simple 0.1, -> LEmitSound(@, PPM2.PlayerFootstepsListener.RandHoof(), 75, 1, 85)

hook.Add 'StartCommand', 'PPM2.Hoofsteps', ProcessFalldownEvents


textutils: nonickalertnicks, rot13

textutils: nonickalertnicks, rot13

Former-commit-id: e2c732432daf9e0687af3922693f7ca58439959e [formerly 465cc38c964ee09f0731fdc877009819a9d7ec65]
Former-commit-id: 5e9047206ca6329012cd79e2b2a00a4e6671a43c

PRIVMSG:
  '^%pupper (.+)$': (source, destination, arg) =>
    say arg\upper!
  '^%plower (.+)$': (source, destination, arg) =>
    say arg\lower!
  '^%preverse (.+)$': (source, destination, arg) =>
    say arg\reverse!
  '^%plen (.+)$': (source, destination, arg) =>
    say tostring(arg\len!)
  '^%pnicks$': (source, destination) =>
    say table.concat([ivar2.util.nonickalert(ivar2.channels[destination].nicks, n) for n,k in pairs(ivar2.channels[destination].nicks)], ' ')
  '^%prandom (.+)$': (source, destination, arg) =>
    words = [word for word in arg\gmatch('%S+')]
    say words[math.random(1, #words)]
  '^%pbold (.+)$': (source, destination, arg) =>
    say ivar2.util.bold(arg)
  '^%punderline (.+)$': (source, destination, arg) =>
    say ivar2.util.underline(arg)
  '^%pitalic (.+)$': (source, destination, arg) =>
    say ivar2.util.italic(arg)
  '^%pinvert (.+)$': (source, destination, arg) =>
    say ivar2.util.reverse(arg)
  '^%ptrim (.+)$': (source, destination, arg) =>
    say ivar2.util.trim(arg)
  '^%pcolor ([0-9]+) (.+)$': (source, destination, color, arg) =>
    say ivar2.util.color(arg, color)
  '^%pfirst (.+)$': (source, destination, arg) =>
    say ivar2.util.split(arg, '%s')[1]
  '^%psplit (.-) (.*)$': (source, destination, arg, args) =>
    say table.concat(ivar2.util.split(args, arg), ' ')
  '^%preplace (.-) (.-) (.*)$': (source, destination, pat, repl, arg) =>
    new, n = string.gsub(arg, pat, repl)
    say(new)
  '^%pnocolor (.*)$': (source, destination, arg) =>
    new, n = string.gsub(arg, '\002', '')
    new, n = string.gsub(new, '\003[0-9]+(,[0-9]+)?', '')
    say(new)
  '^%pstutter (.*)$': (source, destination, arg) =>
    -- Stutter by anders from luabot
    s_senpai = 0.65
    say arg\gsub("(%a[%w%p]+)", (w) =>
      if math.random! >= s_senpai
        return (w\sub(1, 1).."-")\rep(math.random(1, 3))..w
      else
        return w)
  '^%prot13 (.+)$': (source, destination, arg) =>
    say ivar2.util.rot13(arg)

PRIVMSG:
  '^%pupper (.+)$': (source, destination, arg) =>
    say arg\upper!
  '^%plower (.+)$': (source, destination, arg) =>
    say arg\lower!
  '^%preverse (.+)$': (source, destination, arg) =>
    say arg\reverse!
  '^%plen (.+)$': (source, destination, arg) =>
    say tostring(arg\len!)
  '^%pnicks$': (source, destination) =>
    say table.concat([n for n,k in pairs(ivar2.channels[destination].nicks)], ' ')
  '^%prandom (.+)$': (source, destination, arg) =>
    words = [word for word in arg\gmatch('%S+')]
    say words[math.random(1, #words)]
  '^%pbold (.+)$': (source, destination, arg) =>
    say ivar2.util.bold(arg)
  '^%punderline (.+)$': (source, destination, arg) =>
    say ivar2.util.underline(arg)
  '^%pitalic (.+)$': (source, destination, arg) =>
    say ivar2.util.italic(arg)
  '^%pinvert (.+)$': (source, destination, arg) =>
    say ivar2.util.reverse(arg)
  '^%ptrim (.+)$': (source, destination, arg) =>
    say ivar2.util.trim(arg)
  '^%pcolor ([0-9]+) (.+)$': (source, destination, color, arg) =>
    say ivar2.util.color(arg, color)
  '^%pfirst (.+)$': (source, destination, arg) =>
    say ivar2.util.split(arg, '%s')[1]
  '^%psplit (.-) (.*)$': (source, destination, arg, args) =>
    say table.concat(ivar2.util.split(args, arg), ' ')
  '^%preplace (.-) (.-) (.*)$': (source, destination, pat, repl, arg) =>
    new, n = string.gsub(arg, pat, repl)
    say(new)
  '^%pnocolor (.*)$': (source, destination, arg) =>
    new, n = string.gsub(arg, '\002', '')
    new, n = string.gsub(new, '\003[0-9]+(,[0-9]+)?', '')
    say(new)
  '^%pstutter (.*)$': (source, destination, arg) =>
    -- Stutter by anders from luabot
    s_senpai = 0.65
    say arg\gsub("(%a[%w%p]+)", (w) ->
      if math.random! >= s_senpai
        return (w\sub(1, 1).."-")\rep(math.random(1, 3))..w
      else
        return w)


Moon: ignore lines with empty text.

Moon: ignore lines with empty text.

Like comments, they are guaranteed to be completely blank.

export script_name        = "OptimASS"
export script_description = "Allow me to optimize your ass."
export script_author      = "torque"
export script_version     = "0.0.1"

ffi = require 'ffi'
log = require 'a-mo.log'

ffi.cdef [[
int optimASS_init( int, int );
void optimASS_addHeader( const char*, unsigned int );
void optimASS_addEvents( const char**, unsigned int*, unsigned int );
uint8_t* optimASS_checkLine( const int, const int*, const unsigned int );
void optimASS_cleanup( void );
]]

optimASS = ffi.load aegisub.decode_path "?user/automation/include/OptimASS/libOptimASS" .. (('OSX' == ffi.os) and '.dylib' or ('Windows' == ffi.os) and '.dll' or '.so')

getDirty = ( subtitle, selectedLines, activeLine ) ->
	local eventOffset, resX, resY, lineLengths, lines, eventCount

	scriptHeader = {
		"[Script Info]"
	}

	infoFields = {
		PlayResX: true
		PlayResY: true
		ScaledBorderAndShadow: true
		ScriptType: true
		WrapStyle: true
	}

	seenStyles = false
	seenDialogue = false

	subtitleLen = #subtitle

	for index = 1, subtitleLen
		with line = subtitle[index]
			if "info" == .class
				if infoFields[.key]
					table.insert scriptHeader, .raw

				if "PlayResX" == .key
					resX = tonumber .value
				elseif "PlayResY" == .key
					resY = tonumber .value

			elseif "style" == .class
				unless seenStyles
					table.insert scriptHeader, "[V4+ Styles]"
					seenStyles = true

				table.insert scriptHeader, .raw

			elseif "dialogue" == .class
				unless seenDialogue
					eventOffset = index
					table.insert scriptHeader, "[Events]"
					-- Since lua already has the lengths of the lines calculated,
					-- it seems like it would be a waste to re-re-calculate them
					-- in C.
					eventCount = subtitleLen - eventOffset + 1
					lineLengths = ffi.new "unsigned int[?]", eventCount
					lines = ffi.new "const char*[?]", eventCount
					seenDialogue = true

				arrayIndex = index - eventOffset
				lineLengths[arrayIndex] =  #.raw
				lines[arrayIndex] = .raw

	vidResX, vidResY = aegisub.video_size!

	-- If a video is loaded, use its resolution instead of the script's
	-- resolution.
	resX = vidResX or resX
	resY = vidResY or resY

	minimalHeader = table.concat( scriptHeader, '\n' ) .. '\n'
	minimalHeaderLength = #minimalHeader

	result = optimASS.optimASS_init resX, resY
	if result != 0
		log.windowError "optimASS C library init failed."

	optimASS.optimASS_addHeader minimalHeader, minimalHeaderLength
	optimASS.optimASS_addEvents lines, lineLengths, eventCount

	ffms = aegisub.frame_from_ms
	msff = aegisub.ms_from_frame
	for eventIndex = eventOffset, subtitleLen
		index = eventIndex - eventOffset
		with event = subtitle[eventIndex]
			unless .comment or ("" == .text)
				startFrame = ffms .start_time
				endFrame   = ffms .end_time
				numFrames  = endFrame - startFrame

				times = ffi.new "int[?]", numFrames

				for frame = 0, numFrames-1
					frameTime = msff startFrame + frame
					nextFrameTime = msff startFrame + frame + 1
					times[frame] = math.floor 0.5*(frameTime + nextFrameTime)

				results = optimASS.optimASS_checkLine index, times, numFrames

				for resIdx = 0, numFrames - 1
					if 0 == results[resIdx]
						log.warn "Line #{index+1} is invisible on frame #{startFrame+resIdx}."

	optimASS.optimASS_cleanup!

aegisub.register_macro "Optimize Script", "Get your ass optimized", getDirty

export script_name        = "OptimASS"
export script_description = "Allow me to optimize your ass."
export script_author      = "torque"
export script_version     = "0.0.1"

ffi = require 'ffi'
log = require 'a-mo.log'

ffi.cdef [[
int optimASS_init( int, int );
void optimASS_addHeader( const char*, unsigned int );
void optimASS_addEvents( const char**, unsigned int*, unsigned int );
uint8_t* optimASS_checkLine( const int, const int*, const unsigned int );
void optimASS_cleanup( void );
]]

optimASS = ffi.load aegisub.decode_path "?user/automation/include/OptimASS/libOptimASS" .. (('OSX' == ffi.os) and '.dylib' or ('Windows' == ffi.os) and '.dll' or '.so')

getDirty = ( subtitle, selectedLines, activeLine ) ->
	local eventOffset, resX, resY, lineLengths, lines, eventCount

	scriptHeader = {
		"[Script Info]"
	}

	infoFields = {
		PlayResX: true
		PlayResY: true
		ScaledBorderAndShadow: true
		ScriptType: true
		WrapStyle: true
	}

	seenStyles = false
	seenDialogue = false

	subtitleLen = #subtitle

	for index = 1, subtitleLen
		with line = subtitle[index]
			if "info" == .class
				if infoFields[.key]
					table.insert scriptHeader, .raw

				if "PlayResX" == .key
					resX = tonumber .value
				elseif "PlayResY" == .key
					resY = tonumber .value

			elseif "style" == .class
				unless seenStyles
					table.insert scriptHeader, "[V4+ Styles]"
					seenStyles = true

				table.insert scriptHeader, .raw

			elseif "dialogue" == .class
				unless seenDialogue
					eventOffset = index
					table.insert scriptHeader, "[Events]"
					-- Since lua already has the lengths of the lines calculated,
					-- it seems like it would be a waste to re-re-calculate them
					-- in C.
					eventCount = subtitleLen - eventOffset + 1
					lineLengths = ffi.new "unsigned int[?]", eventCount
					lines = ffi.new "const char*[?]", eventCount
					seenDialogue = true

				arrayIndex = index - eventOffset
				lineLengths[arrayIndex] =  #.raw
				lines[arrayIndex] = .raw

	vidResX, vidResY = aegisub.video_size!

	-- If a video is loaded, use its resolution instead of the script's
	-- resolution.
	resX = vidResX or resX
	resY = vidResY or resY

	minimalHeader = table.concat( scriptHeader, '\n' ) .. '\n'
	minimalHeaderLength = #minimalHeader

	result = optimASS.optimASS_init resX, resY
	if result != 0
		log.windowError "optimASS C library init failed."

	optimASS.optimASS_addHeader minimalHeader, minimalHeaderLength
	optimASS.optimASS_addEvents lines, lineLengths, eventCount

	ffms = aegisub.frame_from_ms
	msff = aegisub.ms_from_frame
	for eventIndex = eventOffset, subtitleLen
		index = eventIndex - eventOffset
		with event = subtitle[eventIndex]
			unless .comment
				startFrame = ffms .start_time
				endFrame   = ffms .end_time
				numFrames  = endFrame - startFrame

				times = ffi.new "int[?]", numFrames

				for frame = 0, numFrames-1
					frameTime = msff startFrame + frame
					nextFrameTime = msff startFrame + frame + 1
					times[frame] = math.floor 0.5*(frameTime + nextFrameTime)

				results = optimASS.optimASS_checkLine index, times, numFrames

				for resIdx = 0, numFrames - 1
					if 0 == results[resIdx]
						log.warn "Line #{index+1} is invisible on frame #{startFrame+resIdx}."

	optimASS.optimASS_cleanup!

aegisub.register_macro "Optimize Script", "Get your ass optimized", getDirty


Fix typo and boolean issue in broker minimap icon

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

{database: db, localization: L, :log, :number, xp_manager: xp} = T

{:IsControlKeyDown, :IsShiftKeyDown, :LibStub} = _G

ldb = LibStub\GetLibrary 'LibDataBroker-1.1'
icon = LibStub\GetLibrary 'LibDBIcon-1.0'

data =
    type: 'data source'
    label: NAME
    icon: "Interface\\AddOns\\#{NAME}\\icon.tga"
    tocname: NAME
    text: L.b_text_empty

click_handlers =
    none:
        LeftButton: -> log\error 'Allan please add xp sharing to chat'

tooltip_visible = false

obj = ldb\NewDataObject "Broker_#{NAME}", data

obj.OnTooltipShow = =>
    if xp\has_data!
        @AddLine L.b_header_xp
        @AddLine ' ' -- Separator
        for name, data in xp.data
            with data
                left = '%s [%d]'\format .friendly_name, .current_level
                right = '%s/%s'\format number.format(.current_xp), number.format(.max_xp)
                @AddDoubleLine left, right, 1, 1, 1, 1, 1, 1
    else
        @AddLine L.b_header_empty
    @AddLine ' ' -- Separator
    @AddDoubleLine L.leftclick, L.b_announce_smart, 0, 1, 0, 0, 1, 0

obj.OnClick = (button) =>
    mod = ((IsControlKeyDown! and 'ctrl') or (IsShiftKeyDown! and 'shift')) or 'none'
    return unless click_handlers[mod]
    func = click_handlers[mod][button]
    func! if func

update = ->
    GameTooltip\ClearLines!
    obj.OnTooltipShow GameTooltip
    GameTooltip\Show!
    tooltip_visible = true

obj.OnEnter = =>
    GameTooltip\SetOwner @, 'ANCHOR_NONE'
    GameTooltip\SetPoint 'TOPLEFT', @, 'BOTTOMLEFT'
    update!

obj.OnLeave = =>
    GameTooltip\Hide!
    tooltip_visible = false

icon_settings_table = setmetatable {},
    __index: (key) =>
        db "broker.minimap.#{key}"
    __newindex: (key, value) =>
        db\set "broker.minimap.#{key}", value

T.SHAREXP_DB_LOADED = (event, database) ->
    return unless database.global_name == db.global_name
    -- Set minimap icon defaults
    db 'broker.minimap.lock', false
    db 'broker.minimap.hide', true
    icon\Register NAME, obj, icon_settings_table

T.SHAREXP_XP_UPDATED = ->
    obj.text = "#{NAME}: Allan please add updating text"
    update! if tooltip_visible

T.broker =
    set_minimap: (enabled) =>
        -- Invert `enabled` since we're setting the 'hide' key
        db\set 'broker.minimap.hide', not enabled
        if enabled then icon\Show NAME else icon\Hide NAME

    toggle_minimap: =>
        @set_minimap db 'broker.minimap.hide'

    is_minimap_enabled: =>
        not db 'broker.minimap.hide'

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

{database: db, localization: L, :log, :number, xp_manager: xp} = T

{:IsControlKeyDown, :IsShiftKeyDown, :LibStub} = _G

ldb = LibStub\GetLibrary 'LibDataBroker-1.1'
icon = LibStub\GetLibrary 'LibDBIcon-1.0'

data =
    type: 'data source'
    label: NAME
    icon: "Interface\\AddOns\\#{NAME}\\icon.tga"
    tocname: NAME
    text: L.b_text_empty

click_handlers =
    none:
        LeftButton: -> log\error 'Allan please add xp sharing to chat'

tooltip_visible = false

obj = ldb\NewDataObject "Broker_#{NAME}", data

obj.OnTooltipShow = =>
    if xp\has_data!
        @AddLine L.b_header_xp
        @AddLine ' ' -- Separator
        for name, data in xp.data
            with data
                left = '%s [%d]'\format .friendly_name, .current_level
                right = '%s/%s'\format number.format(.current_xp), number.format(.max_xp)
                @AddDoubleLine left, right, 1, 1, 1, 1, 1, 1
    else
        @AddLine L.b_header_empty
    @AddLine ' ' -- Separator
    @AddDoubleLine L.leftclick, L.b_announce_smart, 0, 1, 0, 0, 1, 0

obj.OnClick = (button) =>
    mod = ((IsControlKeyDown! and 'ctrl') or (IsShiftKeyDown! and 'shift')) or 'none'
    return unless click_handlers[mod]
    func = click_handlers[mod][button]
    func! if func

update = ->
    GameTooltip\ClearLines!
    obj.OnTooltipShow GameTooltip
    GameTooltip\Show!
    tooltip_visible = true

obj.OnEnter = =>
    GameTooltip\SetOwner @, 'ANCHOR_NONE'
    GameTooltip\SetPoint 'TOPLEFT', @, 'BOTTOMLEFT'
    update!

obj.OnLeave = =>
    GameTooltip\Hide!
    tooltip_visible = false

icon_settings_table = setmetatable {},
    __index: (key) =>
        db "broker.minimap.#{key}"
    __newindex: (key, value) =>
        db\set "broker.minimap.#{key}", value

T.SHAREXP_DB_LOADED = (event, database) ->
    return unless database.global_name == db.global_name
    -- Set minimap icon defaults
    db 'broker.minimap.lock', false
    db 'broker.minimap.hide', true
    icon\Register NAME, obj, icon_settings_table

T.SHAREXP_XP_UPDATED = ->
    obj.text = "#{NAME}: Allan please add updating text"
    update! if tooltip_visible

T.broker =
    set_minimap: (enabled) =>
        -- Invert `enabled` since we're setting the 'hide' key
        db\set 'broker.minimap.hide', not enabled
        if enabled then icon\show NAME else icon\hide NAME

    toggle_minimap: =>
        @set_minimap db 'broker.minimap.hide'

    is_minimap_enabled: =>
        db 'broker.minimap.hide'