Fixed SPEEDSTER missing 5th detail

Fixed SPEEDSTER missing 5th detail

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

PPM2.USE_HIGHRES_BODY = CreateConVar('ppm2_cl_hires_body', '0', {FCVAR_ARCHIVE}, 'Use high resoluation when rendering pony bodies. AFFECTS ONLY TEXTURE COMPILATION TIME (increases lag spike on pony data load)')
PPM2.USE_HIGHRES_TEXTURES = CreateConVar('ppm2_cl_hires_generic', '0', {FCVAR_ARCHIVE}, 'Create 1024x1024 textures instead of 512x512 on texture compiling')
PPM2.FORCE_PRECACHE = CreateConVar('ppm2_cl_force_precache', '0', {FCVAR_ARCHIVE}, 'Force precache render textures instead of loading them when needed')

_Material = Material

RELOADABLE_MATERIALS = {}
PPM2.RELOADABLE_MATERIALS = RELOADABLE_MATERIALS

concommand.Add 'ppm2_reload_materials', ->
	cTime = SysTime()
	for _, mat in ipairs RELOADABLE_MATERIALS
		if texname = mat\GetString('$basetexture')
			mat\SetTexture('$basetexture', texname)
		if texture = mat\GetTexture('$basetexture')
			texture\Download()
		mat\Recompute()
	PPM2.PonyTextureController.URL_MATERIAL_CACHE = {}
	PPM2.PonyTextureController.SessionID = math.random(1, 1000)
	PPM2.Message('Reloaded textures in ', math.floor((SysTime() - cTime) * 100000) / 100, ' milliseconds.')
	RunConsoleCommand('ppm2_reload')
	RunConsoleCommand('ppm2_require')

_CreateMaterial = CreateMaterial

Material = (path) ->
	return path if not PPM2.FORCE_PRECACHE\GetBool()
	matNew = _Material(path)
	table.insert(RELOADABLE_MATERIALS, matNew)
	return matNew

CreateMaterial = (name, shader, data) ->
	matNew, time = _CreateMaterial(name, shader, data)
	table.insert(RELOADABLE_MATERIALS, matNew)
	return matNew, time

module = {
	BODY_DETAILS: {
		Material('models/ppm2/partrender/body_leggrad1.png')
		Material('models/ppm2/partrender/body_lines1.png')
		Material('models/ppm2/partrender/body_stripes1.png')
		Material('models/ppm2/partrender/body_headstripes1.png')
		Material('models/ppm2/partrender/body_freckles.png')
		Material('models/ppm2/partrender/body_hooves1.png')
		Material('models/ppm2/partrender/body_hooves2.png')
		Material('models/ppm2/partrender/body_headmask1.png')
		Material('models/ppm2/partrender/body_hooves1_crit.png')
		Material('models/ppm2/partrender/body_hooves2_crit.png')
		Material('models/ppm2/partrender/body_spots1.png')
		Material('models/ppm2/partrender/body_robotic.png')
		Material('models/ppm2/partrender/dash-e.png')
		Material('models/ppm2/partrender/eye_scar.png')
		Material('models/ppm2/partrender/eye_wound.png')
		Material('models/ppm2/partrender/body_scar.png')
		Material('models/ppm2/partrender/gear_socks.png')
		Material('models/ppm2/partrender/sharp_hooves.png')
		Material('models/ppm2/partrender/sharp_hooves2.png')
		Material('models/ppm2/partrender/separated_muzzle.png')
		Material('models/ppm2/partrender/eye_scar_left.png')
		Material('models/ppm2/partrender/eye_scar_right.png')
		Material('models/ppm2/partrender/android_albedo.png')
		Material('models/ppm2/partrender/android_paint.png')
		Material('models/ppm2/partrender/android_albedo_strip.png')
		Material('models/ppm2/partrender/android_paint_strip.png')
		Material('models/ppm2/partrender/cow_paint.png')
		Material('models/ppm2/partrender/deer_paint.png')
		Material('models/ppm2/partrender/deer_extended_paint.png')
		Material('models/ppm2/partrender/demonic_paint.png')
		Material('models/ppm2/partrender/ear_inner_paint.png')
		Material('models/ppm2/partrender/zebra_detail_paint.png')
	}

	UPPER_MANE_DETAILS: {
		[4]: {1, Material('models/ppm2/partrender/upmane_5_mask0.png')}
		[5]: {1, Material('models/ppm2/partrender/upmane_6_mask0.png')}
		[7]: {2, Material('models/ppm2/partrender/upmane_8_mask0.png'), Material('models/ppm2/partrender/upmane_8_mask1.png')}
		[8]: {3, Material('models/ppm2/partrender/upmane_9_mask0.png'), Material('models/ppm2/partrender/upmane_9_mask1.png'), Material('models/ppm2/partrender/upmane_9_mask2.png')}
		[9]: {1, Material('models/ppm2/partrender/upmane_10_mask0.png')}
		[10]: {3, Material('models/ppm2/partrender/upmane_11_mask0.png'), Material('models/ppm2/partrender/upmane_11_mask1.png'), Material('models/ppm2/partrender/upmane_11_mask2.png')}
		[11]: {1, Material('models/ppm2/partrender/upmane_12_mask0.png')}
		[12]: {1, Material('models/ppm2/partrender/upmane_13_mask0.png')}
		[13]: {1, Material('models/ppm2/partrender/upmane_14_mask0.png')}
		[14]: {1, Material('models/ppm2/partrender/upmane_15_mask0.png')}
	}

	LOWER_MANE_DETAILS: {
		[4]: {1, Material('models/ppm2/partrender/dnmane_5_mask0.png')}
		[7]: {2, Material('models/ppm2/partrender/dnmane_8_mask0.png'), Material('models/ppm2/partrender/dnmane_8_mask1.png')}
		[8]: {2, Material('models/ppm2/partrender/dnmane_9_mask0.png'), Material('models/ppm2/partrender/dnmane_9_mask1.png')}
		[9]: {3, Material('models/ppm2/partrender/dnmane_10_mask0.png'), Material('models/ppm2/partrender/dnmane_10_mask1.png'), Material('models/ppm2/partrender/dnmane_10_mask2.png')}
		[10]: {2, Material('models/ppm2/partrender/dnmane_11_mask0.png'), Material('models/ppm2/partrender/dnmane_11_mask1.png')}
		[11]: {1, Material('models/ppm2/partrender/dnmane_12_mask0.png')}
	}

	TAIL_DETAILS: {
		[4]: {1, Material('models/ppm2/partrender/tail_5_mask0.png')}
		[7]: {5, Material('models/ppm2/partrender/tail_8_mask0.png'), Material('models/ppm2/partrender/tail_8_mask1.png'), Material('models/ppm2/partrender/tail_8_mask2.png'), Material('models/ppm2/partrender/tail_8_mask3.png'), Material('models/ppm2/partrender/tail_8_mask4.png')}
		[9]: {1, Material('models/ppm2/partrender/tail_10_mask0.png')}
		[10]: {3, Material('models/ppm2/partrender/tail_11_mask0.png'), Material('models/ppm2/partrender/tail_11_mask1.png'), Material('models/ppm2/partrender/tail_11_mask2.png')}
		[11]: {2, Material('models/ppm2/partrender/tail_12_mask0.png'), Material('models/ppm2/partrender/tail_12_mask1.png')}
		[12]: {1, Material('models/ppm2/partrender/tail_13_mask0.png')}
		[13]: {1, Material('models/ppm2/partrender/tail_14_mask0.png')}
	}

	SOCKS_PATCHS: {
		'models/props_pony/ppm/ppm_socks/socks_striped_unlit'
		'models/props_pony/ppm2/ppm_socks/custom/geometric1_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric2_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric3_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric4_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric5_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric6_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric7_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric8_1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/dark1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers10.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers11.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers12.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers13.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers14.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers15.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers16.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers17.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers18.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers19.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers2.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers20.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers3.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers4.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers5.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers6.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers7.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers8.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers9.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/grey1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/grey2.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/grey3.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/hearts1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/hearts2.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/snow1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/wallpaper1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/wallpaper2.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/wallpaper3.png'
	}

	SOCKS_DETAILS_PATCHS: {
		[2]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric1_4.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric1_5.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric1_6.png'
		}

		[3]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric2_3.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric2_4.png'
		}

		[4]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric3_2.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric3_3.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric3_5.png'
		}

		[5]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric4_2.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric4_3.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric4_4.png'
		}

		[6]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric5_4.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric5_5.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric5_6.png'
		}

		[7]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric6_2.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric6_3.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric6_4.png'
		}

		[8]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric7_3.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric7_4.png'
		}

		[9]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric8_2.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric8_3.png'
		}
	}
}

