utf8: new function mimic, for generating homograph

util = require'util'
html2unicode = require'html'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
charmaps = {
ci: 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl: '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud: [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]
nc: [[🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿]]
sq: [[🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789,⊡]]
ns: [[🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉]]
ds: [[𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡]]
bo: [[𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗]]
bi: [[𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛0123456789]]
bs: [[𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃]]
pt: [[⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵0⑴⑵⑶⑷⑸⑹⑺⑻⑼]]
tl: [[ค๒ς๔єŦﻮђเןкl๓ภ๏קợгรtยשฬץאzค๒ς๔єŦﻮђเןкl๓ภ๏קợгรtยשฬץאz0123456789,.؟!"'`()[]{}«»&_]]
}
codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

mimic = {
  " ": {" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "},
  ["!"]: {"！", "ǃ", "ⵑ", "︕", "﹗", },
  ["\""]: {"＂", },
  ["#"]: {"＃", "﹟", },
  ["$"]: {"＄", "﹩", },
  ["%"]: {"％", "٪", "⁒", "﹪", },
  ["&"]: {"＆", "﹠", },
  ["'"]: {"＇", "ʹ", },
  ["("]: {"（", "⟮", "﹙", },
  [")"]: {"）", "⟯", "﹚", },
  ["*"]: {"＊", "⋆", "﹡", },
  ["+"]: {"＋", "᛭", "﹢", },
  [","]: {"，", "ˏ", "ᛧ", "‚", },
  ["-"]: {"－", "˗", " ", "ᝍ", "᠆", "ᱼ", "−", "⎯", "⎼", "╴", "ⲻ", "ⲻ", "ー", "ㄧ", "﹣", },
  ["."]: {"．", "․", },
  ["/"]: {"／", "᜵", "⁄", "∕", "⧸", "Ⳇ", "〳", },
  ["0"]: {"᱐", },
  ["2"]: {"ᒿ", },
  ["3"]: {"Ʒ", "ᢃ", "ℨ", "Ⳅ", "Ⳍ", "ⳍ", },
  ["4"]: {"Ꮞ", },
  ["6"]: {"Ꮾ", },
  ["9"]: {"Ꮽ", },
  [":"]: {"：", "ː", "˸", "։", "፡", "᛬", "᠄", "ᱺ", "⁚", "∶", "⠆", "︓", "︰", "﹕", },
  [";"]: {"；", ";", "︔", "﹔", },
  ["<"]: {"＜", "˂", "‹", "≺", "❮", "ⵦ", "〱", "ㄑ", "﹤", },
  ["="]: {"＝", "═", "⚌", "゠", "﹦", },
  [">"]: {"＞", "˃", "›", "≻", "❯", "﹥", },
  ["?"]: {"？", "︖", "﹖", },
  ["@"]: {"＠", "﹫", },
  ["A"]: {"Α", "А", "Ꭺ", },
  ["B"]: {"Β", "В", "Ᏼ", "ᗷ", "Ⲃ", },
  ["C"]: {"Ϲ", "С", "Ꮯ", "Ⅽ", "Ⲥ", },
  ["D"]: {"Ꭰ", "ᗪ", "Ⅾ", },
  ["E"]: {"Ε", "Е", "Ꭼ", },
  ["F"]: {"ᖴ", },
  ["G"]: {"Ԍ", "Ꮐ", },
  ["H"]: {"Η", "Н", "ዘ", "Ꮋ", "ᕼ", "Ⲏ", },
  ["I"]: {"Ι", "І", "Ⅰ", },
  ["J"]: {"Ј", "Ꭻ", "ᒍ", },
  ["K"]: {"Κ", "Κ", "Ꮶ", "ᛕ", "K", "Ⲕ", },
  ["L"]: {"Ꮮ", "ᒪ", "Ⅼ", "Ⳑ", },
  ["M"]: {"Μ", "Ϻ", "М", "Ꮇ", "Ⅿ", },
  ["N"]: {"Ν", "Ⲛ", },
  ["O"]: {"Ο", "О", "ᱛ", "Ⲟ", },
  ["P"]: {"Ρ", "Р", "Ꮲ", "Ⲣ", },
  ["Q"]: {"Ԛ", "Ⴓ", "ⵕ", },
  ["R"]: {"Ꭱ", "Ꮢ", "ᖇ", },
  ["S"]: {"Ѕ", "Ⴝ", "Ꮪ", },
  ["T"]: {"Τ", "Т", "Ꭲ", },
  ["V"]: {"Ꮩ", "Ⅴ", },
  ["W"]: {"Ꮃ", "Ꮤ", },
  ["X"]: {"Χ", "Х", "Ⅹ", "Ⲭ", },
  ["Y"]: {"Υ", "Ⲩ", },
  ["Z"]: {"Ζ", "Ꮓ", },
  ["["]: {"［", },
  ["\\"]: {"＼", "∖", "⧵", "⧹", "〵", "﹨", },
  ["]"]: {"］", },
  ["^"]: {"＾", "˄", "ˆ", "ᶺ", "⌃", },
  ["_"]: {"＿", "ˍ", "⚊", "﹘", },
  ["`"]: {"｀", "ˋ", "`", "‵", },
  ["a"]: {"ɑ", "а", },
  ["c"]: {"ϲ", "с", "ⅽ", },
  ["d"]: {"ԁ", "ⅾ", },
  ["e"]: {"е", "ᥱ", },
  ["g"]: {"ɡ", },
  ["h"]: {"һ", },
  ["i"]: {"і", "ⅰ", },
  ["j"]: {"ϳ", "ј", },
  ["l"]: {"ⅼ", },
  ["m"]: {"ⅿ", },
  ["n"]: {"ᥒ", },
  ["o"]: {"ο", "о", "೦", "ഠ", "൦", "ᦞ", "᧐", "ⲟ", },
  ["p"]: {"р", "ⲣ", },
  ["s"]: {"ѕ", },
  ["u"]: {"ᥙ", "∪", },
  ["v"]: {"ᴠ", "ⅴ", "∨", "⋁", },
  ["w"]: {"ᴡ", },
  ["x"]: {"х", "ⅹ", "ⲭ", },
  ["y"]: {"у", "ỿ", },
  ["z"]: {"ᤁ", "ᴢ", },
  ["{"]: {"｛", "﹛", },
  ["|"]: {"｜", "ǀ", "ᛁ", "⎜", "⎟", "⎢", "⎥", "⎪", "⎮", "⼁", "〡", "丨", "︱", "︳", "￨", },
  ["}"]: {"｝", "﹜", },
  ["~"]: {"～", "˜", "⁓", "∼", "〜", },
}

-- Construct a table which can be used for lookup replacement later
-- Iterate over normal ascii and find counterparts in the weirdo strings as substrings
maps = {}
for charmap, chars in pairs charmaps
  i = 1
  maps[charmap] = {}
  for uchar in util.utf8.chars(chars)
    maps[charmap][an\sub(i,i)] = uchar
    i = i +1

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

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

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  -- Limit text to prevent DoS
  text = text\sub(1,512)
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(maps.bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(maps.ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 7)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(maps.ud, util.utf8.reverse(arg))
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pthrow (.+)$': (source, destination, arg) =>
    say "（╯°□°）╯︵ #{remap maps.ud, util.utf8.reverse(arg)}"
  '^%pparanthesized (.+)$': (source, destination, arg) =>
    say remap(maps.pt, arg)
  '^%pnegcircle (.+)$': (source, destination, arg) =>
    say remap(maps['nc'], arg)
  '^%psquare (.+)$': (source, destination, arg) =>
    say remap(maps.sq, arg)
  '^%pnegsquare (.+)$': (source, destination, arg) =>
    say remap(maps.ns, arg)
  '^%pdoublestruck (.+)$': (source, destination, arg) =>
    say remap(maps.ds, arg)
  '^%pubold (.+)$': (source, destination, arg) =>
    say remap(maps.bo, arg)
  '^%pbolditalic (.+)$': (source, destination, arg) =>
    say remap(maps.bi, arg)
  '^%pboldscript (.+)$': (source, destination, arg) =>
    say remap(maps.bs, arg)
  '^%pthai (.+)$': (source, destination, arg) =>
    say remap(maps.tl, arg)
  '^%putfuk (.+)$': (source, destination, arg) =>
    keys = [x for x,_ in pairs(maps)]
    say table.concat([remap(maps[keys[math.random(#keys)]], letter) for letter in codepoints(arg)])
  '^%pmimic (.+)$': (source, destination, arg) =>
      out = {}
      for uchar in util.utf8.chars(arg)
        mimictbl =  mimic[uchar]
        if mimictbl
          out[#out+1] = mimictbl[math.random(#mimictbl)]
        else
          out[#out+1] = uchar
      say table.concat(out)

util = require'util'
html2unicode = require'html'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
charmaps = {
ci: 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl: '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud: [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]
nc: [[🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿]]
sq: [[🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789,⊡]]
ns: [[🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉]]
ds: [[𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡]]
bo: [[𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗]]
bi: [[𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛0123456789]]
bs: [[𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃]]
pt: [[⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵0⑴⑵⑶⑷⑸⑹⑺⑻⑼]]
tl: [[ค๒ς๔єŦﻮђเןкl๓ภ๏קợгรtยשฬץאzค๒ς๔єŦﻮђเןкl๓ภ๏קợгรtยשฬץאz0123456789,.؟!"'`()[]{}«»&_]]
}
codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

-- Construct a table which can be used for lookup replacement later
-- Iterate over normal ascii and find counterparts in the weirdo strings as substrings
maps = {}
for charmap, chars in pairs charmaps
  i = 1
  maps[charmap] = {}
  for uchar in util.utf8.chars(chars)
    maps[charmap][an\sub(i,i)] = uchar
    i = i +1

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

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

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  -- Limit text to prevent DoS
  text = text\sub(1,512)
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(maps.bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(maps.ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 7)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(maps.ud, util.utf8.reverse(arg))
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pthrow (.+)$': (source, destination, arg) =>
    say "（╯°□°）╯︵ #{remap maps.ud, util.utf8.reverse(arg)}"
  '^%pparanthesized (.+)$': (source, destination, arg) =>
    say remap(maps.pt, arg)
  '^%pnegcircle (.+)$': (source, destination, arg) =>
    say remap(maps['nc'], arg)
  '^%psquare (.+)$': (source, destination, arg) =>
    say remap(maps.sq, arg)
  '^%pnegsquare (.+)$': (source, destination, arg) =>
    say remap(maps.ns, arg)
  '^%pdoublestruck (.+)$': (source, destination, arg) =>
    say remap(maps.ds, arg)
  '^%pubold (.+)$': (source, destination, arg) =>
    say remap(maps.bo, arg)
  '^%pbolditalic (.+)$': (source, destination, arg) =>
    say remap(maps.bi, arg)
  '^%pboldscript (.+)$': (source, destination, arg) =>
    say remap(maps.bs, arg)
  '^%pthai (.+)$': (source, destination, arg) =>
    say remap(maps.tl, arg)
  '^%putfuk (.+)$': (source, destination, arg) =>
    keys = [x for x,_ in pairs(maps)]
    say table.concat([remap(maps[keys[math.random(#keys)]], letter) for letter in codepoints(arg)])


utf8: fix syntax

utf8: fix syntax

html2unicode = require'html'

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

    table.concat(t, "")

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

html2unicode = require'html'

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

    table.concat(t, "")

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


improved profile

improved profile

html = require "lapis.html"

class ProfileEdit extends html.Widget
  mkCheck:(name,txt,val)=>
    label for:"c_"..name, class:"pure-checkbox", ->
      input type:"checkbox", id:"c_"..name, :name, checked:(val and "checked" or nil)
      text txt
  content: =>
    form action:@url_for("profile_edit"), class: "pure-form pure-form-stacked", method:"post", ->
      with @profiledata
        fieldset ->
          legend "Birthday"
          input id:"birthday", name:"birthday", type:"date", value: .birthday or ""
          fieldset ->
            legend "Show publicly:"
            div class: "pure-control-group", ->
              @mkCheck "privacy_birthday_dm", "Date & Month", .privacy_birthday_dm
              @mkCheck "privacy_birthday_y", "Year", .privacy_birthday_y
              @mkCheck "privacy_birthday_age", "Age in years", .privacy_birthday_age
        fieldset ->
          legend "Gender"
          input id:"gender", name:"gender", type:"text", list:"genderlist", value: .gender or ""
          datalist id:"genderlist", ->
            option value: "Agender"
            option value: "Female"
            option value: "Male"
            option value: "Gender Fluid"
            option value: "Intersex"
          text "Select or type custom, any capitalized letters will be used for abbreviation."
          fieldset ->
            legend "Show:"
            div class: "pure-control-group", ->
              @mkCheck "privacy_gender", "Publicly", .privacy_gender
        fieldset ->
          legend "About me"
          textarea id:"about", name:"about", ->
            raw .about
          text "This field has "
          a target:"_blank", href:"https://daringfireball.net/projects/markdown/syntax", "markdown support"
          text " but you may not use HTML for security reasons."
          br!
          text "If the first line is a "
          code "# 1st level heading"
          text ", it replaces the \"About me\" heading."
      div class: "pure-controls", ->
        input type:"submit", class: "pure-button pure-button-primary"

html = require "lapis.html"

class ProfileEdit extends html.Widget
  mkCheck:(name,txt,val)=>
    label for:"c_"..name, class:"pure-checkbox", ->
      input type:"checkbox", id:"c_"..name, :name, checked:(val and "checked" or nil)
      text txt
  content: =>
    form action:@url_for("profile_edit"), class: "pure-form pure-form-stacked", method:"post", ->
      with @profiledata
        fieldset ->
          legend "Birthday"
          input id:"birthday", name:"birthday", type:"date", value: .birthday or ""
          fieldset ->
            legend "Show publicly:"
            div class: "pure-control-group", ->
              @mkCheck "privacy_birthday_dm", "Date & Month", .privacy_birthday_dm
              @mkCheck "privacy_birthday_y", "Year", .privacy_birthday_y
              @mkCheck "privacy_birthday_age", "Age in years", .privacy_birthday_age
        fieldset ->
          legend "Gender"
          input id:"gender", name:"gender", type:"text", list:"genderlist", value: .gender or ""
          datalist id:"genderlist", ->
            option value: "Agender"
            option value: "Female"
            option value: "Male"
            option value: "Gender Fluid"
            option value: "Intersex"
          text "Select or type custom, any capitalized letters will be used for abbreviation."
          fieldset ->
            legend "Show:"
            div class: "pure-control-group", ->
              @mkCheck "privacy_gender", "Publicly", .privacy_gender
        fieldset ->
          legend "About me"
          textarea id:"about", name:"about", ->
            raw .about
          text "This field has "
          a target:"_blank", href:"https://daringfireball.net/projects/markdown/syntax", "markdown support"
          text " but you may not use HTML for security reasons."
      div class: "pure-controls", ->
        input type:"submit", class: "pure-button pure-button-primary"


added more room types to UI

added more room types to UI

export ^

require "rooms/rock"
require "rooms/corridor"
require "rooms/reactor"
require "rooms/extractor"
require "rooms/gardens"
require "rooms/kitchen"
require "ui/button"
shapes = require "rooms/shape"

class UI
    -- todo? separate roomPlacer and crewScheduler?
    new: (@spacebase) =>
        @roomTypes = {Corridor, Reactor, Extractor, Gardens, Kitchen}
        @nextShapes = {}
        @hRatio = 0.1 -- space taken on screen
        -- fill with random shapes
        for i=1,4
            table.insert(@nextShapes, shapes.random())

        @roomButtons = {}
        @selectedRoomType = nil
        @currentLeftTurns = 0
        offset = 1/6 * wScr()
        width = 70
        margin_w = 10
        margin_h = 0
        for i, room in ipairs(@roomTypes)
            i -= 1 -- starts at 0
            x = offset + i * width + i * margin_w
            y = margin_h
            w = width
            h = @hRatio * hScr()
            butt = Button(x, y, w, h, room)
            table.insert(@roomButtons, butt)

    draw: =>
        w, h = wScr(), hScr()
        love.graphics.setColor(0,0,0, 100)
        love.graphics.rectangle("fill", 0, 0, w, @hRatio * h)
        love.graphics.rectangle("fill", 0, (1 - @hRatio) * h, w, @hRatio * h)

        -- buttons
        for i, butt in ipairs(@roomButtons)
            butt\draw(i == @selectedRoomType)

    isInside: (x, y) =>
        return y < @hRatio * hScr() or y > (1 - @hRatio) * hScr()

    nextShape: =>
        return @nextShapes[#@nextShapes]

    updateShapes: =>
        table.remove(@nextShapes, 1)
        table.insert(@nextShapes, shapes.random())

    selectedRoom: =>
        return @roomTypes[@selectedRoomType](@nextShape(), {0, 0}, @currentLeftTurns)

    selectNewRoomType: (n) =>
        @selectedRoomType = n
        @spacebase.floatingRoom = @selectedRoom()

    unselectRoomType: =>
        @selectedRoomType = nil
        @spacebase.floatingRoom = nil

    turnSelectedRoom: =>
        @currentLeftTurns += 1
        @currentLeftTurns %= 4
        @spacebase.floatingRoom = @selectedRoom()

    placeRoom: =>
        @spacebase\placeFloatingRoom()
        @selectedRoomType = nil
        @updateShapes()

    mousepressed: (x, y, button) =>
        switch button
            when "l"
                if @selectedRoomType ~= nil
                    @placeRoom()
                for i, butt in ipairs(@roomButtons)
                    if butt\isInside(x, y)
                        @selectNewRoomType(i)
            when "r"
                @turnSelectedRoom()

export ^

require "rooms/rock"
require "rooms/corridor"
require "rooms/reactor"
require "ui/button"
shapes = require "rooms/shape"

class UI
    -- todo? separate roomPlacer and crewScheduler?
    new: (@spacebase) =>
        @roomTypes = {Rock, Corridor, Reactor}
        @nextShapes = {}
        @hRatio = 0.1 -- space taken on screen
        -- fill with random shapes
        for i=1,4
            table.insert(@nextShapes, shapes.random())

        @roomButtons = {}
        @selectedRoomType = nil
        @currentLeftTurns = 0
        offset = 1/6 * wScr()
        width = 70
        margin_w = 10
        margin_h = 0
        for i, room in ipairs(@roomTypes)
            i -= 1 -- starts at 0
            x = offset + i * width + i * margin_w
            y = margin_h
            w = width
            h = @hRatio * hScr()
            butt = Button(x, y, w, h, room)
            table.insert(@roomButtons, butt)

    draw: =>
        w, h = wScr(), hScr()
        love.graphics.setColor(0,0,0, 100)
        love.graphics.rectangle("fill", 0, 0, w, @hRatio * h)
        love.graphics.rectangle("fill", 0, (1 - @hRatio) * h, w, @hRatio * h)

        -- buttons
        for i, butt in ipairs(@roomButtons)
            butt\draw(i == @selectedRoomType)

    isInside: (x, y) =>
        return y < @hRatio * hScr() or y > (1 - @hRatio) * hScr()

    nextShape: =>
        return @nextShapes[#@nextShapes]

    updateShapes: =>
        table.remove(@nextShapes, 1)
        table.insert(@nextShapes, shapes.random())

    selectedRoom: =>
        return @roomTypes[@selectedRoomType](@nextShape(), {0, 0}, @currentLeftTurns)

    selectNewRoomType: (n) =>
        @selectedRoomType = n
        @spacebase.floatingRoom = @selectedRoom()

    unselectRoomType: =>
        @selectedRoomType = nil
        @spacebase.floatingRoom = nil

    turnSelectedRoom: =>
        @currentLeftTurns += 1
        @currentLeftTurns %= 4
        @spacebase.floatingRoom = @selectedRoom()

    placeRoom: =>
        @spacebase\placeFloatingRoom()
        @selectedRoomType = nil
        @updateShapes()

    mousepressed: (x, y, button) =>
        switch button
            when "l"
                if @selectedRoomType ~= nil
                    @placeRoom()
                for i, butt in ipairs(@roomButtons)
                    if butt\isInside(x, y)
                        @selectNewRoomType(i)
            when "r"
                @turnSelectedRoom()


fixed yubikey

fixed yubikey

import Model from require "lapis.db.model"
import Users from require "lazuli.modules.user_management.models.users"

package.loaded['models'] or={}
package.loaded['models'].Users=Users

class YubiCloud extends Model
  @table_name: => "lazuli_modules_user_management_yubicloud"
  @relations: {
    {"user", belongs_to: "Users"}
  }

import Model from require "lapis.db.model"
import Users from require "lazuli.modules.user_management.models.users"

package.preload['models'] or={}
package.preload['models'].Users=Users

class YubiCloud extends Model
  @table_name: => "lazuli_modules_user_management_yubicloud"
  @relations: {
    {"user", belongs_to: "Users"}
  }


Add MySQL config to prod environment

Add MySQL config to prod environment

config = require 'lapis.config'
secrets = require 'secrets'

config {'development', 'development-perftest'}, ->
    mysql ->
        host '127.0.0.1'
        user 'csweek'
        password 'csweek'
        database 'csweek'

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'MG Nedelja informatike - prijave <prijave@csnedelja.mg.edu.rs>'

    port 8080
    num_workers 1
    code_cache 'off'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'debug'
    listen_address '127.0.0.1'
    secret 'this is not so secret!'
    content_prefix '../content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    email_cooldown 20
    uploads_dir 'uploads'

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password

    port 8989
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'warn'
    listen_address '127.0.0.1'
    content_prefix 'content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    email_cooldown 30 * 60
    uploads_dir 'uploads'

config {'development-perftest', 'production-perftest'}, ->
    code_cache 'on'
    measure_performance -> true

config = require 'lapis.config'
secrets = require 'secrets'

config {'development', 'development-perftest'}, ->
    mysql ->
        host '127.0.0.1'
        user 'csweek'
        password 'csweek'
        database 'csweek'

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'MG Nedelja informatike - prijave <prijave@csnedelja.mg.edu.rs>'

    port 8080
    num_workers 1
    code_cache 'off'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'debug'
    listen_address '127.0.0.1'
    secret 'this is not so secret!'
    content_prefix '../content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    email_cooldown 20
    uploads_dir 'uploads'

config {'production', 'production-perftest' }, ->
    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password

    port 8989
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'warn'
    listen_address '127.0.0.1'
    content_prefix 'content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    email_cooldown 30 * 60
    uploads_dir 'uploads'

config {'development-perftest', 'production-perftest'}, ->
    code_cache 'on'
    measure_performance -> true


add opts spec

add opts spec

describe "moor.opt module", ->
	local opts, moor, bkenv, env

	setup ->
		opts = require'moor.opts'
		moor = require'moor.repl'

		deepcpy = (t, list = {}) -> with ret = {}
			for k, v in pairs t
				if type(v) == "table"
					kk = tostring v

					unless  list[kk]
						list[kk] = v
						ret[k] = deepcpy v, list
					else ret[k] = list[kk]
				else ret[k] = v

		if _ENV then bkenv =  deepcpy _ENV
		else _G["_ENV"] = deepcpy _G

		bkenv = deepcpy _ENV
		env = {}

	it "execute and exit test", ->
		assert.falsy opts {}, {"-e", "nil"}

	it "load library to global test", ->
		assert.is_true opts env, {"-luakatsu", "-E", [[kii = Aikatsu\find_birthday "12/03"]]}
		assert.is_not_nil env.kii
		assert.is_not_nil Aikatsu  --- yes, it's correct behavior

	it "load library as LIB test", ->
		expected_txt = "{\n  x = 3\n}"
		assert.is_true opts env, {"-Linspect", "-E", [[foo = inspect {x: 3}]]}
		assert.is_same env.foo, expected_txt

	it "help and exit test", ->
		opts env, {'-h'}
		assert.is_same 1, env.MOOR_EXITCODE


Changed priority

Changed priority

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

WATCH_WEAPONS = {}

timer.Create 'PPM2.ModelChecks', 1, 0, ->
    for ply in *player.GetAll()
        ply.__cachedIsPony = ply\IsPony()
        if ply.__ppm2_weapon_hit and not ply.__cachedIsPony
            wep\SetNoDraw(false) for wep in *ply\GetWeapons()
            ply.__ppm2_weapon_hit = false

    for i, wep in pairs WATCH_WEAPONS
        if not IsValid(wep)
            WATCH_WEAPONS[wep] = nil
            continue
        if not IsValid(wep\GetOwner())
            WATCH_WEAPONS[wep] = nil
            wep\SetNoDraw(false)

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

    for ply in *player.GetAll()
        if ply.__cachedIsPony
            wep = ply\GetActiveWeapon()
            if IsValid(wep)
                ply.__ppm2_weapon_hit = true
                wep\SetNoDraw(ply ~= lply or lply\ShouldDrawLocalPlayer())
                WATCH_WEAPONS[wep\EntIndex()] = wep

            if ply\GetPonyData() and not ply\Alive()
                data = ply\GetPonyData()
                rag = ply\GetRagdollEntity()
                if IsValid(rag)
                    rag\SetNoDraw(true)
                    renderController = data\GetRenderController()
                    if renderController
                        renderController\PreDraw()
                        rag\DrawModel()
                        renderController\PostDraw()

PPM2.PrePlayerDraw = =>
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    status = renderController\PreDraw() if renderController
    return status if status ~= nil
PPM2.PostPlayerDraw = =>
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    renderController\PostDraw() if renderController

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

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

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

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

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

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

lastDataSend = 0
net.Receive 'PPM2.RequestPonyData', ->
    lastDataSend = 0
    RunConsoleCommand('ppm2_reload')

net.Receive 'PPM2.PlayerRespawn', UpdateWeight

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

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

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

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

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

WATCH_WEAPONS = {}

timer.Create 'PPM2.ModelChecks', 1, 0, ->
    for ply in *player.GetAll()
        ply.__cachedIsPony = ply\IsPony()
        if ply.__ppm2_weapon_hit and not ply.__cachedIsPony
            wep\SetNoDraw(false) for wep in *ply\GetWeapons()
            ply.__ppm2_weapon_hit = false

    for i, wep in pairs WATCH_WEAPONS
        if not IsValid(wep)
            WATCH_WEAPONS[wep] = nil
            continue
        if not IsValid(wep\GetOwner())
            WATCH_WEAPONS[wep] = nil
            wep\SetNoDraw(false)

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

    for ply in *player.GetAll()
        if ply.__cachedIsPony
            wep = ply\GetActiveWeapon()
            if IsValid(wep)
                ply.__ppm2_weapon_hit = true
                wep\SetNoDraw(ply ~= lply or lply\ShouldDrawLocalPlayer())
                WATCH_WEAPONS[wep\EntIndex()] = wep

            if ply\GetPonyData() and not ply\Alive()
                data = ply\GetPonyData()
                rag = ply\GetRagdollEntity()
                if IsValid(rag)
                    rag\SetNoDraw(true)
                    renderController = data\GetRenderController()
                    if renderController
                        renderController\PreDraw()
                        rag\DrawModel()
                        renderController\PostDraw()

PPM2.PrePlayerDraw = =>
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    status = renderController\PreDraw() if renderController
    return status if status ~= nil
PPM2.PostPlayerDraw = =>
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    renderController\PostDraw() if renderController

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

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

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

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

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

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

lastDataSend = 0
net.Receive 'PPM2.RequestPonyData', ->
    lastDataSend = 0
    RunConsoleCommand('ppm2_reload')

net.Receive 'PPM2.PlayerRespawn', UpdateWeight

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

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

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

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


Stub Screen

Stub Screen


utf8: Fix trailing whitespace.

utf8: Fix trailing whitespace.

Former-commit-id: ce141193f6f587049e404cd2a78a4ae1ab3744f2 [formerly a07ec0e12d28feaa0e6caf7b86f674e12dd7d2ab]
Former-commit-id: 15a3c674e3692bf990df99b5e2b88328f48a83ca

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
ci = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl = '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud = [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]

an2ci = {}
-- Circled letters are 3 bytes, just use sub string
for i=1, #an
  f = i*3+1-3
  t = i*3
  an2ci[an\sub(i,i)] = ci\sub(f, t)

an2bl = {}
i=1
-- Since blackletters have varying byte length, use the common lua pattern to find multibyte chars
for uchar in string.gfind(bl, "([%z\1-\127\194-\244][\128-\191]*)")
  an2bl[an\sub(i,i)] = uchar
  i = i +1

an2ud = {}
i=1
for uchar in string.gfind(ud, "([%z\1-\127\194-\244][\128-\191]*)")
  an2ud[an\sub(i,i)] = uchar
  i = i +1

codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

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

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(an2bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(an2ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 10)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(an2ud, arg)
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(an2ud, arg)


clue: add silly "bankid" style word generator

Former-commit-id: f47d2a023ef6765127e510bfbdae46fe559fd886 [formerly 733e2973ce976e5e3b0d32454f89856d35fb3f11]
Former-commit-id: 82bc7574a2061b95e24c6d77ba3acb20f78dcd73

-- Clue module requires clue data files and tosql.py from emnh/clue2

dbi = require 'DBI'
require'logging.console'
log = logging.console()
conn = false

attrs = {'grammar','reference', 'country', 'context', 'text'}

connect = ->
  conn, err = DBI.Connect('PostgreSQL', ivar2.config.dbname, ivar2.config.dbuser, ivar2.config.dbpass, ivar2.config.dbhost, ivar2.config.dbport)
  unless conn
    log\error "Unable to connect to DB: #{err}"
    return

  conn\autocommit(true)

dbh = ->
  connect! unless conn

  -- Check if connection is alive
  alive = conn\ping!
  connect! unless alive

  success, err = DBI.Do(conn, 'SELECT NOW()')
  unless success
    log\error "SQL Connection :#{err}"
    connect!

  return conn

lookup = (source, destination, lookup) =>
  stmt = dbh!\prepare [[
    SELECT
      tablename
    from
      pg_catalog.pg_tables
    where
      tablename like 'cl%'
    ]]
  stmt\execute!
  tables = {}
  for row in stmt\rows(true) -- true for column names
    tables[#tables+1] = row.tablename

  out = {}
  for dict in *tables
    out[dict] = {}
    stmt = dbh!\prepare [[
      SELECT
        *
      FROM
        ]]..dict..[[
      WHERE
        word = ?
      ]]
    stmt\execute lookup
    for row in stmt\rows(true) -- true for column names
      table.insert(out[dict], row)

  i = 0
  for dict, rows in pairs out
    res = {}
    dfrom = dict\sub(3, 4)
    to = dict\sub(5, 6)
    first = true
    for row in *rows
      i += 1
      if first
        table.insert(res, "[#{ivar2.util.bold row.word}]")
        table.insert(res, "(#{ivar2.util.italic dfrom}")
        table.insert(res, "-> #{ivar2.util.italic to})")
        first = false
      for attr in *attrs
        if row[attr] != nil and row[attr] != ''
          if attr == 'grammar'
            table.insert(res, "(#{row[attr]}. )")
          else
            table.insert(res, row[attr])
    say table.concat(res, ' ')
  if i == 0
    reply 'Nope.'

upper = (s) ->
  (string.upper(s)\gsub('æ','Æ')\gsub('ø','Ø')\gsub('å','Å'))

getWord = (wordclass) ->
  stmt = dbh!\prepare [[
    SELECT
     *
    FROM
      clnono
    WHERE
      grammar = ?
    AND
      length(word) < 8
    ORDER BY
      random()
    LIMIT 1
  ]]
  stmt\execute wordclass
  out = {}
  for row in stmt\rows(true) -- true for column names
    table.insert(out, row)
  return out

bankid = (source, destination) =>

  adj = getWord 'adj'
  sub = getWord 's'

  say "#{upper adj[1].word} #{upper sub[1].word}"

PRIVMSG:
  '^%pclue (.+)$': lookup
  '^%pbankid$': bankid

-- Clue module requires clue data files and tosql.py from emnh/clue2

dbi = require 'DBI'
require'logging.console'
log = logging.console()
conn = false

attrs = {'grammar','reference', 'country', 'context', 'text'}

connect = ->
  conn, err = DBI.Connect('PostgreSQL', ivar2.config.dbname, ivar2.config.dbuser, ivar2.config.dbpass, ivar2.config.dbhost, ivar2.config.dbport)
  unless conn
    log\error "Unable to connect to DB: #{err}"
    return

  conn\autocommit(true)

dbh = ->
  connect! unless conn

  -- Check if connection is alive
  alive = conn\ping!
  connect! unless alive

  success, err = DBI.Do(conn, 'SELECT NOW()')
  unless success
    log\error "SQL Connection :#{err}"
    connect!

  return conn

lookup = (source, destination, lookup) =>
  stmt = dbh!\prepare [[
    SELECT
      tablename
    from
      pg_catalog.pg_tables
    where
      tablename like 'cl%'
    ]]
  stmt\execute!
  tables = {}
  for row in stmt\rows(true) -- true for column names
    tables[#tables+1] = row.tablename

  out = {}
  for dict in *tables
    out[dict] = {}
    stmt = dbh!\prepare [[
      SELECT
        *
      FROM
        ]]..dict..[[
      WHERE
        word = ?
      ]]
    stmt\execute lookup
    for row in stmt\rows(true) -- true for column names
      table.insert(out[dict], row)

  i = 0
  for dict, rows in pairs out
    res = {}
    dfrom = dict\sub(3, 4)
    to = dict\sub(5, 6)
    first = true
    for row in *rows
      i += 1
      if first
        table.insert(res, "[#{ivar2.util.bold row.word}]")
        table.insert(res, "(#{ivar2.util.italic dfrom}")
        table.insert(res, "-> #{ivar2.util.italic to})")
        first = false
      for attr in *attrs
        if row[attr] != nil and row[attr] != ''
          if attr == 'grammar'
            table.insert(res, "(#{row[attr]}. )")
          else
            table.insert(res, row[attr])
    say table.concat(res, ' ')
  if i == 0
    reply 'Nope.'

PRIVMSG:
  '^%pclue (.+)$': lookup


utf8: gfind -> gmatch

utf8: gfind -> gmatch

Former-commit-id: cb05b472c7934a500a0686932be2943dc334aac2 [formerly 7fa3ba4c3d390ef2b6c977b56d39a8d53fa8695b]
Former-commit-id: 2f44896d055a1e135c1efb1ddb1d3f9dd35236cb

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
charmaps = {
ci: 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl: '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud: [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]
nc: [[🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿]]
sq: [[🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789,⊡]]
ns: [[🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉]]
ds: [[𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡]]
bo: [[𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗]]
bi: [[𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛0123456789]]
bs: [[𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃]]
pt: [[⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵0⑴⑵⑶⑷⑸⑹⑺⑻⑼]]
}
codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

-- Construct a table which can be used for lookup replacement later
-- Iterate over normal ascii and find counterparts in the weirdo strings as substrings
maps = {}
for charmap, chars in pairs charmaps
  i = 1
  maps[charmap] = {}
  for uchar in string.gmatch(chars, "([%z\1-\127\194-\244][\128-\191]*)")
    maps[charmap][an\sub(i,i)] = uchar
    i = i +1

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

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

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(maps.bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(maps.ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 7)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pparanthesized (.+)$': (source, destination, arg) =>
    say remap(maps.pt, arg)
  '^%pnegcircle (.+)$': (source, destination, arg) =>
    say remap(maps['nc'], arg)
  '^%psquare (.+)$': (source, destination, arg) =>
    say remap(maps.sq, arg)
  '^%pnegsquare (.+)$': (source, destination, arg) =>
    say remap(maps.ns, arg)
  '^%pdoublestruck (.+)$': (source, destination, arg) =>
    say remap(maps.ds, arg)
  '^%pubold (.+)$': (source, destination, arg) =>
    say remap(maps.bo, arg)
  '^%pbolditalic (.+)$': (source, destination, arg) =>
    say remap(maps.bi, arg)
  '^%pboldscript (.+)$': (source, destination, arg) =>
    say remap(maps.bs, arg)
  '^%putfuk (.+)$': (source, destination, arg) =>
    keys = [x for x,_ in pairs(maps)]
    say table.concat([remap(maps[keys[math.random(#keys)]], letter) for letter in codepoints(arg)])

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
charmaps = {
ci: 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl: '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud: [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]
nc: [[🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿]]
sq: [[🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789,⊡]]
ns: [[🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉]]
ds: [[𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡]]
bo: [[𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗]]
bi: [[𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛0123456789]]
bs: [[𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃]]
pt: [[⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵0⑴⑵⑶⑷⑸⑹⑺⑻⑼]]
}
codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

-- Construct a table which can be used for lookup replacement later
-- Iterate over normal ascii and find counterparts in the weirdo strings as substrings
maps = {}
for charmap, chars in pairs charmaps
  i = 1
  maps[charmap] = {}
  for uchar in string.gfind(chars, "([%z\1-\127\194-\244][\128-\191]*)")
    maps[charmap][an\sub(i,i)] = uchar
    i = i +1

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

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

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(maps.bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(maps.ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 7)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pparanthesized (.+)$': (source, destination, arg) =>
    say remap(maps.pt, arg)
  '^%pnegcircle (.+)$': (source, destination, arg) =>
    say remap(maps['nc'], arg)
  '^%psquare (.+)$': (source, destination, arg) =>
    say remap(maps.sq, arg)
  '^%pnegsquare (.+)$': (source, destination, arg) =>
    say remap(maps.ns, arg)
  '^%pdoublestruck (.+)$': (source, destination, arg) =>
    say remap(maps.ds, arg)
  '^%pubold (.+)$': (source, destination, arg) =>
    say remap(maps.bo, arg)
  '^%pbolditalic (.+)$': (source, destination, arg) =>
    say remap(maps.bi, arg)
  '^%pboldscript (.+)$': (source, destination, arg) =>
    say remap(maps.bs, arg)
  '^%putfuk (.+)$': (source, destination, arg) =>
    keys = [x for x,_ in pairs(maps)]
    say table.concat([remap(maps[keys[math.random(#keys)]], letter) for letter in codepoints(arg)])