Make it less painful for eyes

Make it less painful for eyes

import BulletManager, Bullet from require "lib.Bullet"
PatternManager = require "lib.PatternManager"
Vector = require "hump.vector"
Boss = require "characters.Boss"
SimpleEnemy = require "characters.SimpleEnemy"
Player = require "characters.Player"
StatsPanel = require "UI.StatsPanel"
StateManager = require "lib.StateManager"
MusicPlayer = require "lib.MusicPlayer"
Moonshine = require "moonshine"
fonts = require "resources.fonts"
lovelog = require "lib.lovelog"
signal = require "hump.signal"
colorize = require "lib.colorize"
const = require "const"

FX = Moonshine(Moonshine.effects.glow)
FX.glow.strength = 3

class SceneManager
  enemies = {}
  player = nil
  canvas: love.graphics.newCanvas const.scene_width, love.graphics.getHeight!

  new: (args) =>
    @statsPanel = StatsPanel {
      width: const.panel_width
      height: const.active_screen_height
    }
    @scene = args.scene
    @spawnPlayer!

    signal.register "dead", (obj) ->
      SceneManager\removeEnemy obj
    signal.register "player meets bullet", (playerstate) ->
      BulletManager\removeAllBullets!
    signal.register "game over", (playerstate) ->
      if playerstate.lives == 0
        MusicPlayer.pauseCurrent!
        StateManager.switch {screen: "GameOver"}
    signal.register "leave scene", ->
      MusicPlayer.pauseCurrent!
    signal.register "resume scene", ->
      MusicPlayer.resumeCurrent!

  addEnemy: (e) =>
    enemies[e] = true

  spawnPlayer: (pos) =>
    player = Player!
    player\setPos const.scene_width * 0.5, const.scene_height * 0.9

  spawnBoss: (args) =>
    pos_x = args.pos.x * const.scene_width
    pos_y = args.pos.y * const.scene_height
    income_pos_x = args.income_pos.x * const.scene_width
    income_pos_y = args.income_pos.y * const.scene_height
    boss = Boss{
      pos: Vector(pos_x, pos_y)
      income_pos: Vector(income_pos_x, income_pos_y)
      modes: args.modes
    }
    enemies[boss] = true

  spawnEnemy: (arg) =>
    enemy = SimpleEnemy arg
    enemies[enemy] = true

  removeEnemy: (obj) =>
    enemies[obj] = nil

  getPlayerPosition: =>
    player.pos

  setPlayer: (p) =>
    player = p

  clear: =>
    enemies = {}
    BulletManager\removeAllBullets!

  update: (dt) =>
    for enemy in pairs enemies
      enemy\update dt
    player\update dt
    PatternManager\update dt
    BulletManager\update dt
    @statsPanel\update dt
    if @scene.update
      @scene\update dt

  draw: =>
    lovelog.reset!
    love.graphics.setCanvas @canvas
    love.graphics.setFont fonts.art
    colorize {20, 20, 20}, ->
      love.graphics.rectangle "fill", 0, 0, @canvas\getWidth!, @canvas\getHeight!
    FX ->
      for enemy, _ in pairs enemies
        enemy\draw!
      player\draw!
      BulletManager\draw!
    love.graphics.setCanvas!
    love.graphics.scale const.scaling
    love.graphics.draw @canvas
    love.graphics.scale 1.0 / const.scaling
    @statsPanel\draw!
    lovelog.print "FPS: " .. love.timer.getFPS!
    if @scene.draw
      @scene\draw!

  keyreleased: (key, rawkey) =>
    if StateManager.PLAYABLE_STATE
      player and player\keyreleased key

  keypressed: (key, rawkey) =>
    if key == "escape"
      MusicPlayer.pauseCurrent!
      StateManager.switch {screen: "PauseMenu", pause: true}
      return
    player\keypressed key

return SceneManager

import BulletManager, Bullet from require "lib.Bullet"
PatternManager = require "lib.PatternManager"
Vector = require "hump.vector"
Boss = require "characters.Boss"
SimpleEnemy = require "characters.SimpleEnemy"
Player = require "characters.Player"
StatsPanel = require "UI.StatsPanel"
StateManager = require "lib.StateManager"
MusicPlayer = require "lib.MusicPlayer"
Moonshine = require "moonshine"
fonts = require "resources.fonts"
lovelog = require "lib.lovelog"
signal = require "hump.signal"
colorize = require "lib.colorize"
const = require "const"

FX = Moonshine(Moonshine.effects.glow)
FX.glow.strength = 5

class SceneManager
  enemies = {}
  player = nil
  canvas: love.graphics.newCanvas const.scene_width, love.graphics.getHeight!

  new: (args) =>
    @statsPanel = StatsPanel {
      width: const.panel_width
      height: const.active_screen_height
    }
    @scene = args.scene
    @spawnPlayer!

    signal.register "dead", (obj) ->
      SceneManager\removeEnemy obj
    signal.register "player meets bullet", (playerstate) ->
      BulletManager\removeAllBullets!
    signal.register "game over", (playerstate) ->
      if playerstate.lives == 0
        MusicPlayer.pauseCurrent!
        StateManager.switch {screen: "GameOver"}
    signal.register "leave scene", ->
      MusicPlayer.pauseCurrent!
    signal.register "resume scene", ->
      MusicPlayer.resumeCurrent!

  addEnemy: (e) =>
    enemies[e] = true

  spawnPlayer: (pos) =>
    player = Player!
    player\setPos const.scene_width * 0.5, const.scene_height * 0.9

  spawnBoss: (args) =>
    pos_x = args.pos.x * const.scene_width
    pos_y = args.pos.y * const.scene_height
    income_pos_x = args.income_pos.x * const.scene_width
    income_pos_y = args.income_pos.y * const.scene_height
    boss = Boss{
      pos: Vector(pos_x, pos_y)
      income_pos: Vector(income_pos_x, income_pos_y)
      modes: args.modes
    }
    enemies[boss] = true

  spawnEnemy: (arg) =>
    enemy = SimpleEnemy arg
    enemies[enemy] = true

  removeEnemy: (obj) =>
    enemies[obj] = nil

  getPlayerPosition: =>
    player.pos

  setPlayer: (p) =>
    player = p

  clear: =>
    enemies = {}
    BulletManager\removeAllBullets!

  update: (dt) =>
    for enemy in pairs enemies
      enemy\update dt
    player\update dt
    PatternManager\update dt
    BulletManager\update dt
    @statsPanel\update dt
    if @scene.update
      @scene\update dt

  draw: =>
    lovelog.reset!
    love.graphics.setCanvas @canvas
    love.graphics.setFont fonts.art
    colorize {10, 10, 10}, ->
      love.graphics.rectangle "fill", 0, 0, @canvas\getWidth!, @canvas\getHeight!
    FX ->
      for enemy, _ in pairs enemies
        enemy\draw!
      player\draw!
      BulletManager\draw!
    love.graphics.setCanvas!
    love.graphics.scale const.scaling
    love.graphics.draw @canvas
    love.graphics.scale 1.0 / const.scaling
    @statsPanel\draw!
    lovelog.print "FPS: " .. love.timer.getFPS!
    if @scene.draw
      @scene\draw!

  keyreleased: (key, rawkey) =>
    if StateManager.PLAYABLE_STATE
      player and player\keyreleased key

  keypressed: (key, rawkey) =>
    if key == "escape"
      MusicPlayer.pauseCurrent!
      StateManager.switch {screen: "PauseMenu", pause: true}
      return
    player\keypressed key