additionTable = (...) ->
	tab = {'$ignorez': 1, '$vertexcolor': 1, '$vertexalpha': 1, '$nolod': 1}
	args = {...}
	for i = 1, #args, 2
		key, val = args[i], args[i + 1]
		tab[key] = val
	return tab

module.SOCKS_MATERIALS = [Material(id) for _, id in ipairs module.SOCKS_PATCHS]
module.SOCKS_DETAILS = {i, [Material(path) for path in *data] for i, data in pairs module.SOCKS_DETAILS_PATCHS}
module.CUTIEMARKS = [Material("models/ppm2/cmarks/#{mark}.png") for _, mark in ipairs PPM2.DefaultCutiemarks]
module.SUITS = [Material("models/ppm2/texclothes/#{mat}.png") for _, mat in ipairs {
	'clothes_royalguard', 'clothes_sbs_full'
	'clothes_sbs_light', 'clothes_wbs_full'
	'clothes_wbs_light', 'spidermane_light'
	'spidermane_full'
}]

module.TATTOOS = [Material("models/ppm2/partrender/tattoo/#{fil\lower()}.png") for _, fil in ipairs PPM2.TATTOOS_REGISTRY when fil ~= 'NONE']
debugwhite = {
	'$basetexture': 'models/debug/debugwhite'
	'$ignorez': 1
	'$vertexcolor': 1
	'$vertexalpha': 1
	'$nolod': 1
}

module.EYE_OVALS = {
	Material('models/ppm2/partrender/eye_oval.png')
	Material('models/ppm2/partrender/eye_oval_aperture.png')
}

module.EYE_REFLECTIONS = {
	Material('models/ppm2/partrender/eye_reflection.png')
	Material('models/ppm2/partrender/eye_reflection_crystal_foal.png')
	Material('models/ppm2/partrender/eye_reflection_crystal_unisex.png')
	Material('models/ppm2/partrender/eye_reflection_foal.png')
	Material('models/ppm2/partrender/eye_reflection_male.png')
}

module.HEAD_CLOTHES = {
	[2]: {
		Material('models/ppm2/clothesrender/hat_aj.png')
	}

	[3]: {
		Material('models/ppm2/clothesrender/hat_braeburn_2.png')
	}

	[4]: {
		Material('models/ppm2/clothesrender/tr_hat_stars_1.png')
		Material('models/ppm2/clothesrender/tr_hat_stars_2.png')
	}

	[5]: {
		Material('models/ppm2/clothesrender/headphones_1.png')
		Material('models/ppm2/clothesrender/headphones_2.png')
		Material('models/ppm2/clothesrender/headphones_note.png')
	}
}

module.HEAD_CLOTHES_INDEX = {
	[2]: 0
	[3]: 1
	[4]: 2
	[5]: 3
}

module.NECK_CLOTHES = {
	[2]: {
		Material('models/ppm2/clothesrender/winter_scarf_1.png')
		Material('models/ppm2/clothesrender/winter_scarf_2.png')
	}

	[3]: {
		{
			Material('models/ppm2/clothesrender/cape_stars_1.png')
			Material('models/ppm2/clothesrender/cape_stars_2.png')
			nil
		}
		{
			Material('models/ppm2/clothesrender/gem.png')
			nil
		}
	}

	[4]: {
		Material('models/ppm2/clothesrender/tie_1.png')
		Material('models/ppm2/clothesrender/tie_2.png')
	}

	[5]: {
		Material('models/ppm2/clothesrender/bowtie_1.png')
		Material('models/ppm2/clothesrender/bowtie_2.png')
	}
}

module.NECK_CLOTHES_INDEX = {
	[2]: 4
	[3]: {5, 6}
	[4]: 7
	[5]: 8
}

module.BODY_CLOTHES = {
	[2]: {
		Material('models/ppm2/clothesrender/vest_pouches.png')
		Material('models/ppm2/clothesrender/vest_string.png')
	}

	[3]: {
		Material('models/ppm2/clothesrender/shirt.png')
	}

	[4]: {
		Material('models/ppm2/clothesrender/hoodie.png')
	}

	[5]: {
		Material('models/ppm2/clothesrender/badge.png')
	}
}

module.BODY_CLOTHES_INDEX = {
	[2]: 9
	[3]: 10
	[4]: 11
	[5]: 12
}

module.EYE_CLOTHES = {
	[2]: {
		{}
		{Material('models/ppm2/clothesrender/lense.png')}
	}
	[4]: {
		{}
		{Material('models/ppm2/clothesrender/shades_lense.png')}
	}
	[6]: {
		{}
		{}
	}
	[8]: {
		{}
		{}
	}
}

module.EYE_CLOTHES[3] = module.EYE_CLOTHES[2]
module.EYE_CLOTHES[5] = module.EYE_CLOTHES[4]
module.EYE_CLOTHES[7] = module.EYE_CLOTHES[6]
module.EYE_CLOTHES[9] = module.EYE_CLOTHES[8]

module.EYE_CLOTHES_INDEX = {
	[2]: {13, 14}
	[4]: {15, 16}
	[6]: {17, 18}
	[8]: {20, 21}
}

module.EYE_CLOTHES_INDEX[3] = module.EYE_CLOTHES_INDEX[2]
module.EYE_CLOTHES_INDEX[5] = module.EYE_CLOTHES_INDEX[4]
module.EYE_CLOTHES_INDEX[7] = module.EYE_CLOTHES_INDEX[6]
module.EYE_CLOTHES_INDEX[9] = module.EYE_CLOTHES_INDEX[8]

