Fix critical issue with network data values cutoff This issue were introduced in 2017-12-03

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

net = DLib.net

wUInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteUInt(arg, size)

wInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteInt(arg, size)

rUInt = (size = 8, min = 0, max = 255) ->
	return -> math.Clamp(net.ReadUInt(size), min, max)

rInt = (size = 8, min = -128, max = 127) ->
	return -> math.Clamp(net.ReadInt(size), min, max)

rFloat = (min = 0, max = 255) ->
	return -> math.Clamp(net.ReadDouble(), min, max)

wFloat = net.WriteDouble
rBool = net.ReadBool
wBool = net.WriteBool
rColor = net.ReadColor
wColor = net.WriteColor
rString = net.ReadString
wString = net.WriteString

COLOR_FIXER = (r = 255, g = 255, b = 255, a = 255) ->
	func = (arg = Color(r, g, b, a)) ->
		if not IsColor(arg)
			return Color()
		else
			{:r, :g, :b, :a} = arg
			if r and g and b and a
				return Color(r, g, b, a)
			else
				return Color()
	return func

URL_FIXER = (arg = '') ->
	arg = tostring(arg)
	if arg\find('^https?://')
		return arg
	else
		return ''

rURL = -> URL_FIXER(rString())

FLOAT_FIXER = (def = 1, min = 0, max = 1) ->
	defFunc = -> def if type(def) ~= 'function'
	defFunc = def if type(def) == 'function'
	return (arg = defFunc()) -> math.Clamp(tonumber(arg) or defFunc(), min, max)

INT_FIXER = (def = 1, min = 0, max = 1) ->
	defFunc = -> def if type(def) ~= 'function'
	defFunc = def if type(def) == 'function'
	return (arg = defFunc()) -> math.floor(math.Clamp(tonumber(arg) or defFunc(), min, max))