return SceneManager


Fixed ducked view offset wow

Fixed ducked view offset
wow

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

PPM2.MIN_WEIGHT = 0.7
PPM2.MAX_WEIGHT = 1.3

PPM2.MIN_SCALE = 0.6
PPM2.MAX_SCALE = 1.2

PPM2.PONY_HEIGHT_MODIFIER = 0.64
PPM2.PONY_HEIGHT_MODIFIER_DUCK = 1.12

PPM2.PLAYER_VOFFSET = 64 * PPM2.PONY_HEIGHT_MODIFIER
PPM2.PLAYER_VOFFSET_DUCK = 28 * PPM2.PONY_HEIGHT_MODIFIER_DUCK

PPM2.PLAYER_VIEW_OFFSET = Vector(0, 0, PPM2.PLAYER_VOFFSET)
PPM2.PLAYER_VIEW_OFFSET_DUCK = Vector(0, 0, PPM2.PLAYER_VOFFSET_DUCK)

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

PPM2.MIN_TAIL_SIZE = 0.6
PPM2.MAX_TAIL_SIZE = 1.7 -- i luv big tails

PPM2.MIN_IRIS = 0.4
PPM2.MAX_IRIS = 1.3

PPM2.MIN_HOLE = 0.1
PPM2.MAX_HOLE = .95

PPM2.MIN_HOLE_SHIFT = -0.5
PPM2.MAX_HOLE_SHIFT = 0.5

PPM2.MIN_PUPIL_SIZE = 0.2
PPM2.MAX_PUPIL_SIZE = 1

PPM2.MIN_EYE_ROTATION = -180
PPM2.MAX_EYE_ROTATION = 180

PPM2.AvaliableTails = {
    'MAILCALL'
    'FLOOFEH'
    'ADVENTUROUS'
    'SHOWBOAT'
    'ASSERTIVE'
    'BOLD'
    'STUMPY'
    'SPEEDSTER'
    'EDGY'
    'RADICAL'
    'BOOKWORM'
    'BUMPKIN'
    'POOFEH'
    'CURLY'
    'NONE'
}

PPM2.AvaliableUpperManes = {
    'MAILCALL', 'FLOOFEH', 'ADVENTUROUS', 'SHOWBOAT', 'ASSERTIVE'
    'BOLD', 'STUMPY', 'SPEEDSTER', 'RADICAL', 'SPIKED'
    'BOOKWORM', 'BUMPKIN', 'POOFEH', 'CURLY', 'INSTRUCTOR', 'NONE'
}

PPM2.AvaliableLowerManes = {
    'MAILCALL', 'FLOOFEH', 'ADVENTUROUS', 'SHOWBOAT'
    'ASSERTIVE', 'BOLD', 'STUMPY', 'HIPPIE', 'SPEEDSTER'
    'BOOKWORM', 'BUMPKIN', 'CURLY', 'NONE'
}

PPM2.EyelashTypes = {
    'Default', 'Double', 'Coy', 'Full', 'Mess', 'None'
}

PPM2.BodyDetails = {
    'None', 'Leg gradient', 'Lines', 'Stripes', 'Head stripes'
    'Freckles', 'Hooves big', 'Hooves small', 'Head layer'
    'Hooves big rnd', 'Hooves small rnd', 'Spots 1'
}

PPM2.BodyDetailsEnum = {
    'NONE', 'GRADIENT', 'LINES', 'STRIPES', 'HSTRIPES'
    'FRECKLES', 'HOOF_BIG', 'HOOF_SMALL', 'LAYER'
    'HOOF_BIG_ROUND', 'HOOF_SMALL_ROUND', 'SPOTS'
}

PPM2.SocksTypes = {
    'DEFAULT'
    'GEOMETRIC1'
    'GEOMETRIC2'
    'GEOMETRIC3'
    'GEOMETRIC4'
    'GEOMETRIC5'
    'GEOMETRIC6'
    'GEOMETRIC7'
    'GEOMETRIC8'
    'DARK1'
    'FLOWERS10'
    'FLOWERS11'
    'FLOWERS12'
    'FLOWERS13'
    'FLOWERS14'
    'FLOWERS15'
    'FLOWERS16'
    'FLOWERS17'
    'FLOWERS18'
    'FLOWERS19'
    'FLOWERS2'
    'FLOWERS20'
    'FLOWERS3'
    'FLOWERS4'
    'FLOWERS5'
    'FLOWERS6'
    'FLOWERS7'
    'FLOWERS8'
    'FLOWERS9'
    'GREY1'
    'GREY2'
    'GREY3'
    'HEARTS1'
    'HEARTS2'
    'SNOW1'
    'WALLPAPER1'
    'WALLPAPER2'
    'WALLPAPER3'
}

PPM2.DefaultCutiemarks = {
    '8ball', 'dice', 'magichat',
    'magichat02', 'record', 'microphone',
    'bits', 'checkered', 'lumps',
    'mirror', 'camera', 'magnifier',
    'padlock', 'binaryfile', 'floppydisk',
    'cube', 'bulb', 'battery',
    'deskfan', 'flames', 'alarm',
    'myon', 'beer', 'berryglass',
    'roadsign', 'greentree', 'seasons',
    'palette', 'palette02', 'palette03',
    'lightningstone', 'partiallycloudy', 'thunderstorm',
    'storm', 'stoppedwatch', 'twistedclock',
    'surfboard', 'surfboard02', 'star',
    'ussr', 'vault', 'anarchy',
    'suit', 'deathscythe', 'shoop',
    'smiley', 'dawsome', 'weegee'
    'applej', 'applem', 'bon_bon', 'carrots', 'celestia', 'cloudy', 'custom01', 'custom02', 'derpy', 'firezap',
    'fluttershy', 'fruits', 'island', 'lyra', 'mine', 'note', 'octavia', 'pankk', 'pinkie_pie', 'rainbow_dash',
    'rarity', 'rosen', 'sflowers', 'storm', 'time', 'time2', 'trixie', 'twilight', 'waters', 'weer', 'zecora'
}

PPM2.AvaliableUpperManesNew = {
    'MAILCALL', 'FLOOFEH', 'ADVENTUROUS', 'SHOWBOAT', 'ASSERTIVE'
    'BOLD', 'STUMPY', 'SPEEDSTER', 'RADICAL', 'SPIKED'
    'BOOKWORM', 'BUMPKIN', 'POOFEH', 'CURLY', 'INSTRUCTOR', 'NONE'
}

PPM2.AvaliableLowerManesNew = {
    'MAILCALL', 'FLOOFEH', 'ADVENTUROUS', 'SHOWBOAT'
    'ASSERTIVE', 'BOLD', 'STUMPY', 'HIPPIE', 'SPEEDSTER'
    'BOOKWORM', 'BUMPKIN', 'CURLY', 'NONE'
}

PPM2.AvaliableTailsNew = {
    'MAILCALL', 'FLOOFEH', 'ADVENTUROUS', 'SHOWBOAT'
    'ASSERTIVE', 'BOLD', 'STUMPY', 'SPEEDSTER', 'EDGY'
    'RADICAL', 'BOOKWORM', 'BUMPKIN', 'POOFEH', 'CURLY', 'NONE'
}