do
	_prMaterial = (tab) ->
		for i, sub in pairs(tab)
			if not istable(sub[1])
				tab[i] = {}
				tab[i][1] = [mat for mat in *sub when not isnumber(mat)]

	_prIndex = (tab) ->
		for i, sub in pairs(tab)
			if not istable(sub)
				tab[i] = {sub}

	_prMaterial(module.HEAD_CLOTHES)
	_prIndex(module.HEAD_CLOTHES_INDEX)
	_prMaterial(module.NECK_CLOTHES)
	_prIndex(module.NECK_CLOTHES_INDEX)
	_prMaterial(module.BODY_CLOTHES)
	_prIndex(module.BODY_CLOTHES_INDEX)
	_prMaterial(module.EYE_CLOTHES)
	_prIndex(module.EYE_CLOTHES_INDEX)

module.DEBUGWHITE = CreateMaterial('PPM2.Debugwhite', 'UnlitGeneric', debugwhite)
module.HAIR_MATERIAL_COLOR = CreateMaterial('PPM2.ManeTextureBase', 'UnlitGeneric', debugwhite)
module.TAIL_MATERIAL_COLOR = CreateMaterial('PPM2.TailTextureBase', 'UnlitGeneric', debugwhite)
module.WINGS_MATERIAL_COLOR = CreateMaterial('PPM2.WingsMaterialBase', 'UnlitGeneric', debugwhite)
module.HORN_MATERIAL_COLOR = CreateMaterial('PPM2.HornMaterialBase', 'UnlitGeneric', additionTable('$basetexture', 'models/ppm2/base/horn'))
module.BODY_MATERIAL = CreateMaterial('PPM2.BodyTexture', 'UnlitGeneric', additionTable('$basetexture', 'models/ppm2/base/body'))
module.HORN_DETAIL_BUMP = CreateMaterial('PPM2.HornBumpMapRenderer', 'UnlitGeneric', additionTable('$basetexture', 'models/ppm2/base/horn_normal'))
module.HORN_DETAIL_COLOR = Material('models/ppm2/partrender/horn_detail.png')
module.EYE_OVAL = Material('models/ppm2/partrender/eye_oval.png')
module.EYE_GRAD = Material('models/ppm2/partrender/eye_grad.png')
module.EYE_EFFECT = Material('models/ppm2/partrender/eye_effect.png')
module.EYE_LINE_L_1 = Material('models/ppm2/partrender/eye_line_l1.png')
module.EYE_LINE_R_1 = Material('models/ppm2/partrender/eye_line_r1.png')
module.EYE_LINE_L_2 = Material('models/ppm2/partrender/eye_line_l2.png')
module.EYE_LINE_R_2 = Material('models/ppm2/partrender/eye_line_r2.png')
module.EYEBROWS = Material('models/ppm2/partrender/eyebrows.png')
module.PONY_SOCKS = Material('models/ppm2/texclothes/pony_socks.png')

module.LIPS = Material('models/ppm2/partrender/lips.png')
module.NOSE = Material('models/ppm2/partrender/nose.png')
module.BODY_BUMP = Material('models/ppm2/partrender/body_bump2.png')

module.EYE_CORNERA = Material('models/ppm2/eyes/eye_cornea')
module.EYE_CORNERA_OVAL = Material('models/ppm2/eyes/eye_cornea_oval')
module.EYE_EXTRA = Material('models/ppm2/eyes/eye_extra')
module.EYE_EXTRA2 = Material('models/ppm2/eyes/eye_extra2')
module.EYE_LIGHTWARP = Material('models/ppm2/eyes/eye_lightwarp')
module.EYE_REFLECTION2 = Material('models/ppm2/eyes/eye_reflection')

module.MAGIC_HANDS_MATERIAL = Material('models/ppm2/base/magic_arms')

__index = (key) =>
	value = rawget(getmetatable(@).original, key)
	return value if not isstring(value)
	mat = _Material(value, 'smooth')
	table.insert(RELOADABLE_MATERIALS, mat)
	rawset(@, key, mat)
	return mat

patch = (input) ->
	patch(value) for key, value in pairs(input) when istable(value)

	meta = {
		__index: __index
		original: table.Copy(input)
	}

	for k in *table.GetKeys(input)
		if not istable(input[k])
			input[k] = nil

	return setmetatable(input, meta)

PPM2.MaterialsRegistry = patch(module)

return module

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

PPM2.USE_HIGHRES_BODY = CreateConVar('ppm2_cl_hires_body', '0', {FCVAR_ARCHIVE}, 'Use high resoluation when rendering pony bodies. AFFECTS ONLY TEXTURE COMPILATION TIME (increases lag spike on pony data load)')
PPM2.USE_HIGHRES_TEXTURES = CreateConVar('ppm2_cl_hires_generic', '0', {FCVAR_ARCHIVE}, 'Create 1024x1024 textures instead of 512x512 on texture compiling')
PPM2.FORCE_PRECACHE = CreateConVar('ppm2_cl_force_precache', '0', {FCVAR_ARCHIVE}, 'Force precache render textures instead of loading them when needed')

_Material = Material

RELOADABLE_MATERIALS = {}
PPM2.RELOADABLE_MATERIALS = RELOADABLE_MATERIALS

concommand.Add 'ppm2_reload_materials', ->
	cTime = SysTime()
	for _, mat in ipairs RELOADABLE_MATERIALS
		if texname = mat\GetString('$basetexture')
			mat\SetTexture('$basetexture', texname)
		if texture = mat\GetTexture('$basetexture')
			texture\Download()
		mat\Recompute()
	PPM2.PonyTextureController.URL_MATERIAL_CACHE = {}
	PPM2.PonyTextureController.SessionID = math.random(1, 1000)
	PPM2.Message('Reloaded textures in ', math.floor((SysTime() - cTime) * 100000) / 100, ' milliseconds.')
	RunConsoleCommand('ppm2_reload')
	RunConsoleCommand('ppm2_require')

_CreateMaterial = CreateMaterial

Material = (path) ->
	return path if not PPM2.FORCE_PRECACHE\GetBool()
	matNew = _Material(path)
	table.insert(RELOADABLE_MATERIALS, matNew)
	return matNew

CreateMaterial = (name, shader, data) ->
	matNew, time = _CreateMaterial(name, shader, data)
	table.insert(RELOADABLE_MATERIALS, matNew)
	return matNew, time

