Update format function and optimize some

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

NAME, T = ...

{ :GetRealmName, :LARGE_NUMBER_SEPERATOR } = _G

T.string =
    split: (str, delim using nil) ->
        return nil unless str
        t = {}
        if delim then
            return {str} unless str\find delim
            pattern = '(.-)' .. delim .. '()'
            local lastpos
            for part, pos in str\gmatch pattern
                t[#t + 1] = part
                lastpos = pos
            t[#t + 1] = str\sub lastpos
        else
            for token in str\gmatch '[^%s]+'
                t[#t + 1] = token
        t

T.number =
    -- Format function courtesy of SDPhantom from WoWInterface
    -- http://www.wowinterface.com/forums/showpost.php?p=270917&postcount=11
    format: (number using nil) ->
        number = tostring number
        number\gsub '^(%d)(%d+)', (pre, post) ->
            pre .. post\reverse!\gsub('(%d%d%d)', '%1' .. LARGE_NUMBER_SEPERATOR)\reverse!

local equal

T.table =
    equal: (first, second using nil) ->
        first_type = type first
        second_type = type second
        return false if first_type != 'table' or second_type != 'table'
        for k, v in pairs first
            if type(k) == 'table'
                error 'Comparison with tables as keys not yet implemented'
            if type(v) == 'table'
                return false unless equal v, second[k]
            else
                return false if v != second[k]
        true

import equal from T.table

T.misc =
    fix_name: (name, realm using nil) ->
        dashindex = name\find '-'
        return name if dashindex and dashindex != name\len!
        return name .. '-' .. realm\gsub '%s', '' if realm
        name = name\sub 1, dashindex - 1 if dashindex
        name .. '-' .. GetRealmName!\gsub '%s', ''

    decorate: (text, color using nil) ->
        '\127cff%s%s\127r'\format color, text

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

NAME, T = ...

{ :GetRealmName } = _G

T.string =
    split: (str, delim) ->
        return nil unless str
        t = {}
        if delim then
            return {str} unless str\find delim
            pattern = '(.-)' .. delim .. '()'
            local lastpos
            for part, pos in str\gmatch pattern
                t[#t + 1] = part
                lastpos = pos
            t[#t + 1] = str\sub lastpos
        else
            for token in str\gmatch '[^%s]+'
                t[#t + 1] = token
        t

T.number =
    format: (number) ->
        number = tostring number
        return number if number\match '[^%d%.]' -- Only format normal numbers
        left, right = number\match '^(%d+)(.*)' -- We do not want decimals
        len = left\len!
        return number if len < 4
        mod = len % 3
        left = left\sub(1, mod)\gsub('%d$', '%1,') .. left\sub(mod + 1)\gsub '%d%d%d', '%1,'
        (left\sub 1, left\len! - 1) .. right

local equal

T.table =
    equal: (first, second) ->
        first_type = type first
        second_type = type second
        return false if first_type != 'table' or second_type != 'table'
        for k, v in pairs first
            if type(k) == 'table'
                error 'Comparison with tables as keys not yet implemented'
            if type(v) == 'table'
                return false unless equal v, second[k]
            else
                return false if v != second[k]
        true

import equal from T.table

T.misc =
    fix_name: (name, realm) ->
        dashindex = name\find '-'
        return name if dashindex and dashindex != name\len!
        return name .. '-' .. realm\gsub '%s', '' if realm
        name = name\sub 1, dashindex - 1 if dashindex
        name .. '-' .. GetRealmName!\gsub '%s', ''

    decorate: (text, color) ->
        '\127cff%s%s\127r'\format color, text


Dont create ragdoll if killed with :KillSilent()

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

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

		if @IsPony()
			return if @GetPonyData()
			timer.Simple 0.5, ->
				net.Start('PPM2.RequestPonyData')
				net.Send(@)

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

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

do
	REQUIRE_CLIENTS = {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

hook.Add 'PlayerSpawn', 'PPM2.Bots', PlayerSpawnBot
timer.Simple 0, -> PlayerSpawnBot(ply) for ply in *player.GetAll()

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

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

		if @IsPony()
			return if @GetPonyData()
			timer.Simple 0.5, ->
				net.Start('PPM2.RequestPonyData')
				net.Send(@)

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

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

do
	REQUIRE_CLIENTS = {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

hook.Add 'PlayerSpawn', 'PPM2.Bots', PlayerSpawnBot
timer.Simple 0, -> PlayerSpawnBot(ply) for ply in *player.GetAll()


textutils: fix stutter, nicks, emote

textutils: fix stutter, nicks, emote

Former-commit-id: 519b1f7165fea818021b7030dc1d13f8e1b47c86
Former-commit-id: 4afdb0deb6fbbf1057204d2b564d1ce9e5e8fce9

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

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