PPM2.AvaliableEyeTypes = {
    'DEFAULT', 'APERTURE'
}

PPM2.AvaliablePonyWings = {'DEFAULT', 'BATPONY'}

PPM2.AvaliablePonySuits = {'NONE', 'ROYAL_GUARD', 'SHADOWBOLTS_FULL', 'SHADOWBOLTS_LIGHT', 'WONDERBOLTS_FULL', 'WONDERBOLTS_LIGHT'}

do
    i = -1
    for mark in *PPM2.DefaultCutiemarks
        i += 1
        PPM2["CMARK_#{mark\upper()}"] = i

PPM2.MIN_EYELASHES = 0
PPM2.MAX_EYELASHES = #PPM2.EyelashTypes - 1
PPM2.EYELASHES_NONE = #PPM2.EyelashTypes - 1

PPM2.MIN_TAILS = 0
PPM2.MAX_TAILS = #PPM2.AvaliableTails - 1

PPM2.MIN_TAILS_NEW = 0
PPM2.MAX_TAILS_NEW = #PPM2.AvaliableTailsNew - 1

PPM2.MIN_UPPER_MANES = 0
PPM2.MAX_UPPER_MANES = #PPM2.AvaliableUpperManes - 1

PPM2.MIN_LOWER_MANES = 0
PPM2.MAX_LOWER_MANES = #PPM2.AvaliableLowerManes - 1

PPM2.MIN_UPPER_MANES_NEW = 0
PPM2.MAX_UPPER_MANES_NEW = #PPM2.AvaliableUpperManesNew - 1

PPM2.MIN_LOWER_MANES_NEW = 0
PPM2.MAX_LOWER_MANES_NEW = #PPM2.AvaliableLowerManesNew - 1

PPM2.MIN_EYE_TYPE = 0
PPM2.MAX_EYE_TYPE = #PPM2.AvaliableEyeTypes - 1

PPM2.MIN_DETAIL = 0
PPM2.MAX_DETAIL = #PPM2.BodyDetails - 1

PPM2.MIN_CMARK = 0
PPM2.MAX_CMARK = #PPM2.DefaultCutiemarks - 1

PPM2.MIN_SUIT = 0
PPM2.MAX_SUIT = #PPM2.AvaliablePonySuits - 1

PPM2.MIN_WINGS = 0
PPM2.MAX_WINGS = #PPM2.AvaliablePonyWings - 1

PPM2.MIN_SOCKS = 0
PPM2.MAX_SOCKS = #PPM2.SocksTypes - 1

PPM2.GENDER_FEMALE = 0
PPM2.GENDER_MALE = 1

PPM2.MAX_BODY_DETAILS = 8

PPM2.RACE_EARTH = 0
PPM2.RACE_PEGASUS = 1
PPM2.RACE_UNICORN = 2
PPM2.RACE_ALICORN = 3
PPM2.RACE_ENUMS = {'EARTH', 'PEGASUS', 'UNICORN', 'ALICORN'}

PPM2.AGE_FILLY = 0
PPM2.AGE_ADULT = 1
PPM2.AGE_MATURE = 2
PPM2.AGE_ENUMS = {'FILLY', 'ADULT', 'MATURE'}

PPM2.MIN_DERP_STRENGTH = 0.1
PPM2.MAX_DERP_STRENGTH = 1.3

PPM2.MIN_MALE_BUFF = 0
PPM2.DEFAULT_MALE_BUFF = 1
PPM2.MAX_MALE_BUFF = 2

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

PPM2.MIN_WEIGHT = 0.7
PPM2.MAX_WEIGHT = 1.3

PPM2.MIN_SCALE = 0.6
PPM2.MAX_SCALE = 1.2

PPM2.PONY_HEIGHT_MODIFIER = 0.64
PPM2.PONY_HEIGHT_MODIFIER_DUCK = 1.12

PPM2.PLAYER_VOFFSET = 64 * PPM2.PONY_HEIGHT_MODIFIER
PPM2.PLAYER_VOFFSET_DUCK = 28 * 1.2

PPM2.PLAYER_VIEW_OFFSET = Vector(0, 0, PPM2.PLAYER_VOFFSET)
PPM2.PLAYER_VIEW_OFFSET_DUCK = Vector(0, 0, PPM2.PLAYER_VOFFSET_DUCK)

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

PPM2.MIN_TAIL_SIZE = 0.6
PPM2.MAX_TAIL_SIZE = 1.7 -- i luv big tails

PPM2.MIN_IRIS = 0.4
PPM2.MAX_IRIS = 1.3

PPM2.MIN_HOLE = 0.1
PPM2.MAX_HOLE = .95

PPM2.MIN_HOLE_SHIFT = -0.5
PPM2.MAX_HOLE_SHIFT = 0.5

PPM2.MIN_PUPIL_SIZE = 0.2
PPM2.MAX_PUPIL_SIZE = 1

PPM2.MIN_EYE_ROTATION = -180
PPM2.MAX_EYE_ROTATION = 180

PPM2.AvaliableTails = {
    'MAILCALL'
    'FLOOFEH'
    'ADVENTUROUS'
    'SHOWBOAT'
    'ASSERTIVE'
    'BOLD'
    'STUMPY'
    'SPEEDSTER'
    'EDGY'
    'RADICAL'
    'BOOKWORM'
    'BUMPKIN'
    'POOFEH'
    'CURLY'
    'NONE'
}

PPM2.AvaliableUpperManes = {
    'MAILCALL', 'FLOOFEH', 'ADVENTUROUS', 'SHOWBOAT', 'ASSERTIVE'
    'BOLD', 'STUMPY', 'SPEEDSTER', 'RADICAL', 'SPIKED'
    'BOOKWORM', 'BUMPKIN', 'POOFEH', 'CURLY', 'INSTRUCTOR', 'NONE'
}

PPM2.AvaliableLowerManes = {
    'MAILCALL', 'FLOOFEH', 'ADVENTUROUS', 'SHOWBOAT'
    'ASSERTIVE', 'BOLD', 'STUMPY', 'HIPPIE', 'SPEEDSTER'
    'BOOKWORM', 'BUMPKIN', 'CURLY', 'NONE'
}

PPM2.EyelashTypes = {
    'Default', 'Double', 'Coy', 'Full', 'Mess', 'None'
}

PPM2.BodyDetails = {
    'None', 'Leg gradient', 'Lines', 'Stripes', 'Head stripes'
    'Freckles', 'Hooves big', 'Hooves small', 'Head layer'
    'Hooves big rnd', 'Hooves small rnd', 'Spots 1'
}

PPM2.BodyDetailsEnum = {
    'NONE', 'GRADIENT', 'LINES', 'STRIPES', 'HSTRIPES'
    'FRECKLES', 'HOOF_BIG', 'HOOF_SMALL', 'LAYER'
    'HOOF_BIG_ROUND', 'HOOF_SMALL_ROUND', 'SPOTS'
}