module = {
	BODY_DETAILS: {
		Material('models/ppm2/partrender/body_leggrad1.png')
		Material('models/ppm2/partrender/body_lines1.png')
		Material('models/ppm2/partrender/body_stripes1.png')
		Material('models/ppm2/partrender/body_headstripes1.png')
		Material('models/ppm2/partrender/body_freckles.png')
		Material('models/ppm2/partrender/body_hooves1.png')
		Material('models/ppm2/partrender/body_hooves2.png')
		Material('models/ppm2/partrender/body_headmask1.png')
		Material('models/ppm2/partrender/body_hooves1_crit.png')
		Material('models/ppm2/partrender/body_hooves2_crit.png')
		Material('models/ppm2/partrender/body_spots1.png')
		Material('models/ppm2/partrender/body_robotic.png')
		Material('models/ppm2/partrender/dash-e.png')
		Material('models/ppm2/partrender/eye_scar.png')
		Material('models/ppm2/partrender/eye_wound.png')
		Material('models/ppm2/partrender/body_scar.png')
		Material('models/ppm2/partrender/gear_socks.png')
		Material('models/ppm2/partrender/sharp_hooves.png')
		Material('models/ppm2/partrender/sharp_hooves2.png')
		Material('models/ppm2/partrender/separated_muzzle.png')
		Material('models/ppm2/partrender/eye_scar_left.png')
		Material('models/ppm2/partrender/eye_scar_right.png')
		Material('models/ppm2/partrender/android_albedo.png')
		Material('models/ppm2/partrender/android_paint.png')
		Material('models/ppm2/partrender/android_albedo_strip.png')
		Material('models/ppm2/partrender/android_paint_strip.png')
		Material('models/ppm2/partrender/cow_paint.png')
		Material('models/ppm2/partrender/deer_paint.png')
		Material('models/ppm2/partrender/deer_extended_paint.png')
		Material('models/ppm2/partrender/demonic_paint.png')
		Material('models/ppm2/partrender/ear_inner_paint.png')
		Material('models/ppm2/partrender/zebra_detail_paint.png')
	}

	UPPER_MANE_DETAILS: {
		[4]: {1, Material('models/ppm2/partrender/upmane_5_mask0.png')}
		[5]: {1, Material('models/ppm2/partrender/upmane_6_mask0.png')}
		[7]: {2, Material('models/ppm2/partrender/upmane_8_mask0.png'), Material('models/ppm2/partrender/upmane_8_mask1.png')}
		[8]: {3, Material('models/ppm2/partrender/upmane_9_mask0.png'), Material('models/ppm2/partrender/upmane_9_mask1.png'), Material('models/ppm2/partrender/upmane_9_mask2.png')}
		[9]: {1, Material('models/ppm2/partrender/upmane_10_mask0.png')}
		[10]: {3, Material('models/ppm2/partrender/upmane_11_mask0.png'), Material('models/ppm2/partrender/upmane_11_mask1.png'), Material('models/ppm2/partrender/upmane_11_mask2.png')}
		[11]: {1, Material('models/ppm2/partrender/upmane_12_mask0.png')}
		[12]: {1, Material('models/ppm2/partrender/upmane_13_mask0.png')}
		[13]: {1, Material('models/ppm2/partrender/upmane_14_mask0.png')}
		[14]: {1, Material('models/ppm2/partrender/upmane_15_mask0.png')}
	}

	LOWER_MANE_DETAILS: {
		[4]: {1, Material('models/ppm2/partrender/dnmane_5_mask0.png')}
		[7]: {2, Material('models/ppm2/partrender/dnmane_8_mask0.png'), Material('models/ppm2/partrender/dnmane_8_mask1.png')}
		[8]: {2, Material('models/ppm2/partrender/dnmane_9_mask0.png'), Material('models/ppm2/partrender/dnmane_9_mask1.png')}
		[9]: {3, Material('models/ppm2/partrender/dnmane_10_mask0.png'), Material('models/ppm2/partrender/dnmane_10_mask1.png'), Material('models/ppm2/partrender/dnmane_10_mask2.png')}
		[10]: {2, Material('models/ppm2/partrender/dnmane_11_mask0.png'), Material('models/ppm2/partrender/dnmane_11_mask1.png')}
		[11]: {1, Material('models/ppm2/partrender/dnmane_12_mask0.png')}
	}

	TAIL_DETAILS: {
		[4]: {1, Material('models/ppm2/partrender/tail_5_mask0.png')}
		[7]: {4, Material('models/ppm2/partrender/tail_8_mask0.png'), Material('models/ppm2/partrender/tail_8_mask1.png'), Material('models/ppm2/partrender/tail_8_mask2.png'), Material('models/ppm2/partrender/tail_8_mask3.png'), Material('models/ppm2/partrender/tail_8_mask4.png')}
		[9]: {1, Material('models/ppm2/partrender/tail_10_mask0.png')}
		[10]: {3, Material('models/ppm2/partrender/tail_11_mask0.png'), Material('models/ppm2/partrender/tail_11_mask1.png'), Material('models/ppm2/partrender/tail_11_mask2.png')}
		[11]: {2, Material('models/ppm2/partrender/tail_12_mask0.png'), Material('models/ppm2/partrender/tail_12_mask1.png')}
		[12]: {1, Material('models/ppm2/partrender/tail_13_mask0.png')}
		[13]: {1, Material('models/ppm2/partrender/tail_14_mask0.png')}
	}

	SOCKS_PATCHS: {
		'models/props_pony/ppm/ppm_socks/socks_striped_unlit'
		'models/props_pony/ppm2/ppm_socks/custom/geometric1_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric2_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric3_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric4_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric5_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric6_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric7_1.png'
		'models/props_pony/ppm2/ppm_socks/custom/geometric8_1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/dark1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers10.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers11.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers12.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers13.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers14.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers15.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers16.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers17.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers18.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers19.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers2.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers20.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers3.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers4.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers5.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers6.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers7.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers8.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/flowers9.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/grey1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/grey2.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/grey3.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/hearts1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/hearts2.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/snow1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/wallpaper1.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/wallpaper2.png'
		'models/props_pony/ppm2/ppm_socks/custom_textured/wallpaper3.png'
	}

	SOCKS_DETAILS_PATCHS: {
		[2]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric1_4.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric1_5.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric1_6.png'
		}

		[3]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric2_3.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric2_4.png'
		}

		[4]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric3_2.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric3_3.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric3_5.png'
		}

		[5]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric4_2.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric4_3.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric4_4.png'
		}

		[6]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric5_4.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric5_5.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric5_6.png'
		}

		[7]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric6_2.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric6_3.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric6_4.png'
		}

		[8]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric7_3.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric7_4.png'
		}

		[9]: {
			'models/props_pony/ppm2/ppm_socks/custom/geometric8_2.png'
			'models/props_pony/ppm2/ppm_socks/custom/geometric8_3.png'
		}
	}
}

additionTable = (...) ->
	tab = {'$ignorez': 1, '$vertexcolor': 1, '$vertexalpha': 1, '$nolod': 1}
	args = {...}
	for i = 1, #args, 2
		key, val = args[i], args[i + 1]
		tab[key] = val
	return tab

module.SOCKS_MATERIALS = [Material(id) for _, id in ipairs module.SOCKS_PATCHS]
module.SOCKS_DETAILS = {i, [Material(path) for path in *data] for i, data in pairs module.SOCKS_DETAILS_PATCHS}
module.CUTIEMARKS = [Material("models/ppm2/cmarks/#{mark}.png") for _, mark in ipairs PPM2.DefaultCutiemarks]
module.SUITS = [Material("models/ppm2/texclothes/#{mat}.png") for _, mat in ipairs {
	'clothes_royalguard', 'clothes_sbs_full'
	'clothes_sbs_light', 'clothes_wbs_full'
	'clothes_wbs_light', 'spidermane_light'
	'spidermane_full'
}]