PPM2.PonyDataRegistry = {
	'age': {
		default: -> PPM2.AGE_ADULT
		getFunc: 'Age'
		enum: {'FILLY', 'ADULT', 'MATURE'}
	}

	'race': {
		default: -> PPM2.RACE_EARTH
		getFunc: 'Race'
		enum: [arg for _, arg in ipairs PPM2.RACE_ENUMS]
	}

	'wings_type': {
		default: -> 0
		getFunc: 'WingsType'
		enum: [arg for _, arg in ipairs PPM2.AvaliablePonyWings]
	}

	'gender': {
		default: -> PPM2.GENDER_FEMALE
		getFunc: 'Gender'
		enum: [arg for _, arg in ipairs PPM2.AGE_ENUMS]
	}

	'weight': {
		default: -> 1
		getFunc: 'Weight'
		min: PPM2.MIN_WEIGHT
		max: PPM2.MAX_WEIGHT
		type: 'FLOAT'
	}

	'ponysize': {
		default: -> 1
		getFunc: 'PonySize'
		min: PPM2.MIN_SCALE
		max: PPM2.MAX_SCALE
		type: 'FLOAT'
	}

	'necksize': {
		default: -> 1
		getFunc: 'NeckSize'
		min: PPM2.MIN_NECK
		max: PPM2.MAX_NECK
		type: 'FLOAT'
	}

	'legssize': {
		default: -> 1
		getFunc: 'LegsSize'
		min: PPM2.MIN_LEGS
		max: PPM2.MAX_LEGS
		type: 'FLOAT'
	}

	'spinesize': {
		default: -> 1
		getFunc: 'BackSize'
		min: PPM2.MIN_SPINE
		max: PPM2.MAX_SPINE
		type: 'FLOAT'
	}

	'male_buff': {
		default: -> PPM2.DEFAULT_MALE_BUFF
		getFunc: 'MaleBuff'
		min: PPM2.MIN_MALE_BUFF
		max: PPM2.MAX_MALE_BUFF
		type: 'FLOAT'
	}

	'clothes_head': {
		default: -> 0
		getFunc: 'HeadClothes'
		enum: [piece for piece in *PPM2.AvailableClothesHead]
	}

	'clothes_neck': {
		default: -> 0
		getFunc: 'NeckClothes'
		enum: [piece for piece in *PPM2.AvailableClothesNeck]
	}

	'clothes_body': {
		default: -> 0
		getFunc: 'BodyClothes'
		enum: [piece for piece in *PPM2.AvailableClothesBody]
	}

	'clothes_eye': {
		default: -> 0
		getFunc: 'EyeClothes'
		enum: [piece for piece in *PPM2.AvailableClothesEye]
	}

	'eyelash': {
		default: -> 0
		getFunc: 'EyelashType'
		enum: [arg for _, arg in ipairs PPM2.EyelashTypes]
	}

	'tail': {
		default: -> 2
		getFunc: 'TailType'
		enum: [arg for _, arg in ipairs PPM2.AvaliableTails]
	}

	'tail_new': {
		default: -> 2
		getFunc: 'TailTypeNew'
		enum: [arg for _, arg in ipairs PPM2.AvaliableTailsNew]
	}

	'mane': {
		default: -> 2
		getFunc: 'ManeType'
		enum: [arg for _, arg in ipairs PPM2.AvaliableUpperManes]
	}

	'mane_new': {
		default: -> 2
		getFunc: 'ManeTypeNew'
		enum: [arg for _, arg in ipairs PPM2.AvaliableUpperManesNew]
	}

	'manelower': {
		default: -> 2
		getFunc: 'ManeTypeLower'
		enum: [arg for _, arg in ipairs PPM2.AvaliableLowerManes]
	}

	'manelower_new': {
		default: -> 2
		getFunc: 'ManeTypeLowerNew'
		enum: [arg for _, arg in ipairs PPM2.AvaliableLowerManesNew]
	}

	'socks_texture': {
		default: -> 0
		getFunc: 'SocksTexture'
		enum: [arg for _, arg in ipairs PPM2.SocksTypes]
	}

	'socks_texture_url': {
		default: -> ''
		getFunc: 'SocksTextureURL'
		type: 'URL'
	}

	'tailsize': {
		default: -> 1
		getFunc: 'TailSize'
		min: PPM2.MIN_TAIL_SIZE
		max: PPM2.MAX_TAIL_SIZE
		type: 'FLOAT'
	}

	'cmark': {
		default: -> true
		getFunc: 'CMark'
		type: 'BOOLEAN'
	}

	'cmark_size': {
		default: -> 1
		getFunc: 'CMarkSize'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'cmark_color': {
		default: -> Color()
		getFunc: 'CMarkColor'
		type: 'COLOR'
	}

	'eyelash_color': {
		default: -> Color(0, 0, 0)
		getFunc: 'EyelashesColor'
		type: 'COLOR'
	}

	'eyelashes_phong_separate': {
		default: -> false
		getFunc: 'SeparateEyelashesPhong'
		type: 'BOOLEAN'
	}

	'fangs': {
		default: -> false
		getFunc: 'Fangs'
		type: 'BOOLEAN'
	}

	'bat_pony_ears': {
		default: -> false
		getFunc: 'BatPonyEars'
		type: 'BOOLEAN'
	}

	'claw_teeth': {
		default: -> false
		getFunc: 'ClawTeeth'
		type: 'BOOLEAN'
	}

	'cmark_type': {
		default: -> 4
		getFunc: 'CMarkType'
		enum: [arg for _, arg in ipairs PPM2.DefaultCutiemarks]
	}

	'cmark_url': {
		default: -> ''
		getFunc: 'CMarkURL'
		type: 'URL'
	}

	'body': {
		default: -> Color(0xEE321F)
		getFunc: 'BodyColor'
		type: 'COLOR'
	}

	'body_bump': {
		default: -> 0.5
		getFunc: 'BodyBumpStrength'
		type: 'FLOAT'
		min: 0
		max: 1
	}

	'eyebrows': {
		default: -> Color(0, 0, 0)
		getFunc: 'EyebrowsColor'
		type: 'COLOR'
	}

	'eyebrows_glow': {
		default: -> false
		getFunc: 'GlowingEyebrows'
		type: 'BOOLEAN'
	}

	'eyebrows_glow_strength': {
		default: -> 1
		getFunc: 'EyebrowsGlowStrength'
		type: 'FLOAT'
		min: 0
		max: 1
	}

	'hide_manes': {
		default: -> true
		getFunc: 'HideManes'
		type: 'BOOLEAN'
	}

	'hide_manes_socks': {
		default: -> true
		getFunc: 'HideManesSocks'
		type: 'BOOLEAN'
	}

	'hide_manes_mane': {
		default: -> true
		getFunc: 'HideManesMane'
		type: 'BOOLEAN'
	}

	'hide_manes_tail': {
		default: -> true
		getFunc: 'HideManesTail'
		type: 'BOOLEAN'
	}

	'new_horn': {
		default: -> false
		getFunc: 'UseNewHorn'
		type: 'BOOLEAN'
	}

	'new_horn_type': {
		default: -> 2
		getFunc: 'NewHornType'
		enum: [type for type in *PPM2.AvailableHorns]
	}

	'horn_color': {
		default: -> Color()
		getFunc: 'HornColor'
		type: 'COLOR'
	}

	'wings_color': {
		default: -> Color()
		getFunc: 'WingsColor'
		type: 'COLOR'
	}

	'separate_wings': {
		default: -> false
		getFunc: 'SeparateWings'
		type: 'BOOLEAN'
	}

	'separate_horn': {
		default: -> false
		getFunc: 'SeparateHorn'
		type: 'BOOLEAN'
	}

	'separate_magic_color': {
		default: -> false
		getFunc: 'SeparateMagicColor'
		type: 'BOOLEAN'
	}

	'horn_magic_color': {
		default: -> Color()
		getFunc: 'HornMagicColor'
		type: 'COLOR'
	}

	'horn_glow': {
		default: -> false
		getFunc: 'HornGlow'
		type: 'BOOLEAN'
	}

	'horn_glow_strength': {
		default: -> 1
		getFunc: 'HornGlowSrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'horn_detail_color': {
		default: -> Color(90, 90, 90)
		getFunc: 'HornDetailColor'
		type: 'COLOR'
	}

	'separate_eyes': {
		default: -> false
		getFunc: 'SeparateEyes'
		type: 'BOOLEAN'
	}

	'separate_mane': {
		default: -> false
		getFunc: 'SeparateMane'
		type: 'BOOLEAN'
	}

	'call_playerfootstep': {
		default: -> true
		getFunc: 'CallPlayerFootstepHook'
		type: 'BOOLEAN'
	}

	'disable_hoofsteps': {
		default: -> false
		getFunc: 'DisableHoofsteps'
		type: 'BOOLEAN'
	}

	'disable_wander_sounds': {
		default: -> false
		getFunc: 'DisableWanderSounds'
		type: 'BOOLEAN'
	}

	'disable_new_step_sounds': {
		default: -> false
		getFunc: 'DisableStepSounds'
		type: 'BOOLEAN'
	}

	'disable_jump_sound': {
		default: -> false
		getFunc: 'DisableJumpSound'
		type: 'BOOLEAN'
	}

	'disable_falldown_sound': {
		default: -> false
		getFunc: 'DisableFalldownSound'
		type: 'BOOLEAN'
	}

	'socks': {
		default: -> false
		getFunc: 'Socks'
		type: 'BOOLEAN'
	}

	'new_male_muzzle': {
		default: -> true
		getFunc: 'NewMuzzle'
		type: 'BOOLEAN'
	}

	'noflex': {
		default: -> false
		getFunc: 'NoFlex'
		type: 'BOOLEAN'
	}

	'socks_model': {
		default: -> false
		getFunc: 'SocksAsModel'
		type: 'BOOLEAN'
	}

	'socks_model_new': {
		default: -> false
		getFunc: 'SocksAsNewModel'
		type: 'BOOLEAN'
	}

	'socks_model_color': {
		default: -> Color()
		getFunc: 'SocksColor'
		type: 'COLOR'
	}

	'socks_new_model_color1': {
		default: -> Color()
		getFunc: 'NewSocksColor1'
		type: 'COLOR'
	}

	'socks_new_model_color2': {
		default: -> Color(0, 0, 0)
		getFunc: 'NewSocksColor2'
		type: 'COLOR'
	}

	'socks_new_model_color3': {
		default: -> Color(0, 0, 0)
		getFunc: 'NewSocksColor3'
		type: 'COLOR'
	}

	'socks_new_texture_url': {
		default: -> ''
		getFunc: 'NewSocksTextureURL'
		type: 'URL'
	}

	'suit': {
		default: -> 0
		getFunc: 'Bodysuit'
		enum: [arg for _, arg in ipairs PPM2.AvaliablePonySuits]
	}

	'left_wing_size': {
		default: -> 1
		getFunc: 'LWingSize'
		min: PPM2.MIN_WING
		max: PPM2.MAX_WING
		type: 'FLOAT'
	}

	'left_wing_x': {
		default: -> 0
		getFunc: 'LWingX'
		min: PPM2.MIN_WINGX
		max: PPM2.MAX_WINGX
		type: 'FLOAT'
	}

	'left_wing_y': {
		default: -> 0
		getFunc: 'LWingY'
		min: PPM2.MIN_WINGY
		max: PPM2.MAX_WINGY
		type: 'FLOAT'
	}

	'left_wing_z': {
		default: -> 0
		getFunc: 'LWingZ'
		min: PPM2.MIN_WINGZ
		max: PPM2.MAX_WINGZ
		type: 'FLOAT'
	}

	'right_wing_size': {
		default: -> 1
		getFunc: 'RWingSize'
		min: PPM2.MIN_WING
		max: PPM2.MAX_WING
		type: 'FLOAT'
	}

	'right_wing_x': {
		default: -> 0
		getFunc: 'RWingX'
		min: PPM2.MIN_WINGX
		max: PPM2.MAX_WINGX
		type: 'FLOAT'
	}

	'right_wing_y': {
		default: -> 0
		getFunc: 'RWingY'
		min: PPM2.MIN_WINGY
		max: PPM2.MAX_WINGY
		type: 'FLOAT'
	}

	'right_wing_z': {
		default: -> 0
		getFunc: 'RWingZ'
		min: PPM2.MIN_WINGZ
		max: PPM2.MAX_WINGZ
		type: 'FLOAT'
	}

	'teeth_color': {
		default: -> Color()
		getFunc: 'TeethColor'
		type: 'COLOR'
	}

	'mouth_color': {
		default: -> Color(219, 65, 155)
		getFunc: 'MouthColor'
		type: 'COLOR'
	}

	'tongue_color': {
		default: -> Color(235, 131, 59)
		getFunc: 'TongueColor'
		type: 'COLOR'
	}

	'bat_wing_color': {
		default: -> Color()
		getFunc: 'BatWingColor'
		type: 'COLOR'
	}

	'bat_wing_skin_color': {
		default: -> Color()
		getFunc: 'BatWingSkinColor'
		type: 'COLOR'
	}

	'separate_horn_phong': {
		default: -> false
		getFunc: 'SeparateHornPhong'
		type: 'BOOLEAN'
	}

	'separate_wings_phong': {
		default: -> false
		getFunc: 'SeparateWingsPhong'
		type: 'BOOLEAN'
	}

	'separate_mane_phong': {
		default: -> false
		getFunc: 'SeparateManePhong'
		type: 'BOOLEAN'
	}

	'separate_tail_phong': {
		default: -> false
		getFunc: 'SeparateTailPhong'
		type: 'BOOLEAN'
	}

	'alternative_fangs': {
		default: -> false
		getFunc: 'AlternativeFangs'
		type: 'BOOLEAN'
	}

	'hoof_fluffers': {
		default: -> false
		getFunc: 'HoofFluffers'
		type: 'BOOLEAN'
	}

	'hoof_fluffers_strength': {
		default: -> 1
		getFunc: 'HoofFluffersStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'ears_size': {
		default: -> 1
		getFunc: 'EarsSize'
		min: 0.1
		max: 2
		type: 'FLOAT'
	}

	'bat_pony_ears_strength': {
		default: -> 1
		getFunc: 'BatPonyEarsStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'fangs_strength': {
		default: -> 1
		getFunc: 'FangsStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'clawteeth_strength': {
		default: -> 1
		getFunc: 'ClawTeethStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'lips_color_inherit': {
		default: -> true
		getFunc: 'LipsColorInherit'
		type: 'BOOLEAN'
	}

	'nose_color_inherit': {
		default: -> true
		getFunc: 'NoseColorInherit'
		type: 'BOOLEAN'
	}

	'lips_color': {
		default: -> Color(172, 92, 92)
		getFunc: 'LipsColor'
		type: 'COLOR'
	}

	'nose_color': {
		default: -> Color(77, 84, 83)
		getFunc: 'NoseColor'
		type: 'COLOR'
	}

	'weapon_hide': {
		default: -> true
		getFunc: 'HideWeapons'
		type: 'BOOLEAN'
	}
}

for {internal, publicName} in *{{'_head', 'Head'}, {'_neck', 'Neck'}, {'_body', 'Body'}, {'_eye', 'Eye'}}
	PPM2.PonyDataRegistry["clothes#{internal}_color_nil"] = {
		default: -> false
		getFunc: "#{publicName}ClothesUseColor"
		type: 'BOOLEAN'
	}

	for i = 1, PPM2.MAX_CLOTHES_URLS
		PPM2.PonyDataRegistry["clothes#{internal}_url_#{i}"] = {
			default: -> ''
			getFunc: "#{publicName}ClothesURL#{i}"
			type: 'URL'
		}

	for num = 1, PPM2.MAX_CLOTHES_COLORS
		PPM2.PonyDataRegistry["clothes#{internal}_color_#{num}"] = {
			default: -> Color()
			getFunc: "#{publicName}ClothesColor#{num}"
			type: 'COLOR'
		}

for _, {internal, publicName} in ipairs {{'_left', 'Left'}, {'_right', 'Right'}, {'', ''}}
	PPM2.PonyDataRegistry["eye_url#{internal}"] = {
		default: -> ''
		getFunc: "EyeURL#{publicName}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["eye_bg#{internal}"] = {
		default: -> Color()
		getFunc: "EyeBackground#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_hole#{internal}"] = {
		default: -> Color(0, 0, 0)
		getFunc: "EyeHole#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_iris1#{internal}"] = {
		default: -> Color(0xEE9620)
		getFunc: "EyeIrisTop#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_iris2#{internal}"] = {
		default: -> Color(0xEE9620)
		getFunc: "EyeIrisBottom#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_irisline1#{internal}"] = {
		default: -> Color()
		getFunc: "EyeIrisLine1#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_irisline_direction#{internal}"] = {
		default: -> false
		getFunc: "EyeLineDirection#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_irisline2#{internal}"] = {
		default: -> Color()
		getFunc: "EyeIrisLine2#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_reflection#{internal}"] = {
		default: -> Color(255, 255, 255, 127)
		getFunc: "EyeReflection#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_effect#{internal}"] = {
		default: -> Color()
		getFunc: "EyeEffect#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_lines#{internal}"] = {
		default: -> true
		getFunc: "EyeLines#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_iris_size#{internal}"] = {
		default: -> 1
		getFunc: "IrisSize#{publicName}"
		min: PPM2.MIN_IRIS
		max: PPM2.MAX_IRIS
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_derp#{internal}"] = {
		default: -> false
		getFunc: "DerpEyes#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_use_refract#{internal}"] = {
		default: -> false
		getFunc: "EyeRefract#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_cornera#{internal}"] = {
		default: -> false
		getFunc: "EyeCornerA#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_derp_strength#{internal}"] = {
		default: -> 1
		getFunc: "DerpEyesStrength#{publicName}"
		min: PPM2.MIN_DERP_STRENGTH
		max: PPM2.MAX_DERP_STRENGTH
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_type#{internal}"] = {
		default: -> 0
		getFunc: "EyeType#{publicName}"
		enum: [arg for _, arg in ipairs PPM2.AvaliableEyeTypes]
	}

	PPM2.PonyDataRegistry["eye_reflection_type#{internal}"] = {
		default: -> 0
		getFunc: "EyeReflectionType#{publicName}"
		enum: [arg for _, arg in ipairs PPM2.AvaliableEyeReflections]
	}

	PPM2.PonyDataRegistry["hole_width#{internal}"] = {
		default: -> 1
		getFunc: "HoleWidth#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["hole_height#{internal}"] = {
		default: -> 1
		getFunc: "HoleHeight#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["iris_width#{internal}"] = {
		default: -> 1
		getFunc: "IrisWidth#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["iris_height#{internal}"] = {
		default: -> 1
		getFunc: "IrisHeight#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_glossy_reflection#{internal}"] = {
		default: -> 0.16
		getFunc: "EyeGlossyStrength#{publicName}"
		min: 0
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["hole_shiftx#{internal}"] = {
		default: -> 0
		getFunc: "HoleShiftX#{publicName}"
		min: PPM2.MIN_HOLE_SHIFT
		max: PPM2.MAX_HOLE_SHIFT
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["hole_shifty#{internal}"] = {
		default: -> 0
		getFunc: "HoleShiftY#{publicName}"
		min: PPM2.MIN_HOLE_SHIFT
		max: PPM2.MAX_HOLE_SHIFT
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_hole_size#{internal}"] = {
		default: -> .8
		getFunc: "HoleSize#{publicName}"
		min: PPM2.MIN_HOLE
		max: PPM2.MAX_HOLE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_rotation#{internal}"] = {
		default: -> 0
		getFunc: "EyeRotation#{publicName}"
		min: PPM2.MIN_EYE_ROTATION
		max: PPM2.MAX_EYE_ROTATION
		type: 'INT'
	}

for i = 1, 3
	PPM2.PonyDataRegistry["horn_url_#{i}"] = {
		default: -> ''
		getFunc: "HornURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["bat_wing_url_#{i}"] = {
		default: -> ''
		getFunc: "BatWingURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["bat_wing_skin_url_#{i}"] = {
		default: -> ''
		getFunc: "BatWingSkinURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["wings_url_#{i}"] = {
		default: -> ''
		getFunc: "WingsURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["horn_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "HornURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["bat_wing_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "BatWingURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["bat_wing_skin_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "BatWingSkinURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["wings_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "WingsURLColor#{i}"
		type: 'COLOR'
	}

for i = 1, 6
	PPM2.PonyDataRegistry["socks_detail_color_#{i}"] = {
		default: -> Color()
		getFunc: "SocksDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "ManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_detail_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "ManeDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "ManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_url_#{i}"] = {
		default: -> ''
		getFunc: "ManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["tail_detail_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "TailDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["tail_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "TailURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["tail_url_#{i}"] = {
		default: -> ''
		getFunc: "TailURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["tail_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "TailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "LowerManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "LowerManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_url_#{i}"] = {
		default: -> ''
		getFunc: "LowerManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["upper_mane_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "UpperManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "UpperManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_url_#{i}"] = {
		default: -> ''
		getFunc: "UpperManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["lower_mane_detail_color_#{i}"] = {
		default: -> Color()
		getFunc: "LowerManeDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_detail_color_#{i}"] = {
		default: -> Color()
		getFunc: "UpperManeDetailColor#{i}"
		type: 'COLOR'
	}

for i = 1, PPM2.MAX_BODY_DETAILS
	PPM2.PonyDataRegistry["body_detail_color_#{i}"] = {
		default: -> Color()
		getFunc: "BodyDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["body_detail_#{i}"] = {
		default: -> 0
		getFunc: "BodyDetail#{i}"
		enum: [arg for _, arg in ipairs PPM2.BodyDetailsEnum]
	}

	PPM2.PonyDataRegistry["body_detail_#{i}_first"] = {
		default: -> false
		getFunc: "BodyDetailFirst#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["body_url_detail_#{i}_first"] = {
		default: -> false
		getFunc: "BodyDetailURLFirst#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["body_detail_url_#{i}"] = {
		default: -> ''
		getFunc: "BodyDetailURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["body_detail_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "BodyDetailURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["body_detail_glow_#{i}"] = {
		default: -> false
		getFunc: "BodyDetailGlow#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["body_detail_glow_strength_#{i}"] = {
		default: -> 1
		getFunc: "BodyDetailGlowStrength#{i}"
		type: 'FLOAT'
		min: 0
		max: 1
	}

for i = 1, PPM2.MAX_TATTOOS
	PPM2.PonyDataRegistry["tattoo_type_#{i}"] = {
		default: -> 0
		getFunc: "TattooType#{i}"
		enum: [arg for _, arg in ipairs PPM2.TATTOOS_REGISTRY]
	}

	PPM2.PonyDataRegistry["tattoo_posx_#{i}"] = {
		default: -> 0
		getFunc: "TattooPosX#{i}"
		min: -100
		max: 100
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_posy_#{i}"] = {
		default: -> 0
		getFunc: "TattooPosY#{i}"
		fix: (arg = 0) -> math.Clamp(tonumber(arg) or 0, -100, 100)
		min: -100
		max: 100
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_rotate_#{i}"] = {
		default: -> 0
		getFunc: "TattooRotate#{i}"
		min: -180
		max: 180
		type: 'INT'
	}

	PPM2.PonyDataRegistry["tattoo_scalex_#{i}"] = {
		default: -> 1
		getFunc: "TattooScaleX#{i}"
		min: 0
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_glow_strength_#{i}"] = {
		default: -> 1
		getFunc: "TattooGlowStrength#{i}"
		min: 0
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_scaley_#{i}"] = {
		default: -> 1
		getFunc: "TattooScaleY#{i}"
		min: 0
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_glow_#{i}"] = {
		default: -> false
		getFunc: "TattooGlow#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["tattoo_over_detail_#{i}"] = {
		default: -> false
		getFunc: "TattooOverDetail#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["tattoo_color_#{i}"] = {
		default: -> Color()
		getFunc: "TattooColor#{i}"
		type: 'COLOR'
	}

for _, ttype in ipairs {'Body', 'Horn', 'Wings', 'BatWingsSkin', 'Socks', 'Mane', 'Tail', 'UpperMane', 'LowerMane', 'LEye', 'REye', 'BEyes', 'Eyelashes', 'Mouth', 'Teeth', 'Tongue'}
	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_exponent'] = {
		default: -> 3
		getFunc: ttype .. 'PhongExponent'
		min: 0.04
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_boost'] = {
		default: -> 0.09
		getFunc: ttype .. 'PhongBoost'
		min: 0.01
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_front'] = {
		default: -> 1
		getFunc: ttype .. 'PhongFront'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_middle'] = {
		default: -> 5
		getFunc: ttype .. 'PhongMiddle'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_sliding'] = {
		default: -> 10
		getFunc: ttype .. 'PhongSliding'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_tint'] = {
		default: -> Color(255, 200, 200)
		getFunc: ttype .. 'PhongTint'
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_lightwarp_texture'] = {
		default: -> 0
		getFunc: ttype .. 'Lightwarp'
		enum: [arg for _, arg in ipairs PPM2.AvaliableLightwarps]
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_lightwarp_texture_url'] = {
		default: -> ''
		getFunc: ttype .. 'LightwarpURL'
		type: 'URL'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_bumpmap_texture_url'] = {
		default: -> ''
		getFunc: ttype .. 'BumpmapURL'
		type: 'URL'
	}

for _, {:flex, :active} in ipairs PPM2.PonyFlexController.FLEX_LIST
	continue if not active
	PPM2.PonyDataRegistry["flex_disable_#{flex\lower()}"] = {
		default: -> false
		getFunc: "DisableFlex#{flex}"
		type: 'BOOLEAN'
	}

testMinimalBits = 0

for key, value in pairs PPM2.PonyDataRegistry
	if value.enum
		value.min = 0
		value.max = #value.enum - 1
		value.type = 'INT'

	switch value.type
		when 'INT'
			error("Variable #{key} has invalid minimal value (#{type(value.min)})") if type(value.min) ~= 'number'
			error("Variable #{max} has invalid maximal value (#{type(value.max)})") if type(value.max) ~= 'number'
			value.fix = INT_FIXER(value.default, value.min, value.max)
			if value.min >= 0
				selectBits = net.ChooseOptimalBits(value.max - value.min)
				testMinimalBits += selectBits
				value.read = rUInt(selectBits, value.min, value.max)
				value.write = wUInt(value.default(), selectBits)
			else
				selectBits = net.ChooseOptimalBits(math.abs(value.max - value.min))
				testMinimalBits += selectBits
				value.read = rInt(selectBits, value.min, value.max)
				value.write = wInt(value.default(), selectBits)
		when 'FLOAT'
			error("Variable #{key} has invalid minimal value (#{type(value.min)})") if type(value.min) ~= 'number'
			error("Variable #{max} has invalid maximal value (#{type(value.max)})") if type(value.max) ~= 'number'
			value.fix = FLOAT_FIXER(value.default, value.min, value.max)
			value.read = rFloat(value.min, value.max)
			value.write = (arg = value.default()) -> wFloat(arg)
			testMinimalBits += 32
		when 'URL'
			value.fix = URL_FIXER
			value.read = rURL
			value.write = wString
			testMinimalBits += 8
		when 'BOOLEAN'
			value.fix = (arg = value.default()) -> tobool(arg)
			value.read = rBool
			value.write = wBool
			testMinimalBits += 1
		when 'COLOR'
			{:r, :g, :b, :a} = value.default()
			value.fix = COLOR_FIXER(r, g, b, a)
			value.read = rColor
			value.write = wColor
			testMinimalBits += 32
		else
			error("Unknown variable type - #{value.type} for #{key}")

-- print('Minimal required bits - ' .. testMinimalBits)
PPM2.testMinimalBits = testMinimalBits

for key, value in pairs PPM2.PonyDataRegistry
	error("Data has no fix function: #{key}") if not value.fix

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

net = DLib.net

wUInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteUInt(arg, size)

wInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteInt(arg, size)

rUInt = (size = 8, min = 0, max = 255) ->
	return -> math.Clamp(net.ReadUInt(size), min, max)

rInt = (size = 8, min = -128, max = 127) ->
	return -> math.Clamp(net.ReadInt(size), min, max)

rFloat = (min = 0, max = 255) ->
	return -> math.Clamp(net.ReadDouble(), min, max)

wFloat = net.WriteDouble
rBool = net.ReadBool
wBool = net.WriteBool
rColor = net.ReadColor
wColor = net.WriteColor
rString = net.ReadString
wString = net.WriteString

COLOR_FIXER = (r = 255, g = 255, b = 255, a = 255) ->
	func = (arg = Color(r, g, b, a)) ->
		if not IsColor(arg)
			return Color()
		else
			{:r, :g, :b, :a} = arg
			if r and g and b and a
				return Color(r, g, b, a)
			else
				return Color()
	return func

URL_FIXER = (arg = '') ->
	arg = tostring(arg)
	if arg\find('^https?://')
		return arg
	else
		return ''

rURL = -> URL_FIXER(rString())

FLOAT_FIXER = (def = 1, min = 0, max = 1) ->
	defFunc = -> def if type(def) ~= 'function'
	defFunc = def if type(def) == 'function'
	return (arg = defFunc()) -> math.Clamp(tonumber(arg) or defFunc(), min, max)

INT_FIXER = (def = 1, min = 0, max = 1) ->
	defFunc = -> def if type(def) ~= 'function'
	defFunc = def if type(def) == 'function'
	return (arg = defFunc()) -> math.floor(math.Clamp(tonumber(arg) or defFunc(), min, max))

PPM2.PonyDataRegistry = {
	'age': {
		default: -> PPM2.AGE_ADULT
		getFunc: 'Age'
		enum: {'FILLY', 'ADULT', 'MATURE'}
	}

	'race': {
		default: -> PPM2.RACE_EARTH
		getFunc: 'Race'
		enum: [arg for _, arg in ipairs PPM2.RACE_ENUMS]
	}

	'wings_type': {
		default: -> 0
		getFunc: 'WingsType'
		enum: [arg for _, arg in ipairs PPM2.AvaliablePonyWings]
	}

	'gender': {
		default: -> PPM2.GENDER_FEMALE
		getFunc: 'Gender'
		enum: [arg for _, arg in ipairs PPM2.AGE_ENUMS]
	}

	'weight': {
		default: -> 1
		getFunc: 'Weight'
		min: PPM2.MIN_WEIGHT
		max: PPM2.MAX_WEIGHT
		type: 'FLOAT'
	}

	'ponysize': {
		default: -> 1
		getFunc: 'PonySize'
		min: PPM2.MIN_SCALE
		max: PPM2.MAX_SCALE
		type: 'FLOAT'
	}

	'necksize': {
		default: -> 1
		getFunc: 'NeckSize'
		min: PPM2.MIN_NECK
		max: PPM2.MAX_NECK
		type: 'FLOAT'
	}

	'legssize': {
		default: -> 1
		getFunc: 'LegsSize'
		min: PPM2.MIN_LEGS
		max: PPM2.MAX_LEGS
		type: 'FLOAT'
	}

	'spinesize': {
		default: -> 1
		getFunc: 'BackSize'
		min: PPM2.MIN_SPINE
		max: PPM2.MAX_SPINE
		type: 'FLOAT'
	}

	'male_buff': {
		default: -> PPM2.DEFAULT_MALE_BUFF
		getFunc: 'MaleBuff'
		min: PPM2.MIN_MALE_BUFF
		max: PPM2.MAX_MALE_BUFF
		type: 'FLOAT'
	}

	'clothes_head': {
		default: -> 0
		getFunc: 'HeadClothes'
		enum: [piece for piece in *PPM2.AvailableClothesHead]
	}

	'clothes_neck': {
		default: -> 0
		getFunc: 'NeckClothes'
		enum: [piece for piece in *PPM2.AvailableClothesNeck]
	}

	'clothes_body': {
		default: -> 0
		getFunc: 'BodyClothes'
		enum: [piece for piece in *PPM2.AvailableClothesBody]
	}

	'clothes_eye': {
		default: -> 0
		getFunc: 'EyeClothes'
		enum: [piece for piece in *PPM2.AvailableClothesEye]
	}

	'eyelash': {
		default: -> 0
		getFunc: 'EyelashType'
		enum: [arg for _, arg in ipairs PPM2.EyelashTypes]
	}

	'tail': {
		default: -> 2
		getFunc: 'TailType'
		enum: [arg for _, arg in ipairs PPM2.AvaliableTails]
	}

	'tail_new': {
		default: -> 2
		getFunc: 'TailTypeNew'
		enum: [arg for _, arg in ipairs PPM2.AvaliableTailsNew]
	}

	'mane': {
		default: -> 2
		getFunc: 'ManeType'
		enum: [arg for _, arg in ipairs PPM2.AvaliableUpperManes]
	}

	'mane_new': {
		default: -> 2
		getFunc: 'ManeTypeNew'
		enum: [arg for _, arg in ipairs PPM2.AvaliableUpperManesNew]
	}

	'manelower': {
		default: -> 2
		getFunc: 'ManeTypeLower'
		enum: [arg for _, arg in ipairs PPM2.AvaliableLowerManes]
	}

	'manelower_new': {
		default: -> 2
		getFunc: 'ManeTypeLowerNew'
		enum: [arg for _, arg in ipairs PPM2.AvaliableLowerManesNew]
	}

	'socks_texture': {
		default: -> 0
		getFunc: 'SocksTexture'
		enum: [arg for _, arg in ipairs PPM2.SocksTypes]
	}

	'socks_texture_url': {
		default: -> ''
		getFunc: 'SocksTextureURL'
		type: 'URL'
	}

	'tailsize': {
		default: -> 1
		getFunc: 'TailSize'
		min: PPM2.MIN_TAIL_SIZE
		max: PPM2.MAX_TAIL_SIZE
		type: 'FLOAT'
	}

	'cmark': {
		default: -> true
		getFunc: 'CMark'
		type: 'BOOLEAN'
	}

	'cmark_size': {
		default: -> 1
		getFunc: 'CMarkSize'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'cmark_color': {
		default: -> Color()
		getFunc: 'CMarkColor'
		type: 'COLOR'
	}

	'eyelash_color': {
		default: -> Color(0, 0, 0)
		getFunc: 'EyelashesColor'
		type: 'COLOR'
	}

	'eyelashes_phong_separate': {
		default: -> false
		getFunc: 'SeparateEyelashesPhong'
		type: 'BOOLEAN'
	}

	'fangs': {
		default: -> false
		getFunc: 'Fangs'
		type: 'BOOLEAN'
	}

	'bat_pony_ears': {
		default: -> false
		getFunc: 'BatPonyEars'
		type: 'BOOLEAN'
	}

	'claw_teeth': {
		default: -> false
		getFunc: 'ClawTeeth'
		type: 'BOOLEAN'
	}

	'cmark_type': {
		default: -> 4
		getFunc: 'CMarkType'
		enum: [arg for _, arg in ipairs PPM2.DefaultCutiemarks]
	}

	'cmark_url': {
		default: -> ''
		getFunc: 'CMarkURL'
		type: 'URL'
	}

	'body': {
		default: -> Color(0xEE321F)
		getFunc: 'BodyColor'
		type: 'COLOR'
	}

	'body_bump': {
		default: -> 0.5
		getFunc: 'BodyBumpStrength'
		type: 'FLOAT'
		min: 0
		max: 1
	}

	'eyebrows': {
		default: -> Color(0, 0, 0)
		getFunc: 'EyebrowsColor'
		type: 'COLOR'
	}

	'eyebrows_glow': {
		default: -> false
		getFunc: 'GlowingEyebrows'
		type: 'BOOLEAN'
	}

	'eyebrows_glow_strength': {
		default: -> 1
		getFunc: 'EyebrowsGlowStrength'
		type: 'FLOAT'
		min: 0
		max: 1
	}

	'hide_manes': {
		default: -> true
		getFunc: 'HideManes'
		type: 'BOOLEAN'
	}

	'hide_manes_socks': {
		default: -> true
		getFunc: 'HideManesSocks'
		type: 'BOOLEAN'
	}

	'hide_manes_mane': {
		default: -> true
		getFunc: 'HideManesMane'
		type: 'BOOLEAN'
	}

	'hide_manes_tail': {
		default: -> true
		getFunc: 'HideManesTail'
		type: 'BOOLEAN'
	}

	'new_horn': {
		default: -> false
		getFunc: 'UseNewHorn'
		type: 'BOOLEAN'
	}

	'new_horn_type': {
		default: -> 2
		getFunc: 'NewHornType'
		enum: [type for type in *PPM2.AvailableHorns]
	}

	'horn_color': {
		default: -> Color()
		getFunc: 'HornColor'
		type: 'COLOR'
	}

	'wings_color': {
		default: -> Color()
		getFunc: 'WingsColor'
		type: 'COLOR'
	}

	'separate_wings': {
		default: -> false
		getFunc: 'SeparateWings'
		type: 'BOOLEAN'
	}

	'separate_horn': {
		default: -> false
		getFunc: 'SeparateHorn'
		type: 'BOOLEAN'
	}

	'separate_magic_color': {
		default: -> false
		getFunc: 'SeparateMagicColor'
		type: 'BOOLEAN'
	}

	'horn_magic_color': {
		default: -> Color()
		getFunc: 'HornMagicColor'
		type: 'COLOR'
	}

	'horn_glow': {
		default: -> false
		getFunc: 'HornGlow'
		type: 'BOOLEAN'
	}

	'horn_glow_strength': {
		default: -> 1
		getFunc: 'HornGlowSrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'horn_detail_color': {
		default: -> Color(90, 90, 90)
		getFunc: 'HornDetailColor'
		type: 'COLOR'
	}

	'separate_eyes': {
		default: -> false
		getFunc: 'SeparateEyes'
		type: 'BOOLEAN'
	}

	'separate_mane': {
		default: -> false
		getFunc: 'SeparateMane'
		type: 'BOOLEAN'
	}

	'call_playerfootstep': {
		default: -> true
		getFunc: 'CallPlayerFootstepHook'
		type: 'BOOLEAN'
	}

	'disable_hoofsteps': {
		default: -> false
		getFunc: 'DisableHoofsteps'
		type: 'BOOLEAN'
	}

	'disable_wander_sounds': {
		default: -> false
		getFunc: 'DisableWanderSounds'
		type: 'BOOLEAN'
	}

	'disable_new_step_sounds': {
		default: -> false
		getFunc: 'DisableStepSounds'
		type: 'BOOLEAN'
	}

	'disable_jump_sound': {
		default: -> false
		getFunc: 'DisableJumpSound'
		type: 'BOOLEAN'
	}

	'disable_falldown_sound': {
		default: -> false
		getFunc: 'DisableFalldownSound'
		type: 'BOOLEAN'
	}

	'socks': {
		default: -> false
		getFunc: 'Socks'
		type: 'BOOLEAN'
	}

	'new_male_muzzle': {
		default: -> true
		getFunc: 'NewMuzzle'
		type: 'BOOLEAN'
	}

	'noflex': {
		default: -> false
		getFunc: 'NoFlex'
		type: 'BOOLEAN'
	}

	'socks_model': {
		default: -> false
		getFunc: 'SocksAsModel'
		type: 'BOOLEAN'
	}

	'socks_model_new': {
		default: -> false
		getFunc: 'SocksAsNewModel'
		type: 'BOOLEAN'
	}

	'socks_model_color': {
		default: -> Color()
		getFunc: 'SocksColor'
		type: 'COLOR'
	}

	'socks_new_model_color1': {
		default: -> Color()
		getFunc: 'NewSocksColor1'
		type: 'COLOR'
	}

	'socks_new_model_color2': {
		default: -> Color(0, 0, 0)
		getFunc: 'NewSocksColor2'
		type: 'COLOR'
	}

	'socks_new_model_color3': {
		default: -> Color(0, 0, 0)
		getFunc: 'NewSocksColor3'
		type: 'COLOR'
	}

	'socks_new_texture_url': {
		default: -> ''
		getFunc: 'NewSocksTextureURL'
		type: 'URL'
	}

	'suit': {
		default: -> 0
		getFunc: 'Bodysuit'
		enum: [arg for _, arg in ipairs PPM2.AvaliablePonySuits]
	}

	'left_wing_size': {
		default: -> 1
		getFunc: 'LWingSize'
		min: PPM2.MIN_WING
		max: PPM2.MAX_WING
		type: 'FLOAT'
	}

	'left_wing_x': {
		default: -> 0
		getFunc: 'LWingX'
		min: PPM2.MIN_WINGX
		max: PPM2.MAX_WINGX
		type: 'FLOAT'
	}

	'left_wing_y': {
		default: -> 0
		getFunc: 'LWingY'
		min: PPM2.MIN_WINGY
		max: PPM2.MAX_WINGY
		type: 'FLOAT'
	}

	'left_wing_z': {
		default: -> 0
		getFunc: 'LWingZ'
		min: PPM2.MIN_WINGZ
		max: PPM2.MAX_WINGZ
		type: 'FLOAT'
	}

	'right_wing_size': {
		default: -> 1
		getFunc: 'RWingSize'
		min: PPM2.MIN_WING
		max: PPM2.MAX_WING
		type: 'FLOAT'
	}

	'right_wing_x': {
		default: -> 0
		getFunc: 'RWingX'
		min: PPM2.MIN_WINGX
		max: PPM2.MAX_WINGX
		type: 'FLOAT'
	}

	'right_wing_y': {
		default: -> 0
		getFunc: 'RWingY'
		min: PPM2.MIN_WINGY
		max: PPM2.MAX_WINGY
		type: 'FLOAT'
	}

	'right_wing_z': {
		default: -> 0
		getFunc: 'RWingZ'
		min: PPM2.MIN_WINGZ
		max: PPM2.MAX_WINGZ
		type: 'FLOAT'
	}

	'teeth_color': {
		default: -> Color()
		getFunc: 'TeethColor'
		type: 'COLOR'
	}

	'mouth_color': {
		default: -> Color(219, 65, 155)
		getFunc: 'MouthColor'
		type: 'COLOR'
	}

	'tongue_color': {
		default: -> Color(235, 131, 59)
		getFunc: 'TongueColor'
		type: 'COLOR'
	}

	'bat_wing_color': {
		default: -> Color()
		getFunc: 'BatWingColor'
		type: 'COLOR'
	}

	'bat_wing_skin_color': {
		default: -> Color()
		getFunc: 'BatWingSkinColor'
		type: 'COLOR'
	}

	'separate_horn_phong': {
		default: -> false
		getFunc: 'SeparateHornPhong'
		type: 'BOOLEAN'
	}

	'separate_wings_phong': {
		default: -> false
		getFunc: 'SeparateWingsPhong'
		type: 'BOOLEAN'
	}

	'separate_mane_phong': {
		default: -> false
		getFunc: 'SeparateManePhong'
		type: 'BOOLEAN'
	}

	'separate_tail_phong': {
		default: -> false
		getFunc: 'SeparateTailPhong'
		type: 'BOOLEAN'
	}

	'alternative_fangs': {
		default: -> false
		getFunc: 'AlternativeFangs'
		type: 'BOOLEAN'
	}

	'hoof_fluffers': {
		default: -> false
		getFunc: 'HoofFluffers'
		type: 'BOOLEAN'
	}

	'hoof_fluffers_strength': {
		default: -> 1
		getFunc: 'HoofFluffersStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'ears_size': {
		default: -> 1
		getFunc: 'EarsSize'
		min: 0.1
		max: 2
		type: 'FLOAT'
	}

	'bat_pony_ears_strength': {
		default: -> 1
		getFunc: 'BatPonyEarsStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'fangs_strength': {
		default: -> 1
		getFunc: 'FangsStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'clawteeth_strength': {
		default: -> 1
		getFunc: 'ClawTeethStrength'
		min: 0
		max: 1
		type: 'FLOAT'
	}

	'lips_color_inherit': {
		default: -> true
		getFunc: 'LipsColorInherit'
		type: 'BOOLEAN'
	}

	'nose_color_inherit': {
		default: -> true
		getFunc: 'NoseColorInherit'
		type: 'BOOLEAN'
	}

	'lips_color': {
		default: -> Color(172, 92, 92)
		getFunc: 'LipsColor'
		type: 'COLOR'
	}

	'nose_color': {
		default: -> Color(77, 84, 83)
		getFunc: 'NoseColor'
		type: 'COLOR'
	}

	'weapon_hide': {
		default: -> true
		getFunc: 'HideWeapons'
		type: 'BOOLEAN'
	}
}

for {internal, publicName} in *{{'_head', 'Head'}, {'_neck', 'Neck'}, {'_body', 'Body'}, {'_eye', 'Eye'}}
	PPM2.PonyDataRegistry["clothes#{internal}_color_nil"] = {
		default: -> false
		getFunc: "#{publicName}ClothesUseColor"
		type: 'BOOLEAN'
	}

	for i = 1, PPM2.MAX_CLOTHES_URLS
		PPM2.PonyDataRegistry["clothes#{internal}_url_#{i}"] = {
			default: -> ''
			getFunc: "#{publicName}ClothesURL#{i}"
			type: 'URL'
		}

	for num = 1, PPM2.MAX_CLOTHES_COLORS
		PPM2.PonyDataRegistry["clothes#{internal}_color_#{num}"] = {
			default: -> Color()
			getFunc: "#{publicName}ClothesColor#{num}"
			type: 'COLOR'
		}

for _, {internal, publicName} in ipairs {{'_left', 'Left'}, {'_right', 'Right'}, {'', ''}}
	PPM2.PonyDataRegistry["eye_url#{internal}"] = {
		default: -> ''
		getFunc: "EyeURL#{publicName}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["eye_bg#{internal}"] = {
		default: -> Color()
		getFunc: "EyeBackground#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_hole#{internal}"] = {
		default: -> Color(0, 0, 0)
		getFunc: "EyeHole#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_iris1#{internal}"] = {
		default: -> Color(0xEE9620)
		getFunc: "EyeIrisTop#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_iris2#{internal}"] = {
		default: -> Color(0xEE9620)
		getFunc: "EyeIrisBottom#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_irisline1#{internal}"] = {
		default: -> Color()
		getFunc: "EyeIrisLine1#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_irisline_direction#{internal}"] = {
		default: -> false
		getFunc: "EyeLineDirection#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_irisline2#{internal}"] = {
		default: -> Color()
		getFunc: "EyeIrisLine2#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_reflection#{internal}"] = {
		default: -> Color(255, 255, 255, 127)
		getFunc: "EyeReflection#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_effect#{internal}"] = {
		default: -> Color()
		getFunc: "EyeEffect#{publicName}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["eye_lines#{internal}"] = {
		default: -> true
		getFunc: "EyeLines#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_iris_size#{internal}"] = {
		default: -> 1
		getFunc: "IrisSize#{publicName}"
		min: PPM2.MIN_IRIS
		max: PPM2.MAX_IRIS
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_derp#{internal}"] = {
		default: -> false
		getFunc: "DerpEyes#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_use_refract#{internal}"] = {
		default: -> false
		getFunc: "EyeRefract#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_cornera#{internal}"] = {
		default: -> false
		getFunc: "EyeCornerA#{publicName}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["eye_derp_strength#{internal}"] = {
		default: -> 1
		getFunc: "DerpEyesStrength#{publicName}"
		min: PPM2.MIN_DERP_STRENGTH
		max: PPM2.MAX_DERP_STRENGTH
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_type#{internal}"] = {
		default: -> 0
		getFunc: "EyeType#{publicName}"
		enum: [arg for _, arg in ipairs PPM2.AvaliableEyeTypes]
	}

	PPM2.PonyDataRegistry["eye_reflection_type#{internal}"] = {
		default: -> 0
		getFunc: "EyeReflectionType#{publicName}"
		enum: [arg for _, arg in ipairs PPM2.AvaliableEyeReflections]
	}

	PPM2.PonyDataRegistry["hole_width#{internal}"] = {
		default: -> 1
		getFunc: "HoleWidth#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["hole_height#{internal}"] = {
		default: -> 1
		getFunc: "HoleHeight#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["iris_width#{internal}"] = {
		default: -> 1
		getFunc: "IrisWidth#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["iris_height#{internal}"] = {
		default: -> 1
		getFunc: "IrisHeight#{publicName}"
		min: PPM2.MIN_PUPIL_SIZE
		max: PPM2.MAX_PUPIL_SIZE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_glossy_reflection#{internal}"] = {
		default: -> 0.16
		getFunc: "EyeGlossyStrength#{publicName}"
		min: 0
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["hole_shiftx#{internal}"] = {
		default: -> 0
		getFunc: "HoleShiftX#{publicName}"
		min: PPM2.MIN_HOLE_SHIFT
		max: PPM2.MAX_HOLE_SHIFT
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["hole_shifty#{internal}"] = {
		default: -> 0
		getFunc: "HoleShiftY#{publicName}"
		min: PPM2.MIN_HOLE_SHIFT
		max: PPM2.MAX_HOLE_SHIFT
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_hole_size#{internal}"] = {
		default: -> .8
		getFunc: "HoleSize#{publicName}"
		min: PPM2.MIN_HOLE
		max: PPM2.MAX_HOLE
		type: 'FLOAT'
		modifiers: true
	}

	PPM2.PonyDataRegistry["eye_rotation#{internal}"] = {
		default: -> 0
		getFunc: "EyeRotation#{publicName}"
		min: PPM2.MIN_EYE_ROTATION
		max: PPM2.MAX_EYE_ROTATION
		type: 'INT'
	}

for i = 1, 3
	PPM2.PonyDataRegistry["horn_url_#{i}"] = {
		default: -> ''
		getFunc: "HornURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["bat_wing_url_#{i}"] = {
		default: -> ''
		getFunc: "BatWingURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["bat_wing_skin_url_#{i}"] = {
		default: -> ''
		getFunc: "BatWingSkinURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["wings_url_#{i}"] = {
		default: -> ''
		getFunc: "WingsURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["horn_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "HornURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["bat_wing_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "BatWingURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["bat_wing_skin_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "BatWingSkinURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["wings_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "WingsURLColor#{i}"
		type: 'COLOR'
	}

for i = 1, 6
	PPM2.PonyDataRegistry["socks_detail_color_#{i}"] = {
		default: -> Color()
		getFunc: "SocksDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "ManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_detail_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "ManeDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "ManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["mane_url_#{i}"] = {
		default: -> ''
		getFunc: "ManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["tail_detail_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "TailDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["tail_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "TailURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["tail_url_#{i}"] = {
		default: -> ''
		getFunc: "TailURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["tail_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "TailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "LowerManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "LowerManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["lower_mane_url_#{i}"] = {
		default: -> ''
		getFunc: "LowerManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["upper_mane_color_#{i}"] = {
		default: -> i == 1 and Color(0xFFD117) or i == 2 and Color(0xE3C23C) or Color()
		getFunc: "UpperManeColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "UpperManeURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_url_#{i}"] = {
		default: -> ''
		getFunc: "UpperManeURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["lower_mane_detail_color_#{i}"] = {
		default: -> Color()
		getFunc: "LowerManeDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["upper_mane_detail_color_#{i}"] = {
		default: -> Color()
		getFunc: "UpperManeDetailColor#{i}"
		type: 'COLOR'
	}

for i = 1, PPM2.MAX_BODY_DETAILS
	PPM2.PonyDataRegistry["body_detail_color_#{i}"] = {
		default: -> Color()
		getFunc: "BodyDetailColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["body_detail_#{i}"] = {
		default: -> 0
		getFunc: "BodyDetail#{i}"
		enum: [arg for _, arg in ipairs PPM2.BodyDetailsEnum]
	}

	PPM2.PonyDataRegistry["body_detail_#{i}_first"] = {
		default: -> false
		getFunc: "BodyDetailFirst#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["body_url_detail_#{i}_first"] = {
		default: -> false
		getFunc: "BodyDetailURLFirst#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["body_detail_url_#{i}"] = {
		default: -> ''
		getFunc: "BodyDetailURL#{i}"
		type: 'URL'
	}

	PPM2.PonyDataRegistry["body_detail_url_color_#{i}"] = {
		default: -> Color()
		getFunc: "BodyDetailURLColor#{i}"
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry["body_detail_glow_#{i}"] = {
		default: -> false
		getFunc: "BodyDetailGlow#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["body_detail_glow_strength_#{i}"] = {
		default: -> 1
		getFunc: "BodyDetailGlowStrength#{i}"
		type: 'FLOAT'
		min: 0
		max: 1
	}

for i = 1, PPM2.MAX_TATTOOS
	PPM2.PonyDataRegistry["tattoo_type_#{i}"] = {
		default: -> 0
		getFunc: "TattooType#{i}"
		enum: [arg for _, arg in ipairs PPM2.TATTOOS_REGISTRY]
	}

	PPM2.PonyDataRegistry["tattoo_posx_#{i}"] = {
		default: -> 0
		getFunc: "TattooPosX#{i}"
		min: -100
		max: 100
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_posy_#{i}"] = {
		default: -> 0
		getFunc: "TattooPosY#{i}"
		fix: (arg = 0) -> math.Clamp(tonumber(arg) or 0, -100, 100)
		min: -100
		max: 100
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_rotate_#{i}"] = {
		default: -> 0
		getFunc: "TattooRotate#{i}"
		min: -180
		max: 180
		type: 'INT'
	}

	PPM2.PonyDataRegistry["tattoo_scalex_#{i}"] = {
		default: -> 1
		getFunc: "TattooScaleX#{i}"
		min: 0
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_glow_strength_#{i}"] = {
		default: -> 1
		getFunc: "TattooGlowStrength#{i}"
		min: 0
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_scaley_#{i}"] = {
		default: -> 1
		getFunc: "TattooScaleY#{i}"
		min: 0
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry["tattoo_glow_#{i}"] = {
		default: -> false
		getFunc: "TattooGlow#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["tattoo_over_detail_#{i}"] = {
		default: -> false
		getFunc: "TattooOverDetail#{i}"
		type: 'BOOLEAN'
	}

	PPM2.PonyDataRegistry["tattoo_color_#{i}"] = {
		default: -> Color()
		getFunc: "TattooColor#{i}"
		type: 'COLOR'
	}

for _, ttype in ipairs {'Body', 'Horn', 'Wings', 'BatWingsSkin', 'Socks', 'Mane', 'Tail', 'UpperMane', 'LowerMane', 'LEye', 'REye', 'BEyes', 'Eyelashes', 'Mouth', 'Teeth', 'Tongue'}
	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_exponent'] = {
		default: -> 3
		getFunc: ttype .. 'PhongExponent'
		min: 0.04
		max: 10
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_boost'] = {
		default: -> 0.09
		getFunc: ttype .. 'PhongBoost'
		min: 0.01
		max: 1
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_front'] = {
		default: -> 1
		getFunc: ttype .. 'PhongFront'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_middle'] = {
		default: -> 5
		getFunc: ttype .. 'PhongMiddle'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_sliding'] = {
		default: -> 10
		getFunc: ttype .. 'PhongSliding'
		min: 0
		max: 20
		type: 'FLOAT'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_phong_tint'] = {
		default: -> Color(255, 200, 200)
		getFunc: ttype .. 'PhongTint'
		type: 'COLOR'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_lightwarp_texture'] = {
		default: -> 0
		getFunc: ttype .. 'Lightwarp'
		enum: [arg for _, arg in ipairs PPM2.AvaliableLightwarps]
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_lightwarp_texture_url'] = {
		default: -> ''
		getFunc: ttype .. 'LightwarpURL'
		type: 'URL'
	}

	PPM2.PonyDataRegistry[ttype\lower() .. '_bumpmap_texture_url'] = {
		default: -> ''
		getFunc: ttype .. 'BumpmapURL'
		type: 'URL'
	}

for _, {:flex, :active} in ipairs PPM2.PonyFlexController.FLEX_LIST
	continue if not active
	PPM2.PonyDataRegistry["flex_disable_#{flex\lower()}"] = {
		default: -> false
		getFunc: "DisableFlex#{flex}"
		type: 'BOOLEAN'
	}

testMinimalBits = 0

for key, value in pairs PPM2.PonyDataRegistry
	if value.enum
		value.min = 0
		value.max = #value.enum - 1
		value.type = 'INT'

	switch value.type
		when 'INT'
			error("Variable #{key} has invalid minimal value (#{type(value.min)})") if type(value.min) ~= 'number'
			error("Variable #{max} has invalid maximal value (#{type(value.max)})") if type(value.max) ~= 'number'
			value.fix = INT_FIXER(value.default, value.min, value.max)
			if value.min >= 0
				selectBits = net.ChooseOptimalBits(value.max - value.min)
				testMinimalBits += selectBits
				value.read = rUInt(selectBits, value.min, value.max)
				value.write = wUInt(value.default(), selectBits)
			else
				selectBits = net.ChooseOptimalBits(math.max(math.abs(value.max), math.abs(value.min)))
				testMinimalBits += selectBits
				value.read = rInt(selectBits, value.min, value.max)
				value.write = wInt(value.default(), selectBits)
		when 'FLOAT'
			error("Variable #{key} has invalid minimal value (#{type(value.min)})") if type(value.min) ~= 'number'
			error("Variable #{max} has invalid maximal value (#{type(value.max)})") if type(value.max) ~= 'number'
			value.fix = FLOAT_FIXER(value.default, value.min, value.max)
			value.read = rFloat(value.min, value.max)
			value.write = (arg = value.default()) -> wFloat(arg)
			testMinimalBits += 32
		when 'URL'
			value.fix = URL_FIXER
			value.read = rURL
			value.write = wString
			testMinimalBits += 8
		when 'BOOLEAN'
			value.fix = (arg = value.default()) -> tobool(arg)
			value.read = rBool
			value.write = wBool
			testMinimalBits += 1
		when 'COLOR'
			{:r, :g, :b, :a} = value.default()
			value.fix = COLOR_FIXER(r, g, b, a)
			value.read = rColor
			value.write = wColor
			testMinimalBits += 32
		else
			error("Unknown variable type - #{value.type} for #{key}")

-- print('Minimal required bits - ' .. testMinimalBits)
PPM2.testMinimalBits = testMinimalBits

for key, value in pairs PPM2.PonyDataRegistry
	error("Data has no fix function: #{key}") if not value.fix


More constants

More constants


Update users.moon

Update users.moon

import Model from require "lapis.db.model"

class Users extends Model
  @table_name: => "lazuli_modules_user_management_users"

import Model from require "lapis.db.model"

class Users extends Model
  @table_name: => "lazuli_module_user_management_users"


remove trailing whitespace

remove trailing whitespace

import Widget from require "lapis.html"

class Layout extends Widget
  content: =>
    raw [[
<!DOCTYPE html>

    <meta charset="utf-8">
    <title>Photo Engine X Administration</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="Photo Engine X">
    <meta name="author" content="Tor Hveem">

    <link href='//fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
    <link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/static/fa/css/font-awesome.min.css" rel="stylesheet">
    <link href="/static/screen.css" rel="stylesheet">

    <link rel="shortcut icon" type="image/png" href="/static/favicon.png">
    <link rel="icon" type="image/png" href="/static/favicon.png">

{{ error }}

      Log in

Photo Engine X Administration

                {{ images.nrofimages }} images in {{ images.albums.length }} albums.   images in processing queue

New album

                            <form class="form form-horizontal uploadform" ng-submit="submitNewAlbum()" class="form-inline">

                                    <input id="albumname" text="text" class="form-control" ng-model="albumname" name="album" placeholder="Name">

                                      <button type="submit" class="btn btn-primary btn-mini"></button>

                            </form>

Filter albums by name

                          <form class="form form-horizontal" class="form-inline">
                              <input id="albumsearch" text="text" class="form-control" ng-model="albumsearch" name="albumsearch" placeholder="Search">
                          </form>

Albums

 Loading all the things...

                                {{ images.imagesarray[album].length }}

                                {{ album }}

Log out

Welcome to the Admin pages

                        Here you can

Create new albums

Modify existing albums (delete pictures, etc)

Delete albums

Create new display links for guests

                        Please select an album to start.

                                 {{ album }}

Upload images to the album {{albumname}}

Alternate upload

Drop files here to upload

Hide alternate upload

                        <form action="" method="post" enctype="multipart/form-data">

                            <input class="input-file" type="file" id="fileField" name="fileField" multiple value="Browse..."/>

                        </form>

                        Upload files
                        Clear list

                          tag
                          created
                          expires
                          album direct link
                          album portal link

                          admin

                          /album/{{ images.tags[selectedAlbum]}}/{{selectedAlbum}}/

                            {{ key}}
                            {{ val[0] }}
                            {{ val[1] }}
                            /album/{{ key }}/{{selectedAlbum}}/
                              /albums/{{ key }}/

                                  {{ img }}

    <form id="form-ttl" method="GET" class="form-horizontal" ng-submit="submitAlbumLink()">

        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>

Create expirable album link

Create a link that expires after a time

                Time to live

                    <select name="ttl" class="input-xlarge" id="ttl">
                        <option value="3600"
                        >One hour</option>
                        <option value="86400"
                        selected="selected"
                        >One day (default)</option>
                        <option value="604800"
                        >One week</option>
                        <option value="18748800"
                        >One month</option>
                        <option value="112492800"
                        >Six months</option>
                        <option value="31536000"
                        >One year</option>
                        <option value="forever"
                        >Forever</option>
                    </select>

The link will automatically be inaccessible if time to live is exceeded.

                Name the link

                    <input type="text" name="name" placeholder="Name">

The name will be used in the link.

            <input id="input-album-name" type="hidden" name="album"></input>

        <button type="submit" class="btn btn-primary">Create link

    </form>

        © 2012-2013 Tor Hveem

Loading ...

]]


layout: add <br> after anchor to main page

html = require "lapis.html"

class extends html.Widget
  content: =>
    html_5 ->
      head ->
        meta charset: 'utf-8'
        link
          rel: "icon", type: "image/x-icon",
          href: "/favicon.ico"
        title @title or @_ "Kodomo Quiz"
      body ->
        h1 class: "header", @_("Kodomo Quiz")
        a href: @url_for('index'), ->
          text @_ "Main page"
        raw '<br/>'
        @content_for "inner"

html = require "lapis.html"

class extends html.Widget
  content: =>
    html_5 ->
      head ->
        meta charset: 'utf-8'
        link
          rel: "icon", type: "image/x-icon",
          href: "/favicon.ico"
        title @title or @_ "Kodomo Quiz"
      body ->
        h1 class: "header", @_("Kodomo Quiz")
        a href: @url_for('index'), ->
          text @_ "Main page"
        @content_for "inner"


rss: use abort to avoid autoincrement

rss: use abort to avoid autoincrement

Former-commit-id: 909cc4b519ab7077b59f11e2184ccecac6c90fe1 [formerly c503366fa75b020ad921e2873cb602e27d051431]
Former-commit-id: 3c25ace59f8c6b56b8c08bd0d6aecb1648441cce

-- Module for subscribing to RSS and announce to channels
feedparser = require 'feedparser' -- http://feedparser.luaforge.net/
{:simplehttp, :json, :urlEncode, :bold} = require'util'
html2unicode = require 'html'
sql = require'lsqlite3'

moduleName = 'rss'

get_db = ->
  db = sql.open 'cache/rss.sql'

  code = db\exec [[
    CREATE TABLE IF NOT EXISTS feed (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT,
      rssurl VARCHAR(1024) NOT NULL,
      url VARCHAR(1024),
      title VARCHAR(1024),
      author VARCHAR(1024),
      etag TEXT,
      lastmodified TEXT
    );

    CREATE TABLE IF NOT EXISTS item (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      feed_id INTEGER NOT NULL,
      guid VARCHAR(64),
      title VARCHAR(1024),
      author VARCHAR(1024),
      url VARCHAR(1024),
      feedurl VARCHAR(1024),
      pubDate TEXT,
      summary TEXT,
      content TEXT,
      FOREIGN KEY(feed_id) REFERENCES feed(id),
      UNIQUE (guid, feed_id)
    );

    CREATE TABLE IF NOT EXISTS subscription (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      destination varchar(100),
      feed_id INTEGER,
      last INTEGER DEFAULT 0,
      created_at timestamp DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY(feed_id) REFERENCES feed(id)
    );
    ]]

  if code != sql.OK
    ivar2\Log 'error', "Error during opendb: #{db\errmsg!}"
    return nil

  return db

prepare = (db, stmt, ...) ->
  code = db\prepare stmt
  unless code
    ivar2\Log 'error', "Error with statement prepare: #{db\errmsg!}"
    return nil
  code\bind_values ...
  return code

-- Function that add functionality to extract specific information for specific sites
feedSpecific = (feedurl, link, title, summary, content) ->
  if feedurl\match 'https://comics.io/my/'

    title = summary\match '
(.-)
' or ''
    img, alt = summary\match '
'
    return "#{html2unicode title} #{img} #{html2unicode alt}"

  return "#{title} - #{link}"

announce = (id) ->
  db = get_db!
  stmt = prepare db, "SELECT DISTINCT feed.id, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id WHERE feed.id = ?", id
  for row in stmt\rows!
    destination = row[2]

    highest = 0
    -- Check last value
    stmt = prepare db, "SELECT last FROM subscription WHERE feed_id = ? and destination = ?", id, destination
    stmt\step!
    value = stmt\get_values()[1]
    if value and value ~= '' and tonumber(value) and tonumber(value) > 0
      highest = value

    ivar2\Log 'debug', 'Got highest %s, %s, %s', id, destination, highest

    stmt = prepare db, "SELECT feed.id AS feed_id, item.id AS item_id, rssurl, pubDate, name, etag, item.url AS link, item.title, content, summary FROM feed JOIN item on item.feed_id = feed.id WHERE feed.id = ? AND item.id > ? ORDER BY item.id DESC LIMIT 100", id, highest
    out = {}
    for item in stmt\nrows!
      with item
        if .item_id > highest
          highest = .item_id
          table.insert out, "[#{bold .name}]: #{feedSpecific(.rssurl, .link, .title, .summary, .content)}"
    if #out > 0
      ivar2\Msg 'privmsg', destination, ivar2.nick, "RSS "..table.concat(out, ', ')
    -- Update last red
    ins = prepare db, 'UPDATE subscription SET last=? WHERE feed_id = ? and destination = ?', highest, id, destination
    code, err = ins\step!
    code, err = ins\finalize!
  db\close!

poll = ->
  db = get_db!
  channels = {}
  for c,_ in pairs(ivar2.channels)
    channels[c] = true
  for row in db\rows "SELECT DISTINCT feed.id, rssurl, lastmodified, name, etag, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id"
  -- Ignore feeds this particular instance of the bot does not subscribe to
    if not channels[row[6]]
      continue
    id = row[1]
    rssurl = row[2]
    lm = row[3]
    name = row[4]
    etag = row[5]
    headers = {}
    if lm and lm != '' then
      headers['If-Modified-Since'] = lm
    if etag and etag != '' then
      headers['If-None-Match'] = etag

    simplehttp {url:rssurl,headers:headers}, (data, url, response) ->
      -- Reopenn DB because of async
      sdb = get_db!

      lastModified = response.headers['Last-Modified']
      if not lastModified
        lastModified = response.headers['Date']

      -- Unmodified content
      if response.status_code == 304
        -- return from the simplehttp callback
        return

      feed, err = feedparser.parse data
      if err
        ivar2\Log 'error', "#{moduleName}: Error during parsing: <#{err}> data for feed: <#{name}> with URL <#{url}>"
      else
        title = feed.title
        author = feed.author
        url = feed.link

        -- Update feed with new values
        ins = sdb\prepare 'UPDATE feed SET title=?, author=?, url=?, lastmodified=? WHERE id=?'
        code, err = ins\bind_values title, author, url, lastModified, id
        code, err = ins\step!
        code, err = ins\finalize!

        out = {}
        for i, e in ipairs(feed.entries)
          -- Attempt to get a unique entry ID
          guid = e.guid
          if not guid then guid = e.id
          if not guid then guid = e.link
          if not guid
            ivar2\Log 'error', "#{moduleName}: No GUID when parsing entry: <#{e}> data for feed: <#{name}> with URL <#{url}>"
            break

          --if guid == last
          --  break
          ins = sdb\prepare [[
            INSERT OR ABORT INTO
              item(feed_id, guid, title, author, url, pubDate, content, summary)
              VALUES (?, ?, ?, ?, ?, ?, ?, ?)
          ]]
          if not ins then
            ivar2\Log 'error', "#{moduleName}: Error during inserting entry: <#{err}> feed: <#{name}> with URL <#{url}> and position: #{i}"
          else
            code = ins\bind_values id, guid, e.title, e.author, e.link, e.updated, e.content, e.summary
            code = ins\step!
            code = ins\finalize!
        announce(id)
      sdb\close!
  db\close!

subscribe = (source, destination, name, url) =>
  db = get_db!
  ins = db\prepare "INSERT INTO feed (name, rssurl) VALUES(?, ?)"
  code = ins\bind_values name, url
  code = ins\step!
  code = ins\finalize!
  ins = db\prepare "INSERT INTO subscription (feed_id, destination) VALUES(?, ?)"
  code = ins\bind_values db\last_insert_rowid!, destination
  code = ins\step!
  code = ins\finalize!
  db\close!
  reply "Ok. Subscribed to #{bold name}"
  poll!

unsubscribe = (source, destination, name) =>
  db = get_db!
  ins = db\prepare "DELETE FROM subscription WHERE feed_id IN (select id from feed where name = ?) and destination = ?"
  code = ins\bind_values name, destination
  code = ins\step!
  code = ins\finalize!
  total_changes = db\total_changes!
  db\close!
  if total_changes < 1
    reply "Wasn't subscribed. But, sure."
  else
    reply "Ok. Stopped caring about #{bold name}"

list = (source, destination) =>
  out = {}
  db = get_db!
  stmt = db\prepare("SELECT name FROM feed JOIN subscription ON feed.id = subscription.feed_id WHERE destination = ?")
  stmt\bind_values destination
  for row in stmt\rows!
    out[#out+1] = row[1]
  if #out > 0 then
    reply "Subscribed to: #{table.concat(out, ', ')}"
  else
    reply "Not subscribed to any feeds."

  db\close!

-- Start the subscribe poller
ivar2\Timer(moduleName, 60*5, 60*5, poll)

PRIVMSG:
  '^%prss latest (.*)': getLatest
  '^%prss subscribe (.-) (.*)': subscribe
  '^%prss unsubscribe (.*)': unsubscribe
  '^%prss list': list
  -- for debugging
  '^%prss poll': poll

-- Module for subscribing to RSS and announce to channels
feedparser = require 'feedparser' -- http://feedparser.luaforge.net/
{:simplehttp, :json, :urlEncode, :bold} = require'util'
html2unicode = require 'html'
sql = require'lsqlite3'

moduleName = 'rss'

get_db = ->
  db = sql.open 'cache/rss.sql'

  code = db\exec [[
    CREATE TABLE IF NOT EXISTS feed (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT,
      rssurl VARCHAR(1024) NOT NULL,
      url VARCHAR(1024),
      title VARCHAR(1024),
      author VARCHAR(1024),
      etag TEXT,
      lastmodified TEXT
    );

    CREATE TABLE IF NOT EXISTS item (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      feed_id INTEGER NOT NULL,
      guid VARCHAR(64),
      title VARCHAR(1024),
      author VARCHAR(1024),
      url VARCHAR(1024),
      feedurl VARCHAR(1024),
      pubDate TEXT,
      summary TEXT,
      content TEXT,
      FOREIGN KEY(feed_id) REFERENCES feed(id),
      UNIQUE (guid, feed_id)
    );

    CREATE TABLE IF NOT EXISTS subscription (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      destination varchar(100),
      feed_id INTEGER,
      last INTEGER DEFAULT 0,
      created_at timestamp DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY(feed_id) REFERENCES feed(id)
    );
    ]]

  if code != sql.OK
    ivar2\Log 'error', "Error during opendb: #{db\errmsg!}"
    return nil

  return db

prepare = (db, stmt, ...) ->
  code = db\prepare stmt
  unless code
    ivar2\Log 'error', "Error with statement prepare: #{db\errmsg!}"
    return nil
  code\bind_values ...
  return code

-- Function that add functionality to extract specific information for specific sites
feedSpecific = (feedurl, link, title, summary, content) ->
  if feedurl\match 'https://comics.io/my/'

    title = summary\match '
(.-)
' or ''
    img, alt = summary\match '
'
    return "#{html2unicode title} #{img} #{html2unicode alt}"

  return "#{title} - #{link}"

announce = (id) ->
  db = get_db!
  stmt = prepare db, "SELECT DISTINCT feed.id, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id WHERE feed.id = ?", id
  for row in stmt\rows!
    destination = row[2]

    highest = 0
    -- Check last value
    stmt = prepare db, "SELECT last FROM subscription WHERE feed_id = ? and destination = ?", id, destination
    stmt\step!
    value = stmt\get_values()[1]
    if value and value ~= '' and tonumber(value) and tonumber(value) > 0
      highest = value

    ivar2\Log 'debug', 'Got highest %s, %s, %s', id, destination, highest

    stmt = prepare db, "SELECT feed.id AS feed_id, item.id AS item_id, rssurl, pubDate, name, etag, item.url AS link, item.title, content, summary FROM feed JOIN item on item.feed_id = feed.id WHERE feed.id = ? AND item.id > ? ORDER BY item.id DESC LIMIT 100", id, highest
    out = {}
    for item in stmt\nrows!
      with item
        if .item_id > highest
          highest = .item_id
          table.insert out, "[#{bold .name}]: #{feedSpecific(.rssurl, .link, .title, .summary, .content)}"
    if #out > 0
      ivar2\Msg 'privmsg', destination, ivar2.nick, "RSS "..table.concat(out, ', ')
    -- Update last red
    ins = prepare db, 'UPDATE subscription SET last=? WHERE feed_id = ? and destination = ?', highest, id, destination
    code, err = ins\step!
    code, err = ins\finalize!
  db\close!

poll = ->
  db = get_db!
  channels = {}
  for c,_ in pairs(ivar2.channels)
    channels[c] = true
  for row in db\rows "SELECT DISTINCT feed.id, rssurl, lastmodified, name, etag, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id"
  -- Ignore feeds this particular instance of the bot does not subscribe to
    if not channels[row[6]]
      continue
    id = row[1]
    rssurl = row[2]
    lm = row[3]
    name = row[4]
    etag = row[5]
    headers = {}
    if lm and lm != '' then
      headers['If-Modified-Since'] = lm
    if etag and etag != '' then
      headers['If-None-Match'] = etag

    simplehttp {url:rssurl,headers:headers}, (data, url, response) ->
      -- Reopenn DB because of async
      sdb = get_db!

      lastModified = response.headers['Last-Modified']
      if not lastModified
        lastModified = response.headers['Date']

      -- Unmodified content
      if response.status_code == 304
        -- return from the simplehttp callback
        return

      feed, err = feedparser.parse data
      if err
        ivar2\Log 'error', "#{moduleName}: Error during parsing: <#{err}> data for feed: <#{name}> with URL <#{url}>"
      else
        title = feed.title
        author = feed.author
        url = feed.link

        -- Update feed with new values
        ins = sdb\prepare 'UPDATE feed SET title=?, author=?, url=?, lastmodified=? WHERE id=?'
        code, err = ins\bind_values title, author, url, lastModified, id
        code, err = ins\step!
        code, err = ins\finalize!

        out = {}
        for i, e in ipairs(feed.entries)
          -- Attempt to get a unique entry ID
          guid = e.guid
          if not guid then guid = e.id
          if not guid then guid = e.link
          if not guid
            ivar2\Log 'error', "#{moduleName}: No GUID when parsing entry: <#{e}> data for feed: <#{name}> with URL <#{url}>"
            break

          --if guid == last
          --  break
          ins = sdb\prepare [[
            INSERT OR IGNORE INTO
              item(feed_id, guid, title, author, url, pubDate, content, summary)
              VALUES (?, ?, ?, ?, ?, ?, ?, ?)
          ]]
          if not ins then
            ivar2\Log 'error', "#{moduleName}: Error during inserting entry: <#{err}> feed: <#{name}> with URL <#{url}> and position: #{i}"
          else
            code = ins\bind_values id, guid, e.title, e.author, e.link, e.updated, e.content, e.summary
            code = ins\step!
            code = ins\finalize!
        announce(id)
      sdb\close!
  db\close!

subscribe = (source, destination, name, url) =>
  db = get_db!
  ins = db\prepare "INSERT INTO feed (name, rssurl) VALUES(?, ?)"
  code = ins\bind_values name, url
  code = ins\step!
  code = ins\finalize!
  ins = db\prepare "INSERT INTO subscription (feed_id, destination) VALUES(?, ?)"
  code = ins\bind_values db\last_insert_rowid!, destination
  code = ins\step!
  code = ins\finalize!
  db\close!
  reply "Ok. Subscribed to #{bold name}"
  poll!

unsubscribe = (source, destination, name) =>
  db = get_db!
  ins = db\prepare "DELETE FROM subscription WHERE feed_id IN (select id from feed where name = ?) and destination = ?"
  code = ins\bind_values name, destination
  code = ins\step!
  code = ins\finalize!
  total_changes = db\total_changes!
  db\close!
  if total_changes < 1
    reply "Wasn't subscribed. But, sure."
  else
    reply "Ok. Stopped caring about #{bold name}"

list = (source, destination) =>
  out = {}
  db = get_db!
  stmt = db\prepare("SELECT name FROM feed JOIN subscription ON feed.id = subscription.feed_id WHERE destination = ?")
  stmt\bind_values destination
  for row in stmt\rows!
    out[#out+1] = row[1]
  if #out > 0 then
    reply "Subscribed to: #{table.concat(out, ', ')}"
  else
    reply "Not subscribed to any feeds."

  db\close!

-- Start the subscribe poller
ivar2\Timer(moduleName, 60*5, 60*5, poll)

PRIVMSG:
  '^%prss latest (.*)': getLatest
  '^%prss subscribe (.-) (.*)': subscribe
  '^%prss unsubscribe (.*)': unsubscribe
  '^%prss list': list
  -- for debugging
  '^%prss poll': poll


utf8: combining underline

utf8: combining underline

Former-commit-id: 106e30470fc293a0c1ee6afd8fda31a91d0c9194
Former-commit-id: 8488a0288b97220dfcac11f986fa2108baa1816b

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

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

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

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

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

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

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

    table.concat(t, "")

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

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

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

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

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


readded steps for top level exposure

readded steps for top level exposure

return (path) ->
    return {
        steps: (require path .. '/sha3/sponge/steps')(path)
        sponge: (require path .. '/sha3/sponge')(path)
        keccakf1600: (require path .. '/sha3/keccakf1600')(path)
        utils: (require path .. '/sha3/utils')(path)
        sha224: (require path .. '/sha3/sha224')(path)
        sha256: (require path .. '/sha3/sha256')(path)
        sha384: (require path .. '/sha3/sha384')(path)
        sha512: (require path .. '/sha3/sha512')(path)
    }

return (path) ->
    return {
        sponge: (require path .. '/sha3/sponge')(path)
        keccakf1600: (require path .. '/sha3/keccakf1600')(path)
        utils: (require path .. '/sha3/utils')(path)
        sha224: (require path .. '/sha3/sha224')(path)
        sha256: (require path .. '/sha3/sha256')(path)
        sha384: (require path .. '/sha3/sha384')(path)
        sha512: (require path .. '/sha3/sha512')(path)
    }


don't show unknown messages because it's valid to set built in events to nil

cjson = require"cjson"

ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=[];',./!@#$%^&*()_+{}|:\"<>?\\`~ "
DEFAULT_FONT = "12px sans-serif"

-- disable invalid functions
os.remove = -> error "os.remove is disabled"
os.rename = -> error "os.rename is disabled"
os.execute = -> error "os.execute is disabled"
os.exit = -> error "os.exit is disabled"
io.tmpfile = -> error "io.tmpfile is disabled"

post_message = (o) ->
  o = cjson.encode o if type(o) == "table"
  nacl.post_message o

printer = (channel="std_out") ->
  (...) ->
    args = {...}
    str = table.concat [tostring s for s in *args], "\t"
    post_message { channel, str }

async_print = printer"std_out"
async_err = printer"std_err"

msg_id = 0
msg_responses = {}

request_response = (msg) ->
  id = msg_id
  msg_id += 1
  post_message { "request", id, msg }
  while true
    msg = coroutine.yield!
    if msg == "response" and msg_responses[id]
      return with msg_responses[id]
        msg_responses[id] = nil

safe_resume = (co, ...) ->
  success, error_msg = coroutine.resume co, ...
  if not success
    async_err debug.traceback co, error_msg
    nacl.set_game_thread nil
    false
  else
    true

async_require = (module_name) ->
  mod = package.loaded[module_name]
  return mod if mod

  loader = if package.preload[module_name]
    package.preload[module_name]
  else
    msg = request_response { "require", module_name }
    status, code = unpack msg
    return async_err code if status == "error"
    assert loadstring code, module_name

  setfenv loader, getfenv 2
  mod = loader module_name
  mod = true if mod == nil
  package.loaded[module_name] = mod
  mod

nacl.lib = {
  core: nacl
  prefetch: (files) ->
    tuples = {}

    add_files = (list, t="text") ->
      for k, file_or_list in pairs list
        if type(file_or_list) == "table"
          add_files file_or_list, k
        else
          table.insert tuples, { t, file_or_list }

    add_files files

    return if #tuples == 0
    status, msg = unpack request_response { "prefetch", tuples }
    error "prefetch: " .. msg if status != "success"

  track_event: (category, action, label=nil, value=nil, interactive=true) ->
    post_message { "track_event", { category, action, label, value, interactive } }
}

-- scope of the game thread
async_scope = setmetatable {
    print: async_print
    require: async_require
    nacl: nacl.lib
  }, {
    __index: _G
  }

game_thread = nil -- to prevent it from being garbage collected

nacl.show_error = async_err -- used by C to send errors to client

nacl.handle_message = (msg) ->
  error "unknown msg: " .. tostring(msg) if type(msg) != "string"
  print ">>", msg\sub 1, 120

  msg = cjson.decode msg

  switch msg[1]
    -- create a new execution thread
    -- throws out the old one
    when "execute"
      fn, err = loadstring msg[2], "execute"
      if not fn
        async_err err
      else
        setfenv fn, async_scope
        run = -> aroma.run fn

        game_thread = coroutine.create run
        nacl.set_game_thread game_thread
        safe_resume game_thread

    when "response"
      _, id, data = unpack msg
      msg_responses[id] = data
      safe_resume game_thread, "response"
    else
      error "Don't know how to handle message: " .. (msg[1] or msg)

img_cache = {}

image_data_from_url = (url) ->
  msg = request_response { "image", url }
  status, bytes, width, height = unpack msg
  if status == "success"
    nacl.image_data_from_byte_string bytes, width, height
  else
    error bytes

class AudioSource
  new: (@source_id, @url) =>
  msg: (...) => post_message { "audio", @source_id, ... }

  play: => @msg "play"
  stop: => @msg "stop"
  rewind: => @msg "rewind"
  setLooping: (should_loop) => @msg "set_looping", should_loop

  __tostring: => "HTMLAudioSource<" .. @url .. ">"

nacl.init = {
  graphics: =>
    old_new_image = @newImage

    @newImage = (url, ...) ->
      return old_new_image url, ... if type(url) != "string"
      return img_cache[url] if img_cache[url]

      img_data = image_data_from_url url
      img = old_new_image img_data
      img_cache[url] = img
      img

    @newFont = (font_str, alphabet=ALPHABET) ->
      glyphs = request_response { "font", font_str, alphabet }
      cache = aroma.font.newGlyphCache!
      for g in *glyphs
        letter, data_string, w, h = unpack g
        data = nacl.image_data_from_byte_string data_string, w, h
        cache\add_glyph letter\byte!, data
      cache\to_font!

    aroma.boot = ->
      if not self.getFont!
        self.setFont self.newFont DEFAULT_FONT

  audio: =>
    @newSource = (url, kind="static") ->
      msg = request_response { "audio", url, kind }
      status, source_id, err_msg = unpack msg
      if status == "success"
        AudioSource source_id, url
      else
        error error_msg

  image: =>
    old_new_image_data = @newImageData
    @newImageData = (...) ->
      args = {...}
      old_new_image_data ... if type(args[1]) != "string"
      image_data_from_url args[1]
}

functions_to_reset = {"draw", "update", "keypressed", "keyreleased", "focus", "load"}

nacl.init_all = (aroma) ->
  aroma.run = (setup_fn) ->
    blank = ->
    aroma[key] = blank for key in *functions_to_reset
    aroma.graphics.reset!
    aroma.boot!

    setup_fn!

    while true -- listen for game messages
      msg = {coroutine.yield!}
      if aroma[msg[1]]
        name = table.remove msg, 1
        aroma[name] unpack msg
      -- else
      --   print "unknown message"
      --   print unpack msg

cjson = require"cjson"

ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=[];',./!@#$%^&*()_+{}|:\"<>?\\`~ "
DEFAULT_FONT = "12px sans-serif"

-- disable invalid functions
os.remove = -> error "os.remove is disabled"
os.rename = -> error "os.rename is disabled"
os.execute = -> error "os.execute is disabled"
os.exit = -> error "os.exit is disabled"
io.tmpfile = -> error "io.tmpfile is disabled"

post_message = (o) ->
  o = cjson.encode o if type(o) == "table"
  nacl.post_message o

printer = (channel="std_out") ->
  (...) ->
    args = {...}
    str = table.concat [tostring s for s in *args], "\t"
    post_message { channel, str }

async_print = printer"std_out"
async_err = printer"std_err"

msg_id = 0
msg_responses = {}

request_response = (msg) ->
  id = msg_id
  msg_id += 1
  post_message { "request", id, msg }
  while true
    msg = coroutine.yield!
    if msg == "response" and msg_responses[id]
      return with msg_responses[id]
        msg_responses[id] = nil

safe_resume = (co, ...) ->
  success, error_msg = coroutine.resume co, ...
  if not success
    async_err debug.traceback co, error_msg
    nacl.set_game_thread nil
    false
  else
    true

async_require = (module_name) ->
  mod = package.loaded[module_name]
  return mod if mod

  loader = if package.preload[module_name]
    package.preload[module_name]
  else
    msg = request_response { "require", module_name }
    status, code = unpack msg
    return async_err code if status == "error"
    assert loadstring code, module_name

  setfenv loader, getfenv 2
  mod = loader module_name
  mod = true if mod == nil
  package.loaded[module_name] = mod
  mod

nacl.lib = {
  core: nacl
  prefetch: (files) ->
    tuples = {}

    add_files = (list, t="text") ->
      for k, file_or_list in pairs list
        if type(file_or_list) == "table"
          add_files file_or_list, k
        else
          table.insert tuples, { t, file_or_list }

    add_files files

    return if #tuples == 0
    status, msg = unpack request_response { "prefetch", tuples }
    error "prefetch: " .. msg if status != "success"

  track_event: (category, action, label=nil, value=nil, interactive=true) ->
    post_message { "track_event", { category, action, label, value, interactive } }
}

-- scope of the game thread
async_scope = setmetatable {
    print: async_print
    require: async_require
    nacl: nacl.lib
  }, {
    __index: _G
  }

game_thread = nil -- to prevent it from being garbage collected

nacl.show_error = async_err -- used by C to send errors to client

nacl.handle_message = (msg) ->
  error "unknown msg: " .. tostring(msg) if type(msg) != "string"
  print ">>", msg\sub 1, 120

  msg = cjson.decode msg

  switch msg[1]
    -- create a new execution thread
    -- throws out the old one
    when "execute"
      fn, err = loadstring msg[2], "execute"
      if not fn
        async_err err
      else
        setfenv fn, async_scope
        run = -> aroma.run fn

        game_thread = coroutine.create run
        nacl.set_game_thread game_thread
        safe_resume game_thread

    when "response"
      _, id, data = unpack msg
      msg_responses[id] = data
      safe_resume game_thread, "response"
    else
      error "Don't know how to handle message: " .. (msg[1] or msg)

img_cache = {}

image_data_from_url = (url) ->
  msg = request_response { "image", url }
  status, bytes, width, height = unpack msg
  if status == "success"
    nacl.image_data_from_byte_string bytes, width, height
  else
    error bytes

class AudioSource
  new: (@source_id, @url) =>
  msg: (...) => post_message { "audio", @source_id, ... }

  play: => @msg "play"
  stop: => @msg "stop"
  rewind: => @msg "rewind"
  setLooping: (should_loop) => @msg "set_looping", should_loop

  __tostring: => "HTMLAudioSource<" .. @url .. ">"

nacl.init = {
  graphics: =>
    old_new_image = @newImage

    @newImage = (url, ...) ->
      return old_new_image url, ... if type(url) != "string"
      return img_cache[url] if img_cache[url]

      img_data = image_data_from_url url
      img = old_new_image img_data
      img_cache[url] = img
      img

    @newFont = (font_str, alphabet=ALPHABET) ->
      glyphs = request_response { "font", font_str, alphabet }
      cache = aroma.font.newGlyphCache!
      for g in *glyphs
        letter, data_string, w, h = unpack g
        data = nacl.image_data_from_byte_string data_string, w, h
        cache\add_glyph letter\byte!, data
      cache\to_font!

    aroma.boot = ->
      if not self.getFont!
        self.setFont self.newFont DEFAULT_FONT

  audio: =>
    @newSource = (url, kind="static") ->
      msg = request_response { "audio", url, kind }
      status, source_id, err_msg = unpack msg
      if status == "success"
        AudioSource source_id, url
      else
        error error_msg

  image: =>
    old_new_image_data = @newImageData
    @newImageData = (...) ->
      args = {...}
      old_new_image_data ... if type(args[1]) != "string"
      image_data_from_url args[1]
}

functions_to_reset = {"draw", "update", "keypressed", "keyreleased", "focus", "load"}

nacl.init_all = (aroma) ->
  aroma.run = (setup_fn) ->
    blank = ->
    aroma[key] = blank for key in *functions_to_reset
    aroma.graphics.reset!
    aroma.boot!

    setup_fn!

    while true -- listen for game messages
      msg = {coroutine.yield!}
      if aroma[msg[1]]
        name = table.remove msg, 1
        aroma[name] unpack msg
      else
        print "unknown message"
        print unpack msg