PPM2.SocksTypes = {
    'DEFAULT'
    'GEOMETRIC1'
    'GEOMETRIC2'
    'GEOMETRIC3'
    'GEOMETRIC4'
    'GEOMETRIC5'
    'GEOMETRIC6'
    'GEOMETRIC7'
    'GEOMETRIC8'
    'DARK1'
    'FLOWERS10'
    'FLOWERS11'
    'FLOWERS12'
    'FLOWERS13'
    'FLOWERS14'
    'FLOWERS15'
    'FLOWERS16'
    'FLOWERS17'
    'FLOWERS18'
    'FLOWERS19'
    'FLOWERS2'
    'FLOWERS20'
    'FLOWERS3'
    'FLOWERS4'
    'FLOWERS5'
    'FLOWERS6'
    'FLOWERS7'
    'FLOWERS8'
    'FLOWERS9'
    'GREY1'
    'GREY2'
    'GREY3'
    'HEARTS1'
    'HEARTS2'
    'SNOW1'
    'WALLPAPER1'
    'WALLPAPER2'
    'WALLPAPER3'
}

PPM2.DefaultCutiemarks = {
    '8ball', 'dice', 'magichat',
    'magichat02', 'record', 'microphone',
    'bits', 'checkered', 'lumps',
    'mirror', 'camera', 'magnifier',
    'padlock', 'binaryfile', 'floppydisk',
    'cube', 'bulb', 'battery',
    'deskfan', 'flames', 'alarm',
    'myon', 'beer', 'berryglass',
    'roadsign', 'greentree', 'seasons',
    'palette', 'palette02', 'palette03',
    'lightningstone', 'partiallycloudy', 'thunderstorm',
    'storm', 'stoppedwatch', 'twistedclock',
    'surfboard', 'surfboard02', 'star',
    'ussr', 'vault', 'anarchy',
    'suit', 'deathscythe', 'shoop',
    'smiley', 'dawsome', 'weegee'
    'applej', 'applem', 'bon_bon', 'carrots', 'celestia', 'cloudy', 'custom01', 'custom02', 'derpy', 'firezap',
    'fluttershy', 'fruits', 'island', 'lyra', 'mine', 'note', 'octavia', 'pankk', 'pinkie_pie', 'rainbow_dash',
    'rarity', 'rosen', 'sflowers', 'storm', 'time', 'time2', 'trixie', 'twilight', 'waters', 'weer', 'zecora'
}

PPM2.AvaliableUpperManesNew = {
    'MAILCALL', 'FLOOFEH', 'ADVENTUROUS', 'SHOWBOAT', 'ASSERTIVE'
    'BOLD', 'STUMPY', 'SPEEDSTER', 'RADICAL', 'SPIKED'
    'BOOKWORM', 'BUMPKIN', 'POOFEH', 'CURLY', 'INSTRUCTOR', 'NONE'
}

PPM2.AvaliableLowerManesNew = {
    'MAILCALL', 'FLOOFEH', 'ADVENTUROUS', 'SHOWBOAT'
    'ASSERTIVE', 'BOLD', 'STUMPY', 'HIPPIE', 'SPEEDSTER'
    'BOOKWORM', 'BUMPKIN', 'CURLY', 'NONE'
}

PPM2.AvaliableTailsNew = {
    'MAILCALL', 'FLOOFEH', 'ADVENTUROUS', 'SHOWBOAT'
    'ASSERTIVE', 'BOLD', 'STUMPY', 'SPEEDSTER', 'EDGY'
    'RADICAL', 'BOOKWORM', 'BUMPKIN', 'POOFEH', 'CURLY', 'NONE'
}

PPM2.AvaliableEyeTypes = {
    'DEFAULT', 'APERTURE'
}

PPM2.AvaliablePonyWings = {'DEFAULT', 'BATPONY'}

PPM2.AvaliablePonySuits = {'NONE', 'ROYAL_GUARD', 'SHADOWBOLTS_FULL', 'SHADOWBOLTS_LIGHT', 'WONDERBOLTS_FULL', 'WONDERBOLTS_LIGHT'}

do
    i = -1
    for mark in *PPM2.DefaultCutiemarks
        i += 1
        PPM2["CMARK_#{mark\upper()}"] = i

PPM2.MIN_EYELASHES = 0
PPM2.MAX_EYELASHES = #PPM2.EyelashTypes - 1
PPM2.EYELASHES_NONE = #PPM2.EyelashTypes - 1

PPM2.MIN_TAILS = 0
PPM2.MAX_TAILS = #PPM2.AvaliableTails - 1

PPM2.MIN_TAILS_NEW = 0
PPM2.MAX_TAILS_NEW = #PPM2.AvaliableTailsNew - 1

PPM2.MIN_UPPER_MANES = 0
PPM2.MAX_UPPER_MANES = #PPM2.AvaliableUpperManes - 1

PPM2.MIN_LOWER_MANES = 0
PPM2.MAX_LOWER_MANES = #PPM2.AvaliableLowerManes - 1

PPM2.MIN_UPPER_MANES_NEW = 0
PPM2.MAX_UPPER_MANES_NEW = #PPM2.AvaliableUpperManesNew - 1

PPM2.MIN_LOWER_MANES_NEW = 0
PPM2.MAX_LOWER_MANES_NEW = #PPM2.AvaliableLowerManesNew - 1

PPM2.MIN_EYE_TYPE = 0
PPM2.MAX_EYE_TYPE = #PPM2.AvaliableEyeTypes - 1

PPM2.MIN_DETAIL = 0
PPM2.MAX_DETAIL = #PPM2.BodyDetails - 1

PPM2.MIN_CMARK = 0
PPM2.MAX_CMARK = #PPM2.DefaultCutiemarks - 1

PPM2.MIN_SUIT = 0
PPM2.MAX_SUIT = #PPM2.AvaliablePonySuits - 1

PPM2.MIN_WINGS = 0
PPM2.MAX_WINGS = #PPM2.AvaliablePonyWings - 1

PPM2.MIN_SOCKS = 0
PPM2.MAX_SOCKS = #PPM2.SocksTypes - 1

PPM2.GENDER_FEMALE = 0
PPM2.GENDER_MALE = 1

PPM2.MAX_BODY_DETAILS = 8

PPM2.RACE_EARTH = 0
PPM2.RACE_PEGASUS = 1
PPM2.RACE_UNICORN = 2
PPM2.RACE_ALICORN = 3
PPM2.RACE_ENUMS = {'EARTH', 'PEGASUS', 'UNICORN', 'ALICORN'}

PPM2.AGE_FILLY = 0
PPM2.AGE_ADULT = 1
PPM2.AGE_MATURE = 2
PPM2.AGE_ENUMS = {'FILLY', 'ADULT', 'MATURE'}

PPM2.MIN_DERP_STRENGTH = 0.1
PPM2.MAX_DERP_STRENGTH = 1.3

PPM2.MIN_MALE_BUFF = 0
PPM2.DEFAULT_MALE_BUFF = 1
PPM2.MAX_MALE_BUFF = 2


irc: New parser

irc: New parser

--- IRC client class
-- @classmod IRCClient

re = require "re"
socket = require 'cqueues.socket'
Logger = require 'logger'

escapers =  {['s']: ' ', ['r']: '\r', ['n']: '\n', [';']: ';'}

local IRCClient, ContextTable