module.TATTOOS = [Material("models/ppm2/partrender/tattoo/#{fil\lower()}.png") for _, fil in ipairs PPM2.TATTOOS_REGISTRY when fil ~= 'NONE']
debugwhite = {
	'$basetexture': 'models/debug/debugwhite'
	'$ignorez': 1
	'$vertexcolor': 1
	'$vertexalpha': 1
	'$nolod': 1
}

module.EYE_OVALS = {
	Material('models/ppm2/partrender/eye_oval.png')
	Material('models/ppm2/partrender/eye_oval_aperture.png')
}

module.EYE_REFLECTIONS = {
	Material('models/ppm2/partrender/eye_reflection.png')
	Material('models/ppm2/partrender/eye_reflection_crystal_foal.png')
	Material('models/ppm2/partrender/eye_reflection_crystal_unisex.png')
	Material('models/ppm2/partrender/eye_reflection_foal.png')
	Material('models/ppm2/partrender/eye_reflection_male.png')
}

module.HEAD_CLOTHES = {
	[2]: {
		Material('models/ppm2/clothesrender/hat_aj.png')
	}

	[3]: {
		Material('models/ppm2/clothesrender/hat_braeburn_2.png')
	}

	[4]: {
		Material('models/ppm2/clothesrender/tr_hat_stars_1.png')
		Material('models/ppm2/clothesrender/tr_hat_stars_2.png')
	}

	[5]: {
		Material('models/ppm2/clothesrender/headphones_1.png')
		Material('models/ppm2/clothesrender/headphones_2.png')
		Material('models/ppm2/clothesrender/headphones_note.png')
	}
}

module.HEAD_CLOTHES_INDEX = {
	[2]: 0
	[3]: 1
	[4]: 2
	[5]: 3
}

module.NECK_CLOTHES = {
	[2]: {
		Material('models/ppm2/clothesrender/winter_scarf_1.png')
		Material('models/ppm2/clothesrender/winter_scarf_2.png')
	}

	[3]: {
		{
			Material('models/ppm2/clothesrender/cape_stars_1.png')
			Material('models/ppm2/clothesrender/cape_stars_2.png')
			nil
		}
		{
			Material('models/ppm2/clothesrender/gem.png')
			nil
		}
	}

	[4]: {
		Material('models/ppm2/clothesrender/tie_1.png')
		Material('models/ppm2/clothesrender/tie_2.png')
	}

	[5]: {
		Material('models/ppm2/clothesrender/bowtie_1.png')
		Material('models/ppm2/clothesrender/bowtie_2.png')
	}
}

module.NECK_CLOTHES_INDEX = {
	[2]: 4
	[3]: {5, 6}
	[4]: 7
	[5]: 8
}

module.BODY_CLOTHES = {
	[2]: {
		Material('models/ppm2/clothesrender/vest_pouches.png')
		Material('models/ppm2/clothesrender/vest_string.png')
	}

	[3]: {
		Material('models/ppm2/clothesrender/shirt.png')
	}

	[4]: {
		Material('models/ppm2/clothesrender/hoodie.png')
	}

	[5]: {
		Material('models/ppm2/clothesrender/badge.png')
	}
}

module.BODY_CLOTHES_INDEX = {
	[2]: 9
	[3]: 10
	[4]: 11
	[5]: 12
}

module.EYE_CLOTHES = {
	[2]: {
		{}
		{Material('models/ppm2/clothesrender/lense.png')}
	}
	[4]: {
		{}
		{Material('models/ppm2/clothesrender/shades_lense.png')}
	}
	[6]: {
		{}
		{}
	}
	[8]: {
		{}
		{}
	}
}

module.EYE_CLOTHES[3] = module.EYE_CLOTHES[2]
module.EYE_CLOTHES[5] = module.EYE_CLOTHES[4]
module.EYE_CLOTHES[7] = module.EYE_CLOTHES[6]
module.EYE_CLOTHES[9] = module.EYE_CLOTHES[8]

module.EYE_CLOTHES_INDEX = {
	[2]: {13, 14}
	[4]: {15, 16}
	[6]: {17, 18}
	[8]: {20, 21}
}

module.EYE_CLOTHES_INDEX[3] = module.EYE_CLOTHES_INDEX[2]
module.EYE_CLOTHES_INDEX[5] = module.EYE_CLOTHES_INDEX[4]
module.EYE_CLOTHES_INDEX[7] = module.EYE_CLOTHES_INDEX[6]
module.EYE_CLOTHES_INDEX[9] = module.EYE_CLOTHES_INDEX[8]

do
	_prMaterial = (tab) ->
		for i, sub in pairs(tab)
			if not istable(sub[1])
				tab[i] = {}
				tab[i][1] = [mat for mat in *sub when not isnumber(mat)]

	_prIndex = (tab) ->
		for i, sub in pairs(tab)
			if not istable(sub)
				tab[i] = {sub}

	_prMaterial(module.HEAD_CLOTHES)
	_prIndex(module.HEAD_CLOTHES_INDEX)
	_prMaterial(module.NECK_CLOTHES)
	_prIndex(module.NECK_CLOTHES_INDEX)
	_prMaterial(module.BODY_CLOTHES)
	_prIndex(module.BODY_CLOTHES_INDEX)
	_prMaterial(module.EYE_CLOTHES)
	_prIndex(module.EYE_CLOTHES_INDEX)

module.DEBUGWHITE = CreateMaterial('PPM2.Debugwhite', 'UnlitGeneric', debugwhite)
module.HAIR_MATERIAL_COLOR = CreateMaterial('PPM2.ManeTextureBase', 'UnlitGeneric', debugwhite)
module.TAIL_MATERIAL_COLOR = CreateMaterial('PPM2.TailTextureBase', 'UnlitGeneric', debugwhite)
module.WINGS_MATERIAL_COLOR = CreateMaterial('PPM2.WingsMaterialBase', 'UnlitGeneric', debugwhite)
module.HORN_MATERIAL_COLOR = CreateMaterial('PPM2.HornMaterialBase', 'UnlitGeneric', additionTable('$basetexture', 'models/ppm2/base/horn'))
module.BODY_MATERIAL = CreateMaterial('PPM2.BodyTexture', 'UnlitGeneric', additionTable('$basetexture', 'models/ppm2/base/body'))
module.HORN_DETAIL_BUMP = CreateMaterial('PPM2.HornBumpMapRenderer', 'UnlitGeneric', additionTable('$basetexture', 'models/ppm2/base/horn_normal'))
module.HORN_DETAIL_COLOR = Material('models/ppm2/partrender/horn_detail.png')
module.EYE_OVAL = Material('models/ppm2/partrender/eye_oval.png')
module.EYE_GRAD = Material('models/ppm2/partrender/eye_grad.png')
module.EYE_EFFECT = Material('models/ppm2/partrender/eye_effect.png')
module.EYE_LINE_L_1 = Material('models/ppm2/partrender/eye_line_l1.png')
module.EYE_LINE_R_1 = Material('models/ppm2/partrender/eye_line_r1.png')
module.EYE_LINE_L_2 = Material('models/ppm2/partrender/eye_line_l2.png')
module.EYE_LINE_R_2 = Material('models/ppm2/partrender/eye_line_r2.png')
module.EYEBROWS = Material('models/ppm2/partrender/eyebrows.png')
module.PONY_SOCKS = Material('models/ppm2/texclothes/pony_socks.png')