class ContextTable
	-- ::TODO:: allow for multiple *and* index
	get: (name, opts = {})=>
		:multiple, :index = opts
		multiple = true if multiple == nil

		local output
		output = {} if multiple
		for context, items in pairs(self)
			-- context
			unless multiple
				-- return the singular if it exists
				return items[name] if items[name] ~= nil
			elseif items[name]
				-- add all that exist
				for element in *items[name]
					table.insert output, element

		if multiple
			output
		elseif index
			index\get name, :multiple

class IRCClient
	commands: ContextTable!
	hooks: ContextTable!
	handlers: ContextTable!
	senders: ContextTable!

	default_config = {
		prefix: "!"
	}

	line_pattern = re.compile [[
		-- tags, command, args
		line <- {| (tags sp)? (prefix sp)? {:command: (command / numeric) :}
			{:args: {| (sp arg)* |} :} |}
		tags <- {:tags: '@' tag (';' tag)* :}
		tag <- {| {:vendor: {[^/]+} '/' :}?
			{:key: {[^=; ]+} -> esc_tag :}
			{:value: ('=' [^; ]+) -> esc_tag :}?
		|}
		prefix <- ':' (
			{:nick: {[^ !]+} :} '!'
			{:user: {[^ @]+} :} '@'
			{:host: {[^ ]+} :} /
			{:nick: {[^ ]+} :})
		command <- [A-Za-z]+
		numeric <- %d^+3^-4 -- at most four digits, at least three
		arg <- ':' {.+} / {%S+}
		sp <- %s
	]], esc_tag: (tag)-> tag\gsub "\\(.)", setmetatable({
		[";"]: ":"
		s: " "
		r: "\r"
		n: "\n"
	}, __index: (t, k) -> k)

	--- Generate a new IRCClient
	-- @tparam string server IRC server name
	-- @tparam number port IRC port number
	-- @tparam table config Default configuration
	new: (server, port=6697, config)=>
		assert(server)
		@config = :server, :port, :config, ssl: port == 6697
		for k, v in pairs(default_config)
			@config[k] = v
		if config
			for k, v in pairs(config)
				@config[k] = v

		@commands = ContextTable!
		@hooks    = ContextTable!
		@handlers = ContextTable!
		@senders  = ContextTable!
		@server   = {}

	unpack = unpack or table.unpack

	handle_options = (options, fn)->
		unless fn
			fn = options
			options = {}

		if options.async
			return (...)->
				args = {...}
				require("queue")\wrap ->
					fn(unpack(args))

		fn

	assert_context = ()=> assert @context, "Missing context"

	--- Change the module context for the current command
	-- @tparam string context module context (typically, the name)
	-- @tparam function fn code to run under context
	with_context: (context, fn)=>
		assert @context == nil, "Already in context: #{@context}"
		@context = context

		for key in *{"hooks", "handlers", "commands", "senders"}
			self[key][context] = {}

		fn self, context
		@context = nil

	--- Add an IRC bot command
	-- @tparam string name Bot command name
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function command Function for handling command
	add_command: (name, options, command)=>
		context = assert_context self
		@commands[context][name] = handle_options(options, command)

	--- Add a client processing hook
	-- @tparam string name Name of event to hook into
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function hook Processor for hook event
	add_hook: (name, options, hook)=>
		context = assert_context self

		hooks = @hooks[context]

		if not hooks[name]
			hooks[name] = {handle_options(options, hook)}
		else
			table.insert hooks[name], handle_options(options, hook)

	--- Add an IRC command handler
	-- @tparam string id IRC command ID (numerics MUST be strings)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function handler IRC command processor
	add_handler: (id, options, handler)=>
		context = assert_context self

		handlers = @handlers[context]

		unless handlers[id]
			handlers[id] = {handle_options(options, handler)}
		else
			table.insert handlers[id], handle_options(options, handler)

	--- Add an IRC command sending handler
	-- @tparam string id IRC command ID ("PRIVMSG", "JOIN", etc.)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function sender Function to handle message to be sent
	add_sender: (id, options, sender)=>
		context = assert_context self
		@senders[context][id] = handle_options(options, sender)

	--- Reset all modules in the IRCClient
	clear_modules: ()=>
		@senders = {}
		@handlers = {}
		@hooks = {}
		@commands = {}

	--- Connect to the IRC server specified in the configuration
	connect: ()=>
		if @socket
			Logger.debug "Shutting down socket: #{tostring(@socket)}"
			@socket\shutdown!
		host = @config.server
		port = @config.port
		ssl  = @config.ssl
		debug_msg = ('Connecting... {host: "%s", port: "%s"}')\format host, port

		@config.nick = 'Moon-Moon' if not @config.nick
		@config.username = 'Mooooon' if not @config.username
		@config.realname = 'Moon Moon: MoonScript IRC Bot' if not @config.realname

		Logger.debug debug_msg, Logger.level.warn .. '--- Connecting...'
		@socket = assert socket.connect{:host, :port}
		if ssl
			Logger.debug 'Starting TLS exchange...'
			@socket\starttls!
			Logger.debug 'Started TLS exchange'
		Logger.print Logger.level.okay .. '--- Connected'
		@fire_hook 'CONNECT'
		nick = @config.nick
		user = @config.username
		real = @config.realname
		pass = @config.password
		Logger.print Logger.level.warn .. '--- Sending authentication data'
		@send_raw ('NICK %s')\format nick
		if pass and ssl
			Logger.debug '*** Sending password'
			@send_raw ('PASS :%s')\format pass
		elseif pass
			Logger.print Logger.level.error .. '*** Not sending password: TLS not enabled ***'
		@send_raw ('USER %s * * :%s')\format user, real
		debug_msg = ('Sent authentication data: {nickname: %s, username: %s, realname: %s}')\format nick, user, real
		Logger.debug debug_msg, Logger.level.okay .. '--- Sent authentication data'

	--- Disconnect from the current IRC server
	disconnect: ()=>
		@socket\shutdown! if @socket
		@fire_hook 'DISCONNECT'

	--- Send a raw line to the currently connected IRC server
	-- @param ... List of strings, concatenated using spaces
	send_raw: (...)=>
		@socket\write table.concat({...}, ' ') .. '\n'
		Logger.debug '==> ' .. table.concat {...}, ' '

	--- Send a command using a builtin sender configured with @add_sender
	-- @tparam string name Name of sender to use
	-- @param ... List of arguments to be passed to sender
	-- @see IRCClient\add_sender
	send: (name, ...)=>
		sender = @senders\get name, multiple: false, index: IRCClient.senders
		@send_raw sender(self, ...)

	date_pattern: "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+).(%d+)Z"

	--- Parse the time of an ISO 8601 compliant string
	-- @tparam string date ISO 8601 compliant YYYY-MM-DDThh:mm:ssZ date
	-- @treturn number Seconds from Epoch from configured date
	parse_time: (date)=>
		year, month, day, hour, min, sec, mil = date\match @date_pattern
		return os.time(:year, :month, :day, :hour, :min, :sec) + tonumber(mil) / 1000

	--- parse an IRC command using line_pattern
	parse: (line)=> line_pattern\match line

	--- Activate hooks configured for the event name
	-- @tparam string hook_name Name of event to "fire"
	-- @treturn boolean True if no errors were encountered, false otherwise
	-- @treturn table Table containing list of errors from hooks
	fire_hook: (hook_name, ...)=>
		Logger.debug "#{Logger.level.warn}--- Running hooks for #{hook_name}"
		has_run = false
		has_errors = false
		errors = {}

		for _, hook in pairs IRCClient.hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running global hook: ' .. hook_name
			ok, err = pcall hook, @, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)
				Logger.print Logger.level.error .. '*** ' .. err
		for _, hook in pairs @hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running hook: ' .. hook_name
			ok, err = pcall hook, @, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)
				Logger.print Logger.level.error .. '*** ' .. err

		Logger.debug Logger.level.error .. "*** Handler not found for #{command}" unless has_run

		return has_errors, errors

	--- Run handlers for an unparsed command
	-- @tparam string line Incoming wire-string formatted line from IRC server
	process: (line)=>
		{
			:tags,
			:nick,
			:user,
			:host,
			:command,
			:args = @parse line
		)
		Logger.debug Logger.level.warn .. '--- | Line: ' .. line
		Logger.debug Logger.level.okay .. '--- |\\ Running trigger: ' .. Logger.level.warn .. command
		if nick and user and host
			Logger.debug "#{Logger.level.okay} --- |\\ User: #{nick}!#{user}@#{host}"
		elseif nick
			Logger.debug "#{Logger.level.okay} --- |\\ Nick: #{nick}"
		if #args > 0
			Logger.debug "#{Logger.level.okay} --- |\\ Arguments: #{table.concat args}"

		has_run = false

		for handler in *IRCClient.handlers\get command
			has_run = true unless has_run
			ok, err, tb = xpcall handler, self\log_traceback, self,
				{:nick, :user, :host}, args, tags
			if not ok
				@log tb
				@log Logger.level.error .. '*** ' .. err

		for handler in *@handlers\get command
			has_run = true unless has_run
			ok, err = xpcall handler, self\log_traceback, @,
				prefix, args, rest, tags
			if not ok
				Logger.print Logger.level.error .. '*** ' .. err

		Logger.debug Logger.level.error .. "*** Handler not found for #{command}" unless has_run

	--- Iterate over lines from a server and handle errors appropriately
	loop: ()=>
		local line
		print_error = (err)->
			Logger.debug "Error: " .. err .. " (" .. line .. ")"

		for received_line in @socket\lines! do
			line = received_line
			xpcall @process, print_error, @, received_line

	--- Print a traceback using the internal logging mechanism
	-- @see IRCClient\log
	log_traceback: (err)=>
		@log Logger.level.error .. '*** ' .. err
		@log debug.traceback()
		@log Logger.level.error .. '---'

	--- Log message from IRC server (used in plugins)
	-- @tparam string input Line to print, IRC color formatted
	-- @see logger
	log: (input)=>
		for line in input\gmatch "[^\r\n]+"
			Logger.print '\00311(\003' .. (@server.caps and
				@server.caps['NETWORK'] or @config.server) ..
				"\00311)\003 #{line}"

return {:IRCClient}

--- IRC client class
-- @classmod IRCClient

socket = require 'cqueues.socket'
Logger = require 'logger'

escapers =  {['s']: ' ', ['r']: '\r', ['n']: '\n', [';']: ';'}

local IRCClient, ContextTable

class ContextTable
	-- ::TODO:: allow for multiple *and* index
	get: (name, opts = {})=>
		:multiple, :index = opts
		multiple = true if multiple == nil

		local output
		output = {} if multiple
		for context, items in pairs(self)
			-- context
			unless multiple
				-- return the singular if it exists
				return items[name] if items[name] ~= nil
			elseif items[name]
				-- add all that exist
				for element in *items[name]
					table.insert output, element

		if multiple
			output
		elseif index
			index\get name, :multiple

class IRCClient
	commands: ContextTable!
	hooks: ContextTable!
	handlers: ContextTable!
	senders: ContextTable!

	default_config = {
		prefix: "!"
	}

	--- Generate a new IRCClient
	-- @tparam string server IRC server name
	-- @tparam number port IRC port number
	-- @tparam table config Default configuration
	new: (server, port=6697, config)=>
		assert(server)
		@config = :server, :port, :config, ssl: port == 6697
		for k, v in pairs(default_config)
			@config[k] = v
		if config
			for k, v in pairs(config)
				@config[k] = v

		@commands = ContextTable!
		@hooks    = ContextTable!
		@handlers = ContextTable!
		@senders  = ContextTable!
		@server   = {}

	unpack = unpack or table.unpack

	handle_options = (options, fn)->
		unless fn
			fn = options
			options = {}

		if options.async
			return (...)->
				args = {...}
				require("queue")\wrap ->
					fn(unpack(args))

		fn

	assert_context = ()=> assert @context, "Missing context"

	--- Change the module context for the current command
	-- @tparam string context module context (typically, the name)
	-- @tparam function fn code to run under context
	with_context: (context, fn)=>
		assert @context == nil, "Already in context: #{@context}"
		@context = context

		for key in *{"hooks", "handlers", "commands", "senders"}
			self[key][context] = {}

		fn self, context
		@context = nil

	--- Add an IRC bot command
	-- @tparam string name Bot command name
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function command Function for handling command
	add_command: (name, options, command)=>
		context = assert_context self
		@commands[context][name] = handle_options(options, command)

	--- Add a client processing hook
	-- @tparam string name Name of event to hook into
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function hook Processor for hook event
	add_hook: (name, options, hook)=>
		context = assert_context self

		hooks = @hooks[context]

		if not hooks[name]
			hooks[name] = {handle_options(options, hook)}
		else
			table.insert hooks[name], handle_options(options, hook)

	--- Add an IRC command handler
	-- @tparam string id IRC command ID (numerics MUST be strings)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function handler IRC command processor
	add_handler: (id, options, handler)=>
		context = assert_context self

		handlers = @handlers[context]

		unless handlers[id]
			handlers[id] = {handle_options(options, handler)}
		else
			table.insert handlers[id], handle_options(options, handler)

	--- Add an IRC command sending handler
	-- @tparam string id IRC command ID ("PRIVMSG", "JOIN", etc.)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function sender Function to handle message to be sent
	add_sender: (id, options, sender)=>
		context = assert_context self
		@senders[context][id] = handle_options(options, sender)

	--- Reset all modules in the IRCClient
	clear_modules: ()=>
		@senders = {}
		@handlers = {}
		@hooks = {}
		@commands = {}

	--- Connect to the IRC server specified in the configuration
	connect: ()=>
		if @socket
			Logger.debug "Shutting down socket: #{tostring(@socket)}"
			@socket\shutdown!
		host = @config.server
		port = @config.port
		ssl  = @config.ssl
		debug_msg = ('Connecting... {host: "%s", port: "%s"}')\format host, port

		@config.nick = 'Moon-Moon' if not @config.nick
		@config.username = 'Mooooon' if not @config.username
		@config.realname = 'Moon Moon: MoonScript IRC Bot' if not @config.realname

		Logger.debug debug_msg, Logger.level.warn .. '--- Connecting...'
		@socket = assert socket.connect{:host, :port}
		if ssl
			Logger.debug 'Starting TLS exchange...'
			@socket\starttls!
			Logger.debug 'Started TLS exchange'
		Logger.print Logger.level.okay .. '--- Connected'
		@fire_hook 'CONNECT'
		nick = @config.nick
		user = @config.username
		real = @config.realname
		pass = @config.password
		Logger.print Logger.level.warn .. '--- Sending authentication data'
		@send_raw ('NICK %s')\format nick
		if pass and ssl
			Logger.debug '*** Sending password'
			@send_raw ('PASS :%s')\format pass
		elseif pass
			Logger.print Logger.level.error .. '*** Not sending password: TLS not enabled ***'
		@send_raw ('USER %s * * :%s')\format user, real
		debug_msg = ('Sent authentication data: {nickname: %s, username: %s, realname: %s}')\format nick, user, real
		Logger.debug debug_msg, Logger.level.okay .. '--- Sent authentication data'

	--- Disconnect from the current IRC server
	disconnect: ()=>
		@socket\shutdown! if @socket
		@fire_hook 'DISCONNECT'

	--- Send a raw line to the currently connected IRC server
	-- @param ... List of strings, concatenated using spaces
	send_raw: (...)=>
		@socket\write table.concat({...}, ' ') .. '\n'
		Logger.debug '==> ' .. table.concat {...}, ' '

	--- Send a command using a builtin sender configured with @add_sender
	-- @tparam string name Name of sender to use
	-- @param ... List of arguments to be passed to sender
	-- @see IRCClient\add_sender
	send: (name, ...)=>
		sender = @senders\get name, multiple: false, index: IRCClient.senders
		@send_raw sender(self, ...)

	date_pattern: "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+).(%d+)Z"

	--- Parse the time of an ISO 8601 compliant string
	-- @tparam string date ISO 8601 compliant YYYY-MM-DDThh:mm:ssZ date
	-- @treturn number Seconds from Epoch from configured date
	parse_time: (date)=>
		year, month, day, hour, min, sec, mil = date\match @date_pattern
		return os.time(:year, :month, :day, :hour, :min, :sec) + tonumber(mil) / 1000

	--- Grab tags from an IRCv3 message
	-- @tparam string tag_message Message containing IRCv3 tags
	-- @treturn table Table containing tags, client tags preceded with a "+"
	parse_tags: (tag_message)=>
		local cur_name
		tags = {}
		charbuf = {}
		pos = 1
		while pos < #tag_message do
			if tag_message\match '^\\', pos
				lookahead = tag_message\sub pos+1, pos+1
				charbuf[#charbuf + 1] = escapers[lookahead] or lookahead
				pos += 2
			elseif cur_name
				if tag_message\match "^;", pos
					tags[cur_name] = table.concat charbuf
					cur_name = nil
					charbuf = {}
					pos += 1
				else
					charbuf[#charbuf + 1], pos = tag_message\match "([^\\;]+)()", pos
			else
				if tag_message\match "^=", pos
					if #charbuf > 0
						cur_name = table.concat charbuf
						charbuf = {}
					pos += 1
				elseif tag_message\match "^;", pos
					if #charbuf > 0
						tags[table.concat charbuf] = true
						charbuf = {}
					pos += 1
				else
					charbuf[#charbuf + 1], pos = tag_message\match "([^\\=;]+)()", pos
		return tags

	--- Parse an IRCv3 compatible wire-format string
	-- @tparam string message_with_tags IRCv3 compliant wire-format command
	-- @treturn table User prefix, if sent by a user or server
	-- @treturn string Command sent to client
	-- @treturn table List of arguments, trailing argument not included
	-- @treturn string Trailing possibly space-inclusive message
	-- @treturn table IRCv3 compatible tag list
	-- @see IRCClient\parse_tags
	parse: (message_with_tags)=>
		local message, tags
		if message_with_tags\sub(1, 1) == '@'
			tags = @parse_tags message_with_tags\sub 2, message_with_tags\find(' ') - 1
			message = message_with_tags\sub((message_with_tags\find(' ') + 1))
		else
			message = message_with_tags
		prefix_end = 0
		prefix = nil
		if message\sub(1, 1) == ':'
			prefix_end = message\find ' '
			prefix = message\sub 2, message\find(' ') - 1

		trailing = nil
		tstart = message\find ' :'
		if tstart
			trailing = message\sub tstart + 2
		else
			tstart = #message

		rest = ((segment)->
			t = {}
			for word in segment\gmatch '%S+'
				table.insert t, word

			return t
		)(message\sub prefix_end + 1, tstart)

		command = rest[1]
		table.remove(rest, 1)
		return prefix, command, rest, trailing, tags

	--- Activate hooks configured for the event name
	-- @tparam string hook_name Name of event to "fire"
	-- @treturn boolean True if no errors were encountered, false otherwise
	-- @treturn table Table containing list of errors from hooks
	fire_hook: (hook_name, ...)=>
		Logger.debug "#{Logger.level.warn}--- Running hooks for #{hook_name}"
		has_run = false
		has_errors = false
		errors = {}

		for _, hook in pairs IRCClient.hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running global hook: ' .. hook_name
			ok, err = pcall hook, @, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)
				Logger.print Logger.level.error .. '*** ' .. err
		for _, hook in pairs @hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running hook: ' .. hook_name
			ok, err = pcall hook, @, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)
				Logger.print Logger.level.error .. '*** ' .. err

		Logger.debug Logger.level.error .. "*** Handler not found for #{command}" unless has_run

		return has_errors, errors

	--- Run handlers for an unparsed command
	-- @tparam string line Incoming wire-string formatted line from IRC server
	process: (line)=>
		prefix, command, args, rest, tags = @parse line
		Logger.debug Logger.level.warn .. '--- | Line: ' .. line
		Logger.debug Logger.level.okay .. '--- |\\ Running trigger: ' .. Logger.level.warn .. command
		if prefix
			Logger.debug Logger.level.okay .. '--- |\\ Prefix: ' .. prefix
		if #args > 0
			Logger.debug Logger.level.okay .. '--- |\\ Arguments: ' .. table.concat(args, ', ')
		if rest
			Logger.debug Logger.level.okay .. '--- |\\ Trailing: ' .. rest

		has_run = false

		for handler in *IRCClient.handlers\get command
			has_run = true unless has_run
			ok, err, tb = xpcall handler, self\log_traceback, self,
				prefix, args, rest, tags
			if not ok
				@log tb
				@log Logger.level.error .. '*** ' .. err

		for handler in *@handlers\get command
			has_run = true unless has_run
			ok, err = xpcall handler, self\log_traceback, @,
				prefix, args, rest, tags
			if not ok
				Logger.print Logger.level.error .. '*** ' .. err

		Logger.debug Logger.level.error .. "*** Handler not found for #{command}" unless has_run

	--- Iterate over lines from a server and handle errors appropriately
	loop: ()=>
		local line
		print_error = (err)->
			Logger.debug "Error: " .. err .. " (" .. line .. ")"

		for received_line in @socket\lines! do
			line = received_line
			xpcall @process, print_error, @, received_line

	--- Print a traceback using the internal logging mechanism
	-- @see IRCClient\log
	log_traceback: (err)=>
		@log Logger.level.error .. '*** ' .. err
		@log debug.traceback()
		@log Logger.level.error .. '---'

	--- Log message from IRC server (used in plugins)
	-- @tparam string input Line to print, IRC color formatted
	-- @see logger
	log: (input)=>
		for line in input\gmatch "[^\r\n]+"
			Logger.print '\00311(\003' .. (@server.caps and
				@server.caps['NETWORK'] or @config.server) ..
				"\00311)\003 #{line}"

return {:IRCClient}


Fixed Lua error

Fixed Lua error

--
-- Copyright (C) 2017-2020 DBotThePony

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

doPatch = =>
	return if not @IsValid()
	local target

	for _, child in ipairs @GetChildren()
		if child\GetName() == 'DIconLayout'
			target = child
			break

	return if not target
	local buttonTarget

	for _, button in ipairs target\GetChildren()
		buttonChilds = button\GetChildren()
		cond1 = buttonChilds[1] and buttonChilds[1]\GetName() == 'DLabel'
		cond2 = buttonChilds[2] and buttonChilds[2]\GetName() == 'DLabel'
		if cond1 and buttonChilds[1]\GetText() == 'Player Model'
			buttonTarget = button
			break
		elseif cond2 and buttonChilds[2]\GetText() == 'Player Model'
			buttonTarget = button
			break

	return if not buttonTarget
	{:title, :init, :icon, :width, :height, :onewindow} = list.Get('DesktopWindows').PlayerEditor
	buttonTarget.DoClick = ->
		return buttonTarget.Window\Center() if onewindow and IsValid(buttonTarget.Window)

		buttonTarget.Window = @Add('DFrame')
		with buttonTarget.Window
			\SetSize(width, height)
			\SetTitle(title)
			\Center()

		init(buttonTarget, buttonTarget.Window)
		local targetModel

		for _, child in ipairs buttonTarget.Window\GetChildren()
			if child\GetName() == 'DModelPanel'
				targetModel = child
				break

		return if not targetModel
		targetModel.oldSetModel = targetModel.SetModel
		targetModel.SetModel = (model) =>
			oldModel = @Entity\GetModel()
			oldPonyData = @Entity\GetPonyData()
			@oldSetModel(model)
			if IsValid(@Entity) and oldPonyData
				oldPonyData\SetupEntity(@Entity)
				oldPonyData\ModelChanges(oldModel, model)
		targetModel.PreDrawModel = (ent) =>
			controller = @ponyController
			return if not controller
			return if not ent\IsPony()
			controller\SetupEntity(ent) if controller.ent ~= ent
			controller\GetRenderController()\DrawModels()
			controller\GetRenderController()\PreDraw(ent)
			controller\GetRenderController()\HideModels(true)
			bg = controller\GetBodygroupController()
			bg\ApplyBodygroups() if bg

		copy = PPM2.GetMainData()\Copy()
		controller = copy\CreateCustomController(targetModel.Entity)
		copy\SetNetworkObject(controller)
		controller\SetDisableTask(true)
		targetModel.ponyController = controller

		hook.Run 'BuildPlayerModelMenu', buttonTarget, buttonTarget.Window

hook.Add 'ContextMenuCreated', 'PPM2.PatchPlayerModelMenu', => timer.Simple 0, -> doPatch(@)

--
-- Copyright (C) 2017-2020 DBotThePony

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

doPatch = =>
	return if not @IsValid()
	local target

	for _, child in ipairs @GetChildren()
		if child\GetName() == 'DIconLayout'
			target = child
			break

	return if not target
	local buttonTarget

	for _, button in ipairs target\GetChildren()
		buttonChilds = button\GetChildren()
		cond1 = buttonChilds[1] and buttonChilds[1]\GetName() == 'DLabel'
		cond2 = buttonChilds[2] and buttonChilds[2]\GetName() == 'DLabel'
		if cond1 and buttonChilds[1]\GetText() == 'Player Model'
			buttonTarget = button
			break
		elseif cond2 and buttonChilds[2]\GetText() == 'Player Model'
			buttonTarget = button
			break

	return if not buttonTarget
	{:title, :init, :icon, :width, :height, :onewindow} = list.Get('DesktopWindows').PlayerEditor
	buttonTarget.DoClick = ->
		return buttonTarget.Window\Center() if onewindow and IsValid(buttonTarget.Window)

		buttonTarget.Window = @Add('DFrame')
		with buttonTarget.Window
			\SetSize(width, height)
			\SetTitle(title)
			\Center()

		init(buttonTarget, buttonTarget.Window)
		local targetModel

		for _, child in ipairs buttonTarget.Window\GetChildren()
			if child\GetName() == 'DModelPanel'
				targetModel = child
				break

		return if not targetModel
		targetModel.oldSetModel = targetModel.SetModel
		targetModel.SetModel = (model) =>
			oldModel = @Entity\GetModel()
			oldPonyData = @Entity\GetPonyData()
			@oldSetModel(model)
			if IsValid(@Entity) and oldPonyData
				oldPonyData\SetupEntity(@Entity)
				oldPonyData\ModelChanges(oldModel, model)
		targetModel.PreDrawModel = (ent) =>
			controller = @ponyController
			return if not controller
			return if not ent\IsPony()
			controller\SetupEntity(ent) if controller.ent ~= ent
			controller\GetRenderController()\DrawModels()
			controller\GetRenderController()\PreDraw(ent)
			controller\GetRenderController()\HideModels(true)
			bg = controller\GetBodygroupController()
			bg\ApplyBodygroups() if bg

		copy = PPM2.GetMainData()\Copy()
		controller = copy\CreateCustomController(targetModel.Entity)
		copy\SetNetworkData(controller)
		controller\SetDisableTask(true)
		targetModel.ponyController = controller

		hook.Run 'BuildPlayerModelMenu', buttonTarget, buttonTarget.Window

hook.Add 'ContextMenuCreated', 'PPM2.PatchPlayerModelMenu', => timer.Simple 0, -> doPatch(@)


anagram: clean up html

anagram: clean up html

Former-commit-id: 76aa4feec8f7965f83738d45af7eeb878860fee0
Former-commit-id: 2d654a66d9ea9db367e9ba50b81b8c2e45c455ba

html2unicode = require'html'

PRIVMSG:
  '^%panagram (.+)$': (s, d, arg) =>
    ivar2.util.simplehttp "http://www.wordsmith.org/anagram/anagram.cgi?t=50&anagram=" .. ivar2.util.urlEncode(arg), (s) ->
      s = s\match "(Displaying%s.-)</?[Pp][ />]"
      unless s
        return say "Did not find result"
      s = s\gsub "[\r\n]", ""
      s = s\gsub "<script>.*", ""
      s = s\gsub "<[Bb][Rr][ />]", ", "
      -- Strip tags
      s = s\gsub "%b<>", ""
      say html2unicode(s)\gsub(":%s*,", ":")\gsub(",%s*$", "")

html2unicode = require'html'

PRIVMSG:
  '^%panagram (.+)$': (s, d, arg) =>
    ivar2.util.simplehttp "http://www.wordsmith.org/anagram/anagram.cgi?t=50&anagram=" .. ivar2.util.urlEncode(arg), (s) ->
      s = s\match "(Displaying%s.-)</?[Pp][ />]"
      unless s
        return say "Did not find result"
      s = s\gsub "<[Bb][Rr][ />]", ", "
      -- Strip tags
      s = s\gsub "%b<>", ""
      say html2unicode(s)\gsub(":%s*,", ":")\gsub(",%s*$", "")