module.LIPS = Material('models/ppm2/partrender/lips.png')
module.NOSE = Material('models/ppm2/partrender/nose.png')
module.BODY_BUMP = Material('models/ppm2/partrender/body_bump2.png')

module.EYE_CORNERA = Material('models/ppm2/eyes/eye_cornea')
module.EYE_CORNERA_OVAL = Material('models/ppm2/eyes/eye_cornea_oval')
module.EYE_EXTRA = Material('models/ppm2/eyes/eye_extra')
module.EYE_EXTRA2 = Material('models/ppm2/eyes/eye_extra2')
module.EYE_LIGHTWARP = Material('models/ppm2/eyes/eye_lightwarp')
module.EYE_REFLECTION2 = Material('models/ppm2/eyes/eye_reflection')

module.MAGIC_HANDS_MATERIAL = Material('models/ppm2/base/magic_arms')

__index = (key) =>
	value = rawget(getmetatable(@).original, key)
	return value if not isstring(value)
	mat = _Material(value, 'smooth')
	table.insert(RELOADABLE_MATERIALS, mat)
	rawset(@, key, mat)
	return mat

patch = (input) ->
	patch(value) for key, value in pairs(input) when istable(value)

	meta = {
		__index: __index
		original: table.Copy(input)
	}

	for k in *table.GetKeys(input)
		if not istable(input[k])
			input[k] = nil

	return setmetatable(input, meta)

PPM2.MaterialsRegistry = patch(module)

return module


Export a wrapper to moon.type

Export a wrapper to moon.type

moon = require "moon"

export instance, moon_type

moon_check = (c, b) ->
	moon.type(c) == moon.type(b)

instance = (c, b) ->
	base = false
	if c.__parent != nil
		base = instance c.__parent, b

	base or moon_check(c, b) or moon_check(c.__parent, b)

moon_type = moon.type

moon = require "moon"

export instance

moon_check = (c, b) ->
	moon.type(c) == moon.type(b)

instance = (c, b) ->
	base = false
	if c.__parent != nil
		base = instance c.__parent, b

	base or moon_check(c, b) or moon_check(c.__parent, b)


fixed /mik

fixed /mik

html = require "lapis.html"
config = (require "lapis.config").get!
appname=config.appname or "KinkyEureka"

class Header extends html.Widget
  @include "lazuli.modules.user_management.views.mixin_menu"
  header_item:(act,txt,...)=>
    url=@url_for(act,...)
    selcl=(@req.parsed_url.path==url and " pure-menu-selected" or "")
    li class:"pure-menu-item"..selcl,-> a href:url, class:"pure-menu-link",txt
  content: =>
    div class:"header",->
      div class:"pure-menu pure-menu-horizontal",->
        a href:@url_for"index", class:"pure-menu-heading pure-menu-link", appname
        ul class:"pure-menu-list",->
        if @modules.user_management.currentuser
          ul class:"pure-menu-list usermenu",->
            url=@url_for("profile_show",id: @modules.user_management.currentuser.id)
            li class: (
                        "own_profile_link pure-menu-has-children pure-menu-allow-hover pure-menu-item"..
                        (@req.parsed_url.path==url and " pure-menu-selected" or "")
                      ), ->
              a href: url, class: "pure-menu-link", @modules.user_management.currentuser.username
              ul class: "pure-menu-children", ->
                @header_item "profile_edit", "Edit profile"
                if config.projectStage=="alpha" and @modules.user_management.currentuser.is_admin or config.projectStage=="beta"
                  @header_item "make_invite_key_form", "Create invite key!"
                @header_item "lazuli_modules_usermanagement_logout", "Logout"
        else
          @userManagementMixinMenu!

html = require "lapis.html"
config = (require "lapis.config").get!
appname=config.appname or "KinkyEureka"

class Header extends html.Widget
  @include "lazuli.modules.user_management.views.mixin_menu"
  header_item:(act,txt,...)=>
    url=@url_for(act,...)
    selcl=(@req.parsed_url.path==url and " pure-menu-selected" or "")
    li class:"pure-menu-item"..selcl,-> a href:url, class:"pure-menu-link",txt
  content: =>
    div class:"header",->
      div class:"pure-menu pure-menu-horizontal",->
        a href:@url_for"index", class:"pure-menu-heading pure-menu-link", appname
        ul class:"pure-menu-list",->
        if @modules.user_management.currentuser
          ul class:"pure-menu-list usermenu",->
            url=@url_for("profile_show",id: @modules.user_management.currentuser.id)
            li class: (
                        "own_profile_link pure-menu-has-children pure-menu-allow-hover pure-menu-item"..
                        (@req.parsed_url.path==url and " pure-menu-selected" or "")
                      ), ->
              a href: url, class: "pure-menu-link", @modules.user_management.currentuser.username
              ul class: "pure-menu-children", ->
                @header_item "profile_edit", "Edit profile"
                if config.projectStage=="alpha" and @modules.user_management.currentuser.id==1 or config.projectStage=="beta"
                  @header_item "make_invite_key_form", "Create invite key!"
                @header_item "lazuli_modules_usermanagement_logout", "Logout"
        else
          @userManagementMixinMenu!


PAC3 hook call got merged, implementing new hook

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

timer.Simple 0, ->
    timer.Simple 0, ->
        timer.Simple 0, ->
            return if not pac
            pac.__ppm2_ResetBones = pac.__ppm2_ResetBones or pac.ResetBones
            pac.ResetBones = (ent, ...) ->
                status = pac.__ppm2_ResetBones(ent, ...)
                if data = ent\GetPonyData()
                    hook.Call('PPM2_PACResetBones', nil, StrongEntity(ent), data)
                return status

hook.Add 'PAC3ResetBones', 'PPM2.ResetBones', (ent) ->
    if pac.__ppm2_ResetBones
        pac.ResetBones = pac.__ppm2_ResetBones
        pac.__ppm2_ResetBones = nil
    if data = ent\GetPonyData()
        hook.Call('PPM2_PACResetBones', nil, StrongEntity(ent), data)


irc: Only break arguments with space (x20)

--- IRC client class
-- @classmod IRCClient

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

moonscript = {
	errors: require 'moonscript.errors'
}

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

local IRCClient, ContextTable, priority

priority = {
	HIGH: 1,
	DEFAULT: 2,
	LOW: 3,
}

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

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

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

	remove: (name)=>
		for context_name, tbl in pairs(self)
			tbl[name] = nil

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

	__tostring: => "IRCClient"

	default_config = {
		prefix: "!"
	}

	line_pattern = re.compile [[
		-- tags, command, args
		line <- {|
			{:tags: {| (tags sp)? |} :}
			{:prefix: {| (prefix sp)? |} :}
			{:command: (command / numeric) :}
			{:args: {| (sp arg)* |} :} |}
		tags <- '@' tag (';' tag)*
		tag <- {|
			{:is_client: {'+'} :}? -- check: if tag.is_client
			{: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 <- ':' {.+} / {[^ ]+}
		sp <- %s
	]], esc_tag: (tag)-> tag\gsub "\\(.)", setmetatable({
		[":"]: ";"
		s: " "
		r: "\r"
		n: "\n"
	}, __index: (t, k) -> k)

	--- Get a tag based on search parameters (is_client IS NOT matched by key)
	-- @tparam table tags Tag from IRC parser
	-- @tparam table opts Options to search for (is_client: bool, key: string)
	get_tag: (tags, opts)=>
		:is_client, :key = opts
		for tag in *tags
			continue if is_client ~= nil and tag.is_client ~= is_client
			if key ~= nil
				continue if tag.vendor ~= nil and key ~= "#{tag.vendor}/#{tag.key}"
				continue if tag.vendor == nil and key ~= tag.key
			return tag

		return {}

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

	get_priority = (options, fn)->
		return fn and options.priority or priority.DEFAULT

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

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

		if options.wrap_iter
			tmp_fn = coroutine.wrap fn
			tmp_fn!
			return tmp_fn

		fn

	assert_context: (context_table)=>
		assert @context, "Missing @with_context"
		return context_table[assert @context, "Missing @with_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

		self["senders"][context] = {} -- no priority for senders, only one
		for key in *{"hooks", "handlers", "commands"}
			self[key][context] = [{} for p=priority.HIGH, priority.LOW]

		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)=>
		final_command = command
		commands = @assert_context @commands

		-- `options` is passed, which fills in `command`
		if command
			-- is async, send typing until completed
			if options.async
				-- async, send @+draft/typing
				final_command = (prefix, target, line, ...)=>
					@vars.typing_counter = 0 if not @vars.typing_counter
					@vars.typing_counter += 1
					@send 'TAGMSG', target, "+draft/typing": 'active'
					@pcall command, prefix, target, line, ...
					@vars.typing_counter -= 1
					print @vars.typing_counter
					if @vars.typing_counter == 0
						@send 'TAGMSG', target, "+draft/typing": 'done'
					else
						@send 'TAGMSG', target, "+draft/typing": 'active'

		p = get_priority options, final_command
		commands[p][name] = @handle_options options, final_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)=>
		hooks = @assert_context @hooks

		p = get_priority options, hook

		unless hooks[p][name]
			hooks[p][name] = {@handle_options options, hook}
		else
			table.insert hooks[p][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)=>
		handlers = @assert_context @handlers

		p = get_priority options, handler

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

	--- Add an IRC command sending handler, does NOT take a priority
	-- @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)=>
		senders = @assert_context @senders
		senders[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', nick
		if pass and ssl
			Logger.debug '*** Sending password'
			@send_raw 'PASS', pass
		elseif pass
			Logger.print Logger.level.error .. '*** Not sending password: TLS not enabled ***'
		@send_raw 'USER', 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'

	tag_escapes = {
		";": ":"
		" ": "s"
		"\r": "r"
		"\n": "n"
	}

	--- Serialize a value for being sent in a tag
	serialize_tag_string = (key, value)->
		return key if type(value) == "boolean"
		return key .. "=" .. value\gsub "([: \r\n])", (value)-> "\\#{tbl[value]}"

	--- Serialize client->client tags for sending to the server
	-- @param tags Table of tags to serialize
	serialize_tags = (tags)->
		output = [serialize_tag_string(k, v) for k, v in pairs tags]
		return "@#{table.concat output, ';'}"

	--- Send a raw line to the currently connected IRC server
	-- @param ... List of strings, concatenated using spaces
	send_raw: (...)=>
		output = {...}
		arg_count = select("#", ...)
		opts_table = select(arg_count, ...)
		if type(opts_table) == "table"
			-- we have tags, server accepts tags
			if opts_table.tags and next(opts_table.tags) and @server.ircv3_caps["message-tags"]
				table.insert output, 1, serialize_tags opts_table.tags
			-- remove opts_table regardless of caps
			table.remove output, #output
		arg_count = #output
		if output[arg_count]\find " "
			output[arg_count] = ":#{output[arg_count]}"
		@socket\write table.concat(output, ' ') .. '\n'
		Logger.debug '==> ' .. table.concat output, ' '

	--- 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, uses_priority: false
		input = sender self, ...
		@socket\write input if input

	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)
		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.debug Logger.level.error .. "*** Hook not found for #{command}" unless has_run

		return has_errors, errors, has_run

	process_line: (line, opts={})=>
		{:tags, :prefix, :command, :args} = @parse line
		opts.line = line
		@process prefix, command, args, tags, opts

	--- Run handlers for an unparsed command
	-- @tparam table prefix Prefix argument from server (nick, user, host)
	-- @tparam table args Argument list from server, including trailing
	-- @tparam table tags Tags list from server
	-- @tparam table opts Optional arguments, in_batch: IRCv3 batching
	process: (prefix, command, args, tags, opts={})=>
		{:in_batch} = opts
		{:nick, :user, :host} = prefix
		if opts.line
			Logger.debug Logger.level.warn .. '--- | Line: ' .. opts.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

		if not in_batch and tags.batch
			-- will return _, _, true if a hook run; this is necessary because
			-- the protocol might send batches even if we don't support them.
			return unless select(2, @fire_hook "BATCH.#{tags.batch}", prefix,
				args, tags, opts)

		for handler in *IRCClient.handlers\get command
			has_run = true unless has_run
			@pcall handler, prefix, args, tags, opts

		for handler in *@handlers\get command
			has_run = true unless has_run
			@pcall handler, prefix, args, tags, opts

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

	--- Iterate over lines from a server and handle errors appropriately
	loop: ()=>
		for received_line in @socket\lines! do
			@pcall @process_line, received_line, in_batch: false

	--- Call a function and, when failed, print debug information
	-- @tparam function fn Function to be called
	-- @param ... vararg list to pass to function
	pcall: (fn, ...)=>
		ok, err = xpcall fn, self\log_traceback, self, ...
		if not ok
			Logger.debug "Arguments:"
			for arg in *{...}
				Logger.debug tostring arg
		return ok, err

	--- Call a function without self and, when failed, print debug information
	-- @tparam function fn Function to be called
	-- @param ... vararg list to pass to function
	pcall_bare: (fn, ...)=>
		ok, err = xpcall fn, self\log_traceback, ...
		if not ok
			Logger.debug "Arguments:"
			Logger.debug tostring arg for arg in *{...}
		return ok, err

	--- Print a traceback using the internal logging mechanism
	-- @see IRCClient\log
	log_traceback: (err)=>
		err = tostring err
		Logger.debug moonscript.errors.rewrite_traceback debug.traceback!, err
		Logger.debug "#{Logger.level.error} ---"
		return err

	--- 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, :priority}

--- IRC client class
-- @classmod IRCClient

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

moonscript = {
	errors: require 'moonscript.errors'
}

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

local IRCClient, ContextTable, priority

priority = {
	HIGH: 1,
	DEFAULT: 2,
	LOW: 3,
}

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

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

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

	remove: (name)=>
		for context_name, tbl in pairs(self)
			tbl[name] = nil

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

	__tostring: => "IRCClient"

	default_config = {
		prefix: "!"
	}

	line_pattern = re.compile [[
		-- tags, command, args
		line <- {|
			{:tags: {| (tags sp)? |} :}
			{:prefix: {| (prefix sp)? |} :}
			{:command: (command / numeric) :}
			{:args: {| (sp arg)* |} :} |}
		tags <- '@' tag (';' tag)*
		tag <- {|
			{:is_client: {'+'} :}? -- check: if tag.is_client
			{: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)

	--- Get a tag based on search parameters (is_client IS NOT matched by key)
	-- @tparam table tags Tag from IRC parser
	-- @tparam table opts Options to search for (is_client: bool, key: string)
	get_tag: (tags, opts)=>
		:is_client, :key = opts
		for tag in *tags
			continue if is_client ~= nil and tag.is_client ~= is_client
			if key ~= nil
				continue if tag.vendor ~= nil and key ~= "#{tag.vendor}/#{tag.key}"
				continue if tag.vendor == nil and key ~= tag.key
			return tag

		return {}

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

	get_priority = (options, fn)->
		return fn and options.priority or priority.DEFAULT

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

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

		if options.wrap_iter
			tmp_fn = coroutine.wrap fn
			tmp_fn!
			return tmp_fn

		fn

	assert_context: (context_table)=>
		assert @context, "Missing @with_context"
		return context_table[assert @context, "Missing @with_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

		self["senders"][context] = {} -- no priority for senders, only one
		for key in *{"hooks", "handlers", "commands"}
			self[key][context] = [{} for p=priority.HIGH, priority.LOW]

		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)=>
		final_command = command
		commands = @assert_context @commands

		-- `options` is passed, which fills in `command`
		if command
			-- is async, send typing until completed
			if options.async
				-- async, send @+draft/typing
				final_command = (prefix, target, line, ...)=>
					@vars.typing_counter = 0 if not @vars.typing_counter
					@vars.typing_counter += 1
					@send 'TAGMSG', target, "+draft/typing": 'active'
					@pcall command, prefix, target, line, ...
					@vars.typing_counter -= 1
					print @vars.typing_counter
					if @vars.typing_counter == 0
						@send 'TAGMSG', target, "+draft/typing": 'done'
					else
						@send 'TAGMSG', target, "+draft/typing": 'active'

		p = get_priority options, final_command
		commands[p][name] = @handle_options options, final_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)=>
		hooks = @assert_context @hooks

		p = get_priority options, hook

		unless hooks[p][name]
			hooks[p][name] = {@handle_options options, hook}
		else
			table.insert hooks[p][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)=>
		handlers = @assert_context @handlers

		p = get_priority options, handler

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

	--- Add an IRC command sending handler, does NOT take a priority
	-- @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)=>
		senders = @assert_context @senders
		senders[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', nick
		if pass and ssl
			Logger.debug '*** Sending password'
			@send_raw 'PASS', pass
		elseif pass
			Logger.print Logger.level.error .. '*** Not sending password: TLS not enabled ***'
		@send_raw 'USER', 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'

	tag_escapes = {
		";": ":"
		" ": "s"
		"\r": "r"
		"\n": "n"
	}

	--- Serialize a value for being sent in a tag
	serialize_tag_string = (key, value)->
		return key if type(value) == "boolean"
		return key .. "=" .. value\gsub "([: \r\n])", (value)-> "\\#{tbl[value]}"

	--- Serialize client->client tags for sending to the server
	-- @param tags Table of tags to serialize
	serialize_tags = (tags)->
		output = [serialize_tag_string(k, v) for k, v in pairs tags]
		return "@#{table.concat output, ';'}"

	--- Send a raw line to the currently connected IRC server
	-- @param ... List of strings, concatenated using spaces
	send_raw: (...)=>
		output = {...}
		arg_count = select("#", ...)
		opts_table = select(arg_count, ...)
		if type(opts_table) == "table"
			-- we have tags, server accepts tags
			if opts_table.tags and next(opts_table.tags) and @server.ircv3_caps["message-tags"]
				table.insert output, 1, serialize_tags opts_table.tags
			-- remove opts_table regardless of caps
			table.remove output, #output
		arg_count = #output
		if output[arg_count]\find " "
			output[arg_count] = ":#{output[arg_count]}"
		@socket\write table.concat(output, ' ') .. '\n'
		Logger.debug '==> ' .. table.concat output, ' '

	--- 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, uses_priority: false
		input = sender self, ...
		@socket\write input if input

	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)
		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.debug Logger.level.error .. "*** Hook not found for #{command}" unless has_run

		return has_errors, errors, has_run

	process_line: (line, opts={})=>
		{:tags, :prefix, :command, :args} = @parse line
		opts.line = line
		@process prefix, command, args, tags, opts

	--- Run handlers for an unparsed command
	-- @tparam table prefix Prefix argument from server (nick, user, host)
	-- @tparam table args Argument list from server, including trailing
	-- @tparam table tags Tags list from server
	-- @tparam table opts Optional arguments, in_batch: IRCv3 batching
	process: (prefix, command, args, tags, opts={})=>
		{:in_batch} = opts
		{:nick, :user, :host} = prefix
		if opts.line
			Logger.debug Logger.level.warn .. '--- | Line: ' .. opts.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

		if not in_batch and tags.batch
			-- will return _, _, true if a hook run; this is necessary because
			-- the protocol might send batches even if we don't support them.
			return unless select(2, @fire_hook "BATCH.#{tags.batch}", prefix,
				args, tags, opts)

		for handler in *IRCClient.handlers\get command
			has_run = true unless has_run
			@pcall handler, prefix, args, tags, opts

		for handler in *@handlers\get command
			has_run = true unless has_run
			@pcall handler, prefix, args, tags, opts

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

	--- Iterate over lines from a server and handle errors appropriately
	loop: ()=>
		for received_line in @socket\lines! do
			@pcall @process_line, received_line, in_batch: false

	--- Call a function and, when failed, print debug information
	-- @tparam function fn Function to be called
	-- @param ... vararg list to pass to function
	pcall: (fn, ...)=>
		ok, err = xpcall fn, self\log_traceback, self, ...
		if not ok
			Logger.debug "Arguments:"
			for arg in *{...}
				Logger.debug tostring arg
		return ok, err

	--- Call a function without self and, when failed, print debug information
	-- @tparam function fn Function to be called
	-- @param ... vararg list to pass to function
	pcall_bare: (fn, ...)=>
		ok, err = xpcall fn, self\log_traceback, ...
		if not ok
			Logger.debug "Arguments:"
			Logger.debug tostring arg for arg in *{...}
		return ok, err

	--- Print a traceback using the internal logging mechanism
	-- @see IRCClient\log
	log_traceback: (err)=>
		err = tostring err
		Logger.debug moonscript.errors.rewrite_traceback debug.traceback!, err
		Logger.debug "#{Logger.level.error} ---"
		return err

	--- 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, :priority}


textutils: add stutter

textutils: add stutter

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