SINGILARTYOPS, fixed :Copy() null colors of PonyDataInstance

--
-- Copyright (C) 2017-2018 DBot

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

file.CreateDir('ppm2')
file.CreateDir('ppm2/backups')
file.CreateDir('ppm2/thumbnails')

for _, ffind in ipairs file.Find('ppm2/*.txt', 'DATA')
	fTarget = ffind\sub(1, -5)
	-- maybe joined server with old ppm2 and new clear _current was generated
	if not file.Exists('ppm2/' .. fTarget .. '.dat', 'DATA')
		fRead = file.Read('ppm2/' .. ffind, 'DATA')
		json = util.JSONToTable(fRead)
		if json
			TagCompound = DLib.NBT.TagCompound()
			for key, value in pairs json
				switch luatype(value)
					when 'string'
						TagCompound\AddString(key, value)
					when 'number'
						TagCompound\AddFloat(key, value)
					when 'boolean'
						TagCompound\AddByte(key, value and 1 or 0)
					when 'table'
						-- assume color
						TagCompound\AddByteArray(key, {value.r - 128, value.g - 128, value.b - 128, value.a - 128}) if value.r and value.g and value.b and value.a
					else
						error(luatype(value))
			buf = DLib.BytesBuffer()
			TagCompound\WriteFile(buf)
			stream = file.Open('ppm2/' .. fTarget .. '.dat', 'wb', 'DATA')
			buf\ToFileStream(stream)
			stream\Flush()
			stream\Close()
	file.Delete('ppm2/' .. ffind)

class PonyDataInstance
	@DATA_DIR = "ppm2/"
	@DATA_DIR_BACKUP = "ppm2/backups/"

	@FindFiles = =>
		output = [str\sub(1, #str - 4) for _, str in ipairs file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.dat')]
		return output

	@FindInstances = =>
		output = [@(str\sub(1, #str - 4)) for _, str in ipairs file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.dat')]
		return output

	@PONY_DATA = PPM2.PonyDataRegistry

	@PONY_DATA_MAPPING = {getFunc\lower(), key for key, {:getFunc} in pairs @PONY_DATA}
	@PONY_DATA_MAPPING[key] = key for key, value in pairs @PONY_DATA

	for key, data in pairs @PONY_DATA
		continue unless data.enum
		data.enum = [arg\upper() for _, arg in ipairs data.enum]
		data.enumMapping = {}
		data.enumMappingBackward = {}
		i = -1
		for _, enumVal in ipairs data.enum
			i += 1
			data.enumMapping[i] = enumVal
			data.enumMappingBackward[enumVal] = i
	for key, {:getFunc, :fix, :enumMappingBackward, :enumMapping, :enum, :min, :max, :default} in pairs @PONY_DATA
		@__base["Get#{getFunc}"] = => @dataTable[key]
		@__base["GetMin#{getFunc}"] = => min if min
		@__base["GetMax#{getFunc}"] = => max if max
		@__base["Enum#{getFunc}"] = => enum if enum
		@__base["Get#{getFunc}Types"] = => enum if enum

		@["GetMin#{getFunc}"] = => min if min
		@["GetMax#{getFunc}"] = => max if max
		@["GetDefault#{getFunc}"] = default
		@["GetEnum#{getFunc}"] = => enum if enum
		@["Enum#{getFunc}"] = => enum if enum

		if enumMapping
			@__base["Get#{getFunc}Enum"] = => enumMapping[@dataTable[key]] or enumMapping[0] or @dataTable[key]
			@__base["GetEnum#{getFunc}"] = @__base["Get#{getFunc}Enum"]
		@__base["Reset#{getFunc}"] = => @["Set#{getFunc}"](@, default())
		@__base["Set#{getFunc}"] = (val = defValue, ...) =>
			if luatype(val) == 'string' and enumMappingBackward
				newVal = enumMappingBackward[val\upper()]
				val = newVal if newVal
			newVal = fix(val)
			oldVal = @dataTable[key]
			@dataTable[key] = newVal
			@ValueChanges(key, oldVal, newVal, ...)

	WriteNetworkData: =>
		for _, {:strName, :writeFunc, :getName, :defValue} in ipairs PPM2.NetworkedPonyData.NW_Vars
			if @["Get#{getName}"]
				writeFunc(@["Get#{getName}"](@))
			else
				writeFunc(defValue)

	Copy: (fileName = @filename) =>
		copyOfData = {}
		for key, val in pairs @dataTable
			switch luatype(val)
				when 'number', 'string', 'boolean'
					copyOfData[key] = val
				when 'table', 'Color'
					{:r, :g, :b} = val
					if r and g and b
						copyOfData[key] = Color(r, g, b)
		newData = @@(fileName, copyOfData, false)
		return newData
	CreateCustomNetworkObject: (goingToNetwork = false, ply = LocalPlayer(), ...) =>
		newData = PPM2.NetworkedPonyData(nil, ply)
		newData\SetIsGoingToNetwork(goingToNetwork)
		@ApplyDataToObject(newData, ...)
		return newData
	CreateNetworkObject: (goingToNetwork = true, ...) =>
		newData = PPM2.NetworkedPonyData(nil, LocalPlayer())
		newData\SetIsGoingToNetwork(goingToNetwork)
		@ApplyDataToObject(newData, ...)
		return newData
	ApplyDataToObject: (target, ...) =>
		for key, value in pairs @GetAsNetworked()
			error("Attempt to apply data to object #{target} at unknown index #{key}!") if not target["Set#{key}"]
			target["Set#{key}"](target, value, ...)
	UpdateController: (...) => @ApplyDataToObject(@nwObj, ...)
	CreateController: (...) => @CreateNetworkObject(false, ...)
	CreateCustomController: (...) => @CreateCustomNetworkObject(false, ...)

	new: (filename, data, readIfExists = true, force = false, doBackup = true) =>
		@SetFilename(filename)
		@NBTTagCompound = DLib.NBT.TagCompound()
		@updateNWObject = true
		@networkNWObject = true
		@valid = @isOpen
		@rawData = data
		@dataTable = {k, default() for k, {:default} in pairs @@PONY_DATA}
		@saveOnChange = true
		if data
			@SetupData(data, true)
		elseif @exists and readIfExists
			@ReadFromDisk(force, doBackup)

	Reset: => @['Reset' .. getFunc](@) for k, {:getFunc} in pairs @@PONY_DATA

	@ERR_MISSING_PARAMETER = 4
	@ERR_MISSING_CONTENT = 5

	GetSaveOnChange: => @saveOnChange
	SaveOnChange: => @saveOnChange
	SetSaveOnChange: (val = true) => @saveOnChange = val
	GetValueFromNBT: (mapData, value) =>
		if mapData.enum and type(value) == 'string'
			mapData.fix(mapData.enumMappingBackward[value\upper()])
		elseif mapData.type == 'COLOR'
			if IsColor(value)
				mapData.fix(Color(value))
			else
				mapData.fix(Color(value[1] + 128, value[2] + 128, value[3] + 128, value[4] + 128))
		elseif mapData.type == 'BOOLEAN'
			if type(value) == 'boolean'
				mapData.fix(value)
			else
				mapData.fix(value == 1)
		else
			mapData.fix(value)

	GetExtraBackupPath: => "#{@@DATA_DIR_BACKUP}#{@filename}_bak_#{os.date('%S_%M_%H-%d_%m_%Y', os.time())}.dat"

	SetupData: (data = @NBTTagCompound, force = false, doBackup = false) =>
		if luatype(data) == 'NBTCompound'
			data = data\GetValue()
		if doBackup or not force
			makeBackup = false
			for key, value2 in pairs(data)
				key = key\lower()
				map = @@PONY_DATA_MAPPING[key]
				if map
					mapData = @@PONY_DATA[map]
					value = @GetValueFromNBT(mapData, value2)
					if mapData.enum
						if luatype(value) == 'string' and not mapData.enumMappingBackward[value\upper()] or luatype(value) == 'number' and not mapData.enumMapping[value]
							return @@ERR_MISSING_CONTENT if not force
							makeBackup = true
							break
			if doBackup and makeBackup and @exists
				fRead = file.Read(@fpath, 'DATA')
				file.Write(@GetExtraBackupPath(), fRead)

		for key, value2 in pairs(data)
			key = key\lower()
			map = @@PONY_DATA_MAPPING[key]
			continue unless map
			mapData = @@PONY_DATA[map]
			@dataTable[key] = @GetValueFromNBT(mapData, value2)
	ValueChanges: (key, oldVal, newVal, saveNow = @exists and @saveOnChange) =>
		if @nwObj and @updateNWObject
			{:getFunc} = @@PONY_DATA[key]
			@nwObj["Set#{getFunc}"](@nwObj, newVal, @networkNWObject)
		@Save() if saveNow
	SetFilename: (filename) =>
		@filename = filename
		@filenameFull = "#{filename}.dat"
		@fpath = "#{@@DATA_DIR}#{filename}.dat"
		@preview = "#{@@DATA_DIR}thumbnails/#{filename}.png"
		@fpathFull = "data/#{@@DATA_DIR}#{filename}.dat"
		@isOpen = @filename ~= nil
		@exists = file.Exists(@fpath, 'DATA')
		return @exists
	SetNetworkData: (nwObj) => @nwObj = nwObj
	SetPonyData: (nwObj) => @nwObj = nwObj
	SetPonyDataController: (nwObj) => @nwObj = nwObj
	SetPonyController: (nwObj) => @nwObj = nwObj
	SetController: (nwObj) => @nwObj = nwObj
	SetDataController: (nwObj) => @nwObj = nwObj

	SetNetworkOnChange: (newVal = true) => @networkNWObject = newVal
	SetUpdateOnChange: (newVal = true) => @updateNWObject = newVal

	GetNetworkOnChange: => @networkNWObject
	GetUpdateOnChange: => @updateNWObject

	GetNetworkData: => @nwObj
	GetPonyData: => @nwObj
	GetPonyDataController: => @nwObj
	GetPonyController: => @nwObj
	GetController: => @nwObj
	GetDataController: => @nwObj

	IsValid: => @valid
	Exists: => @exists
	FileExists: => @exists
	IsExists: => @exists
	GetFileName: => @filename
	GetFilename: => @filename
	GetFileNameFull: => @filenameFull
	GetFilenameFull: => @filenameFull
	GetFilePath: => @fpath
	GetFullFilePath: => @fpathFull
	SerealizeValue: (valID = '') =>
		map = @@PONY_DATA[valID]
		return unless map
		val = @dataTable[valID]
		if map.enum
			return DLib.NBT.TagString(map.enumMapping[val] or map.enumMapping[map.default()])
		elseif map.serealize
			return map.serealize(val)
		else
			switch map.type
				when 'INT'
					return DLib.NBT.TagInt(val)
				when 'FLOAT'
					return DLib.NBT.TagFloat(val)
				when 'URL'
					return DLib.NBT.TagString(val)
				when 'BOOLEAN'
					return DLib.NBT.TagByte(val and 1 or 0)
				when 'COLOR'
					return DLib.NBT.TagByteArray({val.r - 128, val.g - 128, val.b - 128, val.a - 128})
	GetAsNetworked: => {getFunc, @dataTable[k] for k, {:getFunc} in pairs @@PONY_DATA}

	@READ_SUCCESS = 0
	@ERR_FILE_NOT_EXISTS = 1
	@ERR_FILE_EMPTY = 2
	@ERR_FILE_CORRUPT = 3
	ReadFromDisk: (force = false, doBackup = true) =>
		return @@ERR_FILE_NOT_EXISTS unless @exists
		fRead = file.Read(@fpath, 'DATA')
		return @@ERR_FILE_EMPTY if not fRead or fRead == ''
		@NBTTagCompound\ReadFile(DLib.BytesBuffer(fRead))
		return @SetupData(@NBTTagCompound, force, doBackup) or @@READ_SUCCESS

	SaveAs: (path = @fpath) =>
		@NBTTagCompound\AddTag(key, @SerealizeValue(key)) for key, val in pairs @dataTable
		buf = DLib.BytesBuffer()
		@NBTTagCompound\WriteFile(buf)
		stream = file.Open(path, 'wb', 'DATA')
		buf\ToFileStream(stream)
		stream\Flush()
		stream\Close()
		return buf

	SavePreview: (path = @preview) =>
		buildingModel = ClientsideModel('models/ppm/ppm2_stage.mdl', RENDERGROUP_OTHER)
		buildingModel\SetNoDraw(true)
		buildingModel\SetModelScale(0.9)
		model = ClientsideModel('models/ppm/player_default_base_new_nj.mdl')

		with model
			\SetNoDraw(true)
			data = @CreateCustomController(model)
			.__PPM2_PonyData = data
			ctrl = data\GetRenderController()

			if bg = data\GetBodygroupController()
				bg\ApplyBodygroups()

			with model\PPMBonesModifier()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, model, data)
				\Think(true)

			\SetSequence(22)
			\FrameAdvance(0)

		timer.Simple 0.5, ->
			renderTarget = GetRenderTarget('ppm2_save_preview_generate2', 1024, 1024, false)
			renderTarget\Download()
			render.PushRenderTarget(renderTarget)
			--render.SuppressEngineLighting(true)
			render.Clear(0, 0, 0, 255, true, true)
			cam.Start3D(Vector(49.373046875, -35.021484375, 58.332901000977), Angle(0, 141, 0), 90, 0, 0, 1024, 1024)

			buildingModel\DrawModel()

			with model
				data = .__PPM2_PonyData
				ctrl = data\GetRenderController()

				if bg = data\GetBodygroupController()
					bg\ApplyBodygroups()

				with model\PPMBonesModifier()
					\ResetBones()
					hook.Call('PPM2.SetupBones', nil, model, data)
					\Think(true)

				with ctrl
					\DrawModels()
					\HideModels(true)
					\PreDraw(model, true)

				\DrawModel()
				ctrl\PostDraw(model, true)

			cam.End3D()

			data = render.Capture({
				format: 'png'
				x: 0
				y: 0
				w: 1024
				h: 1024
				alpha: false
			})

			model\Remove()
			buildingModel\Remove()

			file.Write(path, data)

			--render.SuppressEngineLighting(false)
			render.PopRenderTarget()

	Save: (doBackup = true, preview = true) =>
		file.Write(@GetExtraBackupPath(), file.Read(@fpath, 'DATA')) if doBackup and @exists
		buf = @SaveAs(@fpath)
		@SavePreview(@preview) if preview
		@exists = true
		return buf

PPM2.PonyDataInstance = PonyDataInstance

PPM2.MainDataInstance = nil
PPM2.GetMainData = ->
	if not PPM2.MainDataInstance
		PPM2.MainDataInstance = PonyDataInstance('_current', nil, true, true)
		if not PPM2.MainDataInstance\FileExists()
			PPM2.MainDataInstance\Save()
	return PPM2.MainDataInstance

--
-- Copyright (C) 2017-2018 DBot

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

file.CreateDir('ppm2')
file.CreateDir('ppm2/backups')
file.CreateDir('ppm2/thumbnails')

for _, ffind in ipairs file.Find('ppm2/*.txt', 'DATA')
	fTarget = ffind\sub(1, -5)
	-- maybe joined server with old ppm2 and new clear _current was generated
	if not file.Exists('ppm2/' .. fTarget .. '.dat', 'DATA')
		fRead = file.Read('ppm2/' .. ffind, 'DATA')
		json = util.JSONToTable(fRead)
		if json
			TagCompound = DLib.NBT.TagCompound()
			for key, value in pairs json
				switch luatype(value)
					when 'string'
						TagCompound\AddString(key, value)
					when 'number'
						TagCompound\AddFloat(key, value)
					when 'boolean'
						TagCompound\AddByte(key, value and 1 or 0)
					when 'table'
						-- assume color
						TagCompound\AddByteArray(key, {value.r - 128, value.g - 128, value.b - 128, value.a - 128}) if value.r and value.g and value.b and value.a
					else
						error(luatype(value))
			buf = DLib.BytesBuffer()
			TagCompound\WriteFile(buf)
			stream = file.Open('ppm2/' .. fTarget .. '.dat', 'wb', 'DATA')
			buf\ToFileStream(stream)
			stream\Flush()
			stream\Close()
	file.Delete('ppm2/' .. ffind)

class PonyDataInstance
	@DATA_DIR = "ppm2/"
	@DATA_DIR_BACKUP = "ppm2/backups/"

	@FindFiles = =>
		output = [str\sub(1, #str - 4) for _, str in ipairs file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.dat')]
		return output

	@FindInstances = =>
		output = [@(str\sub(1, #str - 4)) for _, str in ipairs file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.dat')]
		return output

	@PONY_DATA = PPM2.PonyDataRegistry

	@PONY_DATA_MAPPING = {getFunc\lower(), key for key, {:getFunc} in pairs @PONY_DATA}
	@PONY_DATA_MAPPING[key] = key for key, value in pairs @PONY_DATA

	for key, data in pairs @PONY_DATA
		continue unless data.enum
		data.enum = [arg\upper() for _, arg in ipairs data.enum]
		data.enumMapping = {}
		data.enumMappingBackward = {}
		i = -1
		for _, enumVal in ipairs data.enum
			i += 1
			data.enumMapping[i] = enumVal
			data.enumMappingBackward[enumVal] = i
	for key, {:getFunc, :fix, :enumMappingBackward, :enumMapping, :enum, :min, :max, :default} in pairs @PONY_DATA
		@__base["Get#{getFunc}"] = => @dataTable[key]
		@__base["GetMin#{getFunc}"] = => min if min
		@__base["GetMax#{getFunc}"] = => max if max
		@__base["Enum#{getFunc}"] = => enum if enum
		@__base["Get#{getFunc}Types"] = => enum if enum

		@["GetMin#{getFunc}"] = => min if min
		@["GetMax#{getFunc}"] = => max if max
		@["GetDefault#{getFunc}"] = default
		@["GetEnum#{getFunc}"] = => enum if enum
		@["Enum#{getFunc}"] = => enum if enum

		if enumMapping
			@__base["Get#{getFunc}Enum"] = => enumMapping[@dataTable[key]] or enumMapping[0] or @dataTable[key]
			@__base["GetEnum#{getFunc}"] = @__base["Get#{getFunc}Enum"]
		@__base["Reset#{getFunc}"] = => @["Set#{getFunc}"](@, default())
		@__base["Set#{getFunc}"] = (val = defValue, ...) =>
			if luatype(val) == 'string' and enumMappingBackward
				newVal = enumMappingBackward[val\upper()]
				val = newVal if newVal
			newVal = fix(val)
			oldVal = @dataTable[key]
			@dataTable[key] = newVal
			@ValueChanges(key, oldVal, newVal, ...)

	WriteNetworkData: =>
		for _, {:strName, :writeFunc, :getName, :defValue} in ipairs PPM2.NetworkedPonyData.NW_Vars
			if @["Get#{getName}"]
				writeFunc(@["Get#{getName}"](@))
			else
				writeFunc(defValue)

	Copy: (fileName = @filename) =>
		copyOfData = {}
		for key, val in pairs @dataTable
			switch luatype(val)
				when 'number'
					copyOfData[key] = val
				when 'string'
					copyOfData[key] = val
				when 'boolean'
					copyOfData[key] = val
				when 'table'
					{:r, :g, :b} = val
					if r and g and b
						copyOfData[key] = Color(r, g, b)
		newData = @@(fileName, copyOfData, false)
		return newData
	CreateCustomNetworkObject: (goingToNetwork = false, ply = LocalPlayer(), ...) =>
		newData = PPM2.NetworkedPonyData(nil, ply)
		newData\SetIsGoingToNetwork(goingToNetwork)
		@ApplyDataToObject(newData, ...)
		return newData
	CreateNetworkObject: (goingToNetwork = true, ...) =>
		newData = PPM2.NetworkedPonyData(nil, LocalPlayer())
		newData\SetIsGoingToNetwork(goingToNetwork)
		@ApplyDataToObject(newData, ...)
		return newData
	ApplyDataToObject: (target, ...) =>
		for key, value in pairs @GetAsNetworked()
			error("Attempt to apply data to object #{target} at unknown index #{key}!") if not target["Set#{key}"]
			target["Set#{key}"](target, value, ...)
	UpdateController: (...) => @ApplyDataToObject(@nwObj, ...)
	CreateController: (...) => @CreateNetworkObject(false, ...)
	CreateCustomController: (...) => @CreateCustomNetworkObject(false, ...)

	new: (filename, data, readIfExists = true, force = false, doBackup = true) =>
		@SetFilename(filename)
		@NBTTagCompound = DLib.NBT.TagCompound()
		@updateNWObject = true
		@networkNWObject = true
		@valid = @isOpen
		@rawData = data
		@dataTable = {k, default() for k, {:default} in pairs @@PONY_DATA}
		@saveOnChange = true
		if data
			@SetupData(data, true)
		elseif @exists and readIfExists
			@ReadFromDisk(force, doBackup)

	Reset: => @['Reset' .. getFunc](@) for k, {:getFunc} in pairs @@PONY_DATA

	@ERR_MISSING_PARAMETER = 4
	@ERR_MISSING_CONTENT = 5

	GetSaveOnChange: => @saveOnChange
	SaveOnChange: => @saveOnChange
	SetSaveOnChange: (val = true) => @saveOnChange = val
	GetValueFromNBT: (mapData, value) =>
		if mapData.enum and luatype(value) == 'string'
			mapData.fix(mapData.enumMappingBackward[value\upper()])
		elseif mapData.type == 'COLOR'
			if value.r and value.g and value.b and value.a
				mapData.fix(Color(value))
			else
				mapData.fix(Color(value[1] + 128, value[2] + 128, value[3] + 128, value[4] + 128))
		elseif mapData.type == 'BOOLEAN'
			if luatype(value) == 'boolean'
				mapData.fix(value)
			else
				mapData.fix(value == 1)
		else
			mapData.fix(value)

	GetExtraBackupPath: => "#{@@DATA_DIR_BACKUP}#{@filename}_bak_#{os.date('%S_%M_%H-%d_%m_%Y', os.time())}.dat"

	SetupData: (data = @NBTTagCompound, force = false, doBackup = false) =>
		if luatype(data) == 'NBTCompound'
			data = data\GetValue()
		if doBackup or not force
			makeBackup = false
			for key, value2 in pairs(data)
				key = key\lower()
				map = @@PONY_DATA_MAPPING[key]
				if map
					mapData = @@PONY_DATA[map]
					value = @GetValueFromNBT(mapData, value2)
					if mapData.enum
						if luatype(value) == 'string' and not mapData.enumMappingBackward[value\upper()] or luatype(value) == 'number' and not mapData.enumMapping[value]
							return @@ERR_MISSING_CONTENT if not force
							makeBackup = true
							break
			if doBackup and makeBackup and @exists
				fRead = file.Read(@fpath, 'DATA')
				file.Write(@GetExtraBackupPath(), fRead)

		for key, value2 in pairs(data)
			key = key\lower()
			map = @@PONY_DATA_MAPPING[key]
			continue unless map
			mapData = @@PONY_DATA[map]
			@dataTable[key] = @GetValueFromNBT(mapData, value2)
	ValueChanges: (key, oldVal, newVal, saveNow = @exists and @saveOnChange) =>
		if @nwObj and @updateNWObject
			{:getFunc} = @@PONY_DATA[key]
			@nwObj["Set#{getFunc}"](@nwObj, newVal, @networkNWObject)
		@Save() if saveNow
	SetFilename: (filename) =>
		@filename = filename
		@filenameFull = "#{filename}.dat"
		@fpath = "#{@@DATA_DIR}#{filename}.dat"
		@preview = "#{@@DATA_DIR}thumbnails/#{filename}.png"
		@fpathFull = "data/#{@@DATA_DIR}#{filename}.dat"
		@isOpen = @filename ~= nil
		@exists = file.Exists(@fpath, 'DATA')
		return @exists
	SetNetworkData: (nwObj) => @nwObj = nwObj
	SetPonyData: (nwObj) => @nwObj = nwObj
	SetPonyDataController: (nwObj) => @nwObj = nwObj
	SetPonyController: (nwObj) => @nwObj = nwObj
	SetController: (nwObj) => @nwObj = nwObj
	SetDataController: (nwObj) => @nwObj = nwObj

	SetNetworkOnChange: (newVal = true) => @networkNWObject = newVal
	SetUpdateOnChange: (newVal = true) => @updateNWObject = newVal

	GetNetworkOnChange: => @networkNWObject
	GetUpdateOnChange: => @updateNWObject

	GetNetworkData: => @nwObj
	GetPonyData: => @nwObj
	GetPonyDataController: => @nwObj
	GetPonyController: => @nwObj
	GetController: => @nwObj
	GetDataController: => @nwObj

	IsValid: => @valid
	Exists: => @exists
	FileExists: => @exists
	IsExists: => @exists
	GetFileName: => @filename
	GetFilename: => @filename
	GetFileNameFull: => @filenameFull
	GetFilenameFull: => @filenameFull
	GetFilePath: => @fpath
	GetFullFilePath: => @fpathFull
	SerealizeValue: (valID = '') =>
		map = @@PONY_DATA[valID]
		return unless map
		val = @dataTable[valID]
		if map.enum
			return DLib.NBT.TagString(map.enumMapping[val] or map.enumMapping[map.default()])
		elseif map.serealize
			return map.serealize(val)
		else
			switch map.type
				when 'INT'
					return DLib.NBT.TagInt(val)
				when 'FLOAT'
					return DLib.NBT.TagFloat(val)
				when 'URL'
					return DLib.NBT.TagString(val)
				when 'BOOLEAN'
					return DLib.NBT.TagByte(val and 1 or 0)
				when 'COLOR'
					return DLib.NBT.TagByteArray({val.r - 128, val.g - 128, val.b - 128, val.a - 128})
	GetAsNetworked: => {getFunc, @dataTable[k] for k, {:getFunc} in pairs @@PONY_DATA}

	@READ_SUCCESS = 0
	@ERR_FILE_NOT_EXISTS = 1
	@ERR_FILE_EMPTY = 2
	@ERR_FILE_CORRUPT = 3
	ReadFromDisk: (force = false, doBackup = true) =>
		return @@ERR_FILE_NOT_EXISTS unless @exists
		fRead = file.Read(@fpath, 'DATA')
		return @@ERR_FILE_EMPTY if not fRead or fRead == ''
		@NBTTagCompound\ReadFile(DLib.BytesBuffer(fRead))
		return @SetupData(@NBTTagCompound, force, doBackup) or @@READ_SUCCESS

	SaveAs: (path = @fpath) =>
		@NBTTagCompound\AddTag(key, @SerealizeValue(key)) for key, val in pairs @dataTable
		buf = DLib.BytesBuffer()
		@NBTTagCompound\WriteFile(buf)
		stream = file.Open(path, 'wb', 'DATA')
		buf\ToFileStream(stream)
		stream\Flush()
		stream\Close()
		return buf

	SavePreview: (path = @preview) =>
		buildingModel = ClientsideModel('models/ppm/ppm2_stage.mdl', RENDERGROUP_OTHER)
		buildingModel\SetNoDraw(true)
		buildingModel\SetModelScale(0.9)
		model = ClientsideModel('models/ppm/player_default_base_new_nj.mdl')

		with model
			\SetNoDraw(true)
			data = @CreateCustomController(model)
			.__PPM2_PonyData = data
			ctrl = data\GetRenderController()

			if bg = data\GetBodygroupController()
				bg\ApplyBodygroups()

			with model\PPMBonesModifier()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, model, data)
				\Think(true)

			\SetSequence(22)
			\FrameAdvance(0)

		timer.Simple 0.5, ->
			renderTarget = GetRenderTarget('ppm2_save_preview_generate2', 1024, 1024, false)
			renderTarget\Download()
			render.PushRenderTarget(renderTarget)
			--render.SuppressEngineLighting(true)
			render.Clear(0, 0, 0, 255, true, true)
			cam.Start3D(Vector(49.373046875, -35.021484375, 58.332901000977), Angle(0, 141, 0), 90, 0, 0, 1024, 1024)

			buildingModel\DrawModel()

			with model
				data = .__PPM2_PonyData
				ctrl = data\GetRenderController()

				if bg = data\GetBodygroupController()
					bg\ApplyBodygroups()

				with model\PPMBonesModifier()
					\ResetBones()
					hook.Call('PPM2.SetupBones', nil, model, data)
					\Think(true)

				with ctrl
					\DrawModels()
					\HideModels(true)
					\PreDraw(model, true)

				\DrawModel()
				ctrl\PostDraw(model, true)

			cam.End3D()

			data = render.Capture({
				format: 'png'
				x: 0
				y: 0
				w: 1024
				h: 1024
				alpha: false
			})

			model\Remove()
			buildingModel\Remove()

			file.Write(path, data)

			--render.SuppressEngineLighting(false)
			render.PopRenderTarget()

	Save: (doBackup = true, preview = true) =>
		file.Write(@GetExtraBackupPath(), file.Read(@fpath, 'DATA')) if doBackup and @exists
		buf = @SaveAs(@fpath)
		@SavePreview(@preview) if preview
		@exists = true
		return buf

PPM2.PonyDataInstance = PonyDataInstance

PPM2.MainDataInstance = nil
PPM2.GetMainData = ->
	if not PPM2.MainDataInstance
		PPM2.MainDataInstance = PonyDataInstance('_current', nil, true, true)
		if not PPM2.MainDataInstance\FileExists()
			PPM2.MainDataInstance\Save()
	return PPM2.MainDataInstance


k: skip k itself

k: skip k itself

Former-commit-id: d00792cee4ad64dfb74bd7513a316a7bf9ea9e3c [formerly fd5f553df34810172c517290ae5de7628ab83e96]
Former-commit-id: 21259f02f646745eac449c8f703318d57b1be44b

-- k, reimplemented from dbot's k by byte[]
history = {}

PRIVMSG: {
  '^%pk$': (source, destination) =>
    t = {}
    max = 5
    for i = 1, max
      h = history[destination][math.random(#history[destination])]
      msg = h
      want = math.random(2, 4)
      getting = false
      msg = msg\gsub("\003%d?%d?,?%d?%d?", "")
      for w in msg\gmatch("[%w%[%]%{%}%|`%^_%-%|'\194-\244\128-\191]+") do
        if w == "http" or w == "https"
          break
        if not getting
          -- Skip k itself
          if w == 'k'
            continue
          -- Less odds if not starting with letter.
          if w\find("^[%a']") or math.random(2) == 1
            if math.random(1, max + i + 1) <= i
              getting = true
        if getting
          if want > 0
            t[#t + 1] = w\lower()
          else
            break
          want = want - 1
    outline = table.concat(t, " ")
    @Msg 'privmsg', destination, source, outline
  (source, destination, argument) =>
    unless history[destination]
      history[destination] = {}
    table.insert history[destination], #history[destination]+1, argument
    if #history[destination] > 20
      table.remove history[destination], 1
}

-- k, reimplemented from dbot's k by byte[]
history = {}

PRIVMSG: {
  '^%pk$': (source, destination) =>
    t = {}
    max = 5
    for i = 1, max
      h = history[destination][math.random(#history[destination])]
      msg = h
      want = math.random(2, 4)
      getting = false
      msg = msg\gsub("\003%d?%d?,?%d?%d?", "")
      for w in msg\gmatch("[%w%[%]%{%}%|`%^_%-%|'\194-\244\128-\191]+") do
        if w == "http" or w == "https"
          break
        if not getting
          -- Less odds if not starting with letter.
          if w\find("^[%a']") or math.random(2) == 1
            if math.random(1, max + i + 1) <= i
              getting = true
        if getting
          if want > 0
            t[#t + 1] = w\lower()
          else
            break
          want = want - 1
    outline = table.concat(t, " ")
    @Msg 'privmsg', destination, source, outline
  (source, destination, argument) =>
    unless history[destination]
      history[destination] = {}
    table.insert history[destination], #history[destination]+1, argument
    if #history[destination] > 20
      table.remove history[destination], 1
}


improved profile

improved profile

lazuli = require "lazuli"
Profiles = require "models.profiles"
import respond_to from require "lapis.application"

import NULL from require "lapis.db"

class extends lazuli.Application
  @enable "user_management"

  @name: "profile_"
  @path: "/profile"

  [show: "/:id[%d]"]: =>
    @profiledata,err=Profiles\getOrCreateByUser tonumber @params.id
    if @profiledata
      return render: true
    else
      return status: 404, "Error 404: "..err

  [edit: "/edit"]: respond_to {
    GET: =>
      if @modules.user_management.currentuser
        @profiledata,err=Profiles\getOrCreateByUser @modules.user_management.currentuser
        return render: true
      else
        @session.login_redirect=@req.parsed_url.path
        return redirect_to: @url_for "lazuli_modules_usermanagement_login"
    POST: =>
      if @modules.user_management.currentuser
        @profiledata,err=Profiles\getOrCreateByUser @modules.user_management.currentuser
        @profiledata\update{
          about: @params.about or NULL
          birthday: @params.birthday\find"%d%d%d%d-%d%d?-%d%d?" and @params.birthday or NULL
          privacy_birthday_dm: @params.privacy_birthday_dm and true or false
          privacy_birthday_y: @params.privacy_birthday_y and true or false
          privacy_birthday_age: @params.privacy_birthday_age and true or false
          gender: @params.gender or NULL
          privacy_gender: @params.privacy_gender and true or false
        }
        return render: true
      else
        @session.login_redirect=@req.parsed_url.path
        return redirect_to: @url_for "lazuli_modules_usermanagement_login"
  }

lazuli = require "lazuli"
Profiles = require "models.profiles"
import respond_to from require "lapis.application"

class extends lazuli.Application
  @enable "user_management"

  @name: "profile_"
  @path: "/profile"

  [show: "/:id[%d]"]: =>
    @profiledata,err=Profiles\getOrCreateByUser tonumber @params.id
    if @profiledata
      return render: true
    else
      return status: 404, "Error 404: "..err

  [edit: "/edit"]: respond_to {
    GET: =>
      if @modules.user_management.currentuser
        @profiledata,err=Profiles\getOrCreateByUser @modules.user_management.currentuser
        return render: true
      else
        @session.login_redirect=@req.parsed_url.path
        return redirect_to: @url_for "lazuli_modules_usermanagement_login"
    POST: =>
      if @modules.user_management.currentuser
        @profiledata,err=Profiles\getOrCreateByUser @modules.user_management.currentuser
        @profiledata\update{
          about: @params.about
          birthday: @params.birthday
          privacy_birthday_dm: @params.privacy_birthday_dm or false
          privacy_birthday_y: @params.privacy_birthday_y or false
          privacy_birthday_age: @params.privacy_birthday_age or false
          gender: @params.gender
          privacy_gender: @params.privacy_gender or false
        }
        return render: true
      else
        @session.login_redirect=@req.parsed_url.path
        return redirect_to: @url_for "lazuli_modules_usermanagement_login"
  }


Small documentation fix

Small documentation fix

[=[
Class: TableX
A static class used for table-specific utility functions that are not specific to Garry's Mod.

Revisions:
	4.0.0 - Initial.
]=]
class ulx.TableX
	[=[
	Function: Count
	Counts the number of elements in a table using pairs.

	Parameters:
		t - The *table* to count.

	Returns:
		The *number* of elements in the table.

	Example:
		:Count( { "apple", "pear", done=true, banana="yellow" } )

		returns...

		:4

	Notes:
		* This is slow and should be avoided if at all possible.
		* Use the '#' operator instead of this if the table only contains numeric indices or if you only care about the numeric indices.
		* Use <IsEmpty> instead of this if you only want to see if a hash table has any values.
		* Complexity is O(n), where n is the number of values in t.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Count: (t using nil) ->
		c = 0
		for v in pairs t
			c += 1

		c

	[=[
	Function: IsEmpty
	Checks if a table contains any values on any type of key.

	Parameters:
		t - The *table* to check.

	Returns:
		A *boolean*, true if the table t has one or more values, false otherwise.

	Notes:
		* This is much faster than <Count> for checking if a table has any elements.
		* Complexity is O(1).

	Revisions:
		4.0.0 - Initial.
	]=]
	@IsEmpty: (t using nil) ->
		next(t) == nil

	[=[
	Function: Empty
	Removes all data from a table.

	Parameters:
		t - The *table* to empty.

	Returns:
		The *table* t.

	Notes:
		* Complexity is O(Count(t)).

	Revisions:
		4.0.0 - Initial.
	]=]
	@Empty: (t using nil) ->
		for k, v in pairs t
			t[ k ] = nil

		t

	[=[
	Function: Copy
	Make a shallow copy of a table. A shallow copy means that any subtables will still refer to the	same underlying table.

	Parameters:
		t - The *table* to make a copy of.

	Returns:
		The copied *table*.

	Notes:
		* Complexity is O(Count(t)).

	Revisions:
		4.0.0 - Initial.
	]=]
	@Copy: (t using nil) ->
		{ k, v for k, v in pairs t }

	[=[
	Function: CopyI
	Exactly the same as <Copy> except that it uses fori instead of pairs.
	In general, this means that it only copies numeric keys. See <A Discussion On fori>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@CopyI: (t using nil) ->
		[ v for v in *t ]

	[=[
	Function: DeepCopy
	Make a deep copy of a table. A deep copy means that any subtables will refer to a new copy of the original subtable.

	Parameters:
		t - The *table* to make a copy of. Must not have any cycles.

	Returns:
		The copied *table*.

	Revisions:
		4.0.0 - Initial.
	]=]
	@DeepCopy: (t using nil) ->
		{ k, (type(v) == "table" and self.DeepCopy(v) or v) for k, v in pairs t }

	InPlaceHelper = (t, inPlace using nil) ->
		if inPlace then return t
		self.Copy t

	InPlaceHelperI = (t, inPlace using nil) ->
		if inPlace then return t
		self.CopyI t

	[=[
	Function: RemoveDuplicateValues
	Removes any duplicate values from a list.

	Parameters:
		list    - The *list* to remove duplicates from.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on list itself, rather than a copy.

	Returns:
		The *list* that was passed in with duplicates removed. Returns the original table reference if in_place is true, a new table otherwise.

	Example:
		:RemoveDuplicateValues( { "apple", "pear", "kiwi", "apple", "banana", "pear", "pear" } )

		returns...
		:{ "apple", "pear", "kiwi", "banana" }

	Notes:
		* This function operates over numeric indices. See <A Discussion On fori>.
		* Complexity is around O(#list * log( #list )).
		* Duplicates are removed after the first value occurs. See example above.

	Revisions:
		4.0.0 - Initial.
	]=]
	@RemoveDuplicateValues: (list, inPlace using nil) ->
		list = InPlaceHelperI list, inPlace

		i = 2
		while i <= #list
			v = list[ i ]
			for j=1, i-1
				if list[j] == v
					table.remove list, i
					i -= 1 -- Since we removed it and it will be incremented below otherwise
					break
			i += 1

		list

	[=[
	Function: UnionByKey
	Merges two tables by key.

	Parameters:
		tableA  - The first *table* in the union.
		tableB  - The second *table* in the union.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on tableA itself, rather than a copy.

	Returns:
		The union *table*. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:UnionByKey( { apple="red", pear="green", kiwi="hairy" },
		:            { apple="green", pear="green", banana="yellow" } )

		returns...

		:{ apple="green", pear="green", kiwi="hairy", banana="yellow" }

	Notes:
		* If both tables have values on the same key, tableB takes precedence.
		* Complexity is O(Count(tableB)).

	Revisions:
		4.0.0 - Initial.
	]=]
	@UnionByKey: (tableA, tableB, inPlace using nil) ->
		tableA = InPlaceHelper tableA, inPlace

		for k, v in pairs tableB
			tableA[k] = v

		tableA

	[=[
	Function: UnionByKeyI
	Exactly the same as <UnionByKey> except that it uses fori instead of pairs. In general, this
	means that it only merges on numeric keys. See <A Discussion On fori>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@UnionByKeyI: (tableA, tableB, inPlace using nil) ->
		tableA = InPlaceHelperI tableA, inPlace

		for i=1, #tableB do
			if tableB[i] ~= nil
				tableA[i] = tableB[i]

		tableA

	[=[
	Function: Union
	Gets the union of two lists by value. If a value occurs once in listA and once in listB, the result of the union will only have one instance of that value as well.

	Parameters:
		listA - The first *list* in the union.
		listB - The second *list* in the union.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on tableA itself, rather than a copy.

	Returns:
		The union *list*. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:Union( { "apple", "pear", "kiwi" }, { "pear", "apple", "banana" } )

		returns...

		:{ "apple", "pear", "kiwi", "banana" }

	Notes:
		* This function operates over numeric indices. See <A Discussion On fori>.
		* The elements that in the returned table are in the same order they were in tableA and then tableB. See example above.
		* This function properly handles duplicate values in either list. All values will be unique in the resulting list.
		* Complexity is O( (#listA + #listB) * log( (#listA + #listB) ) )
		* You might want to consider using <SetFromList> combined with <UnionByKey> for large tables or if you plan on doing this often.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Union: (listA, listB, inPlace using nil) ->
		listA = self.Append listA, listB, inPlace
		self.RemoveDuplicateValues listA, true

	[=[
	Function: IntersectionByKey

	Gets the intersection of two tables by key.

	Parameters:
		tableA - The first *table* in the intersection.
		tableB - The second *table* in the intersection.

	Returns:
		The intersection *table*.

	Example:
		:IntersectionByKey( { apple="red", pear="green", kiwi="hairy" },
		:                   { apple="green", pear="green", banana="yellow" } )

		returns...

		:{ apple="green", pear="green" }

	Notes:
		* If both tables have values on the same key, tableB takes precedence.
		* Complexity is O(Count(tableA)).

	Revisions:
		4.0.0 - Initial.
	]=]
	@IntersectionByKey: (tableA, tableB using nil) ->
		result = {}

		-- Now just fill in each value with whatever the value in tableB is. This takes care of both
		-- elimination and making tableB take precedence when both tables have a value on key k.
		for k, v in pairs tableA
			result[k] = tableB[k]

		result

	[=[
	Function: IntersectionByKeyI
	Exactly the same as <IntersectionByKey> except that it uses fori instead of pairs. In
	general, this means that it only merges on numeric keys. See <A Discussion On fori>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@IntersectionByKeyI: (tableA, tableB using nil) ->
		result = {}

		-- Now just fill in each value with whatever the value in tableB is. This takes care of both
		-- elimination and making tableB take precedence when both tables have a value on key i.
		for i=1, #tableA
			if tableA[i] ~= nil
				result[i] = tableB[i]

		result

	[=[
	Function: Intersection
	Gets the intersection of two lists by value.

	Parameters:
		listA - The first *list* in the intersection.
		listB - The second *list* in the intersection.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on listA itself, rather than a copy.

	Returns:
		The intersection *list*. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:Intersection( { "apple", "pear", "kiwi" }, { "pear", "apple", "banana" } )

	returns...
		:{ "apple", "pear" }

	Notes:
		* This function operates over numeric indices. See <A Discussion On fori>.
		* The elements that are left in the returned table are in the same order they were in listA. See example above.
		* This function properly handles duplicate values in either list. All values will be unique in the resulting list.
		* Complexity is O(#ListA * #ListB).
		* You might want to consider using <SetFromList> combined with <IntersectionByKey> for large tables or if you plan on doing this often.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Intersection: (listA, listB, inPlace using nil) ->
		listA = self.RemoveDuplicateValues listA, inPlace

		i = 1
		while i <= #listA
			if self.HasValueI listB, listA[i]
				i += 1
			else
				table.remove listA, i

		listA

	[=[
	Function: DifferenceByKey
	Gets the difference of two tables by key. Difference is defined as all the keys in table A that are not in table B.

	Parameters:
		tableA - The first *table* in the difference.
		tableB - The second *table* in the difference.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on tableA itself, rather than a copy.

	Returns:
		The difference *table*. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:DifferenceByKey( { apple="red", pear="green", kiwi="hairy" },
		:                 { apple="green", pear="green", banana="yellow" } )

		returns...

		:{ kiwi="hairy" }

	Notes:
		* Complexity is O(Count(tableB)).

	Revisions:
		4.0.0 - Initial.
	]=]
	@DifferenceByKey: (tableA, tableB, inPlace using nil) ->
		tableA = InPlaceHelper tableA, inPlace

		for k, v in pairs tableB
			tableA[k] = nil

		tableA

	[=[
	Function: DifferenceByKeyI
	Exactly the same as <DifferenceByKey> except that it uses fori instead of pairs. In general,
	this means that it only performs the difference on numeric keys. See <A Discussion On fori>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@DifferenceByKeyI: (tableA, tableB, inPlace using nil) ->
		tableA = InPlaceHelperI tableA, inPlace

		for i=1, #tableB
			if tableB[i] ~= nil
				tableA[i] = nil

		tableA

	[=[
	Function: Difference
	Gets the difference of two lists by value.

	Parameters:
		listA - The first *list* in the difference.
		listB - The second *list* in the difference.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on tableA itself, rather than a copy.

	Returns:
		The difference *list*. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:Difference( { "apple", "pear", "kiwi" }, { "pear", "apple", "banana" } )

		returns...

		:{ "kiwi" }

	Notes:
		* This function operates over numeric indices. See <A Discussion On fori>.
		* The elements that are left in the returned table are in the same order they were in tableA. See example above.
		* This function properly handles duplicate values in either list. All values will be unique in the resulting list.
		* Complexity is O(#listA * #listB).
		* You might want to consider using <SetFromList> combined with <DifferenceByKey> for large tables or if you plan on doing this often.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Difference: (listA, listB, inPlace using nil) ->
		listA = self.RemoveDuplicateValues listA, inPlace

		for v in *listB
			hasValue, indexValue = self.HasValueI listA, v
			if hasValue
				table.remove listA, indexValue

		listA

	[=[
	Function: Append
	Appends values with numeric keys from one table to another.

	Parameters:
		listA - The first *list* in the append.
		listB - The second *list* in the append.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on tableA itself, rather than a copy.

	Returns:
		The *list* result of appending tableB to tableA. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:Append( { "apple", "banana", "apple", "kiwi" },
		:        { "orange", "pear", "apple" } )

		returns...

		:{ "apple", "banana", "apple", "kiwi", "orange", "pear", "apple" }

	Notes:
		* This function uses fori. See <A Discussion On fori>.
		* Complexity is O(#listB).

	Revisions:
		4.0.0 - Initial.
	]=]
	@Append: (listA, listB, inPlace using nil) ->
		listA = InPlaceHelperI listA, inPlace

		for v in *listB
			table.insert listA, v

		listA

	[=[
	Function: HasValue
	Checks for the presence of a value in a table.

	Parameters:
		t     - The *table* to check for the value's presence within.
		value - *Any type*, the value to check for within t.

	Returns:
		1 - A *boolean*. True if the table has the value, false otherwise.
		2 - A value of *any type*. The first key the value was found under if it was found, nil otherwise.

	Example:
		:HasValue( { apple="red", pear="green", kiwi="hairy" }, "green" )

		returns...

		:true, "pear"

	Revisions:
		4.0.0 - Initial.
	]=]
	@HasValue: (t, value using nil) ->
		for k, v in pairs t
			if v == value
				return true, k

		false, nil

	[=[
	Function: HasValueI
	Exactly the same as <HasValue> except that it uses fori instead of pairs.
	In general, this means that it only checks numeric keys. See <A Discussion On fori>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@HasValueI: (t, value using nil) ->
		for i=1, #t do
			if t[i] == value then
				return true, i

		false, nil

	[=[
	Function: SetFromList
	Creates a set from a list. A list is defined as a table with all numeric keys in sequential order (such as {"red", "yellow", "green"}).
	A set is defined as a table that only uses the boolean value true for keys that exist in the table.
	This function takes the values from the list and makes them the keys in a set, all with the value of 'true'.
	Note that you lose ordering and duplicates in the list during this conversion, but gain ease of testing for a value's existence in the table.
	One simply needs to test whether the value of a key is true or nil.

	Parameters:
		list - The *list*.

	Returns:
		The *table* representing the set.

	Example:
		:SetFromList( { "apple", "banana", "kiwi", "pear" } )

		returns...

		:{ apple=true, banana=true, kiwi=true, pear=true }

	Notes:
		* This function uses fori during the conversion process. See <A Discussion On fori>.
		* Complexity is O(#list).

	Revisions:
		4.0.0 - Initial.
	]=]
	@SetFromList: (list using nil) ->
		{ v, true for v in *list }

[=[
Class: TableX
A static class used for table-specific utility functions that are not specific to Garry's Mod.

Revisions:
	4.0.0 - Initial.
]=]
class ulx.TableX
	[=[
	Function: Count
	Counts the number of elements in a table using pairs.

	Parameters:
		t - The *table* to count.

	Returns:
		The *number* of elements in the table.

	Example:
		:Count( { "apple", "pear", done=true, banana="yellow" } )

		returns...

		:4

	Notes:
		* This is slow and should be avoided if at all possible.
		* Use the '#' operator instead of this if the table only contains numeric indices or if you only care about the numeric indices.
		* Use <IsEmpty> instead of this if you only want to see if a hash table has any values.
		* Complexity is O(n), where n is the number of values in t.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Count: (t using nil) ->
		c = 0
		for v in pairs t
			c += 1

		c

	[=[
	Function: IsEmpty
	Checks if a table contains any values on any type of key.

	Parameters:
		t - The *table* to check.

	Returns:
		A *boolean*, true if the table t has one or more values, false otherwise.

	Notes:
		* This is much faster than <Count> for checking if a table has any elements.
		* Complexity is O(1).

	Revisions:
		4.0.0 - Initial.
	]=]
	@IsEmpty: (t using nil) ->
		next(t) == nil

	[=[
	Function: Empty
	Removes all data from a table.

	Parameters:
		t - The *table* to empty.

	Returns:
		The *table* t.

	Notes:
		* Complexity is O(Count(t)).

	Revisions:
		4.0.0 - Initial.
	]=]
	@Empty: (t using nil) ->
		for k, v in pairs t
			t[ k ] = nil

		t

	[=[
	Function: Copy
	Make a shallow copy of a table. A shallow copy means that any subtables will still refer to the	same underlying table.

	Parameters:
		t - The *table* to make a copy of.

	Returns:
		The copied *table*.

	Notes:
		* Complexity is O(Count(t)).

	Revisions:
		4.0.0 - Initial.
	]=]
	@Copy: (t using nil) ->
		{ k, v for k, v in pairs t }

	[=[
	Function: CopyI
	Exactly the same as <Copy> except that it uses fori instead of pairs.
	In general, this means that it only copies numeric keys. See <A Discussion On fori>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@CopyI: (t using nil) ->
		[ v for v in *t ]

	[=[
	Function: DeepCopy
	Make a deep copy of a table. A deep copy means that any subtables will refer to a new copy of the original subtable.

	Parameters:
		t - The *table* to make a copy of. Must not have any cycles.

	Returns:
		The copied *table*.

	Revisions:
		4.0.0 - Initial.
	]=]
	@DeepCopy: (t using nil) ->
		{ k, (type(v) == "table" and self.DeepCopy(v) or v) for k, v in pairs t }

	InPlaceHelper = (t, inPlace using nil) ->
		if inPlace then return t
		self.Copy t

	InPlaceHelperI = (t, inPlace using nil) ->
		if inPlace then return t
		self.CopyI t

	[=[
	Function: RemoveDuplicateValues
	Removes any duplicate values from a list.

	Parameters:
		list    - The *list* to remove duplicates from.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on list itself, rather than a copy.

	Returns:
		The *list* that was passed in with duplicates removed. Returns the original table reference if in_place is true, a new table otherwise.

	Example:
		:RemoveDuplicateValues( { "apple", "pear", "kiwi", "apple", "banana", "pear", "pear" } )

		returns...
		:{ "apple", "pear", "kiwi", "banana" }

	Notes:
		* This function operates over numeric indices. See <A Discussion On fori>.
		* Complexity is around O(#list * log( #list )).
		* Duplicates are removed after the first value occurs. See example above.

	Revisions:
		4.0.0 - Initial.
	]=]
	@RemoveDuplicateValues: (list, inPlace using nil) ->
		list = InPlaceHelperI list, inPlace

		i = 2
		while i <= #list
			v = list[ i ]
			for j=1, i-1
				if list[j] == v
					table.remove list, i
					i -= 1 -- Since we removed it and it will be incremented below otherwise
					break
			i += 1

		list

	[=[
	Function: UnionByKey
	Merges two tables by key.

	Parameters:
		tableA  - The first *table* in the union.
		tableB  - The second *table* in the union.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on tableA itself, rather than a copy.

	Returns:
		The union *table*. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:UnionByKey( { apple="red", pear="green", kiwi="hairy" },
		:            { apple="green", pear="green", banana="yellow" } )

		returns...

		:{ apple="green", pear="green", kiwi="hairy", banana="yellow" }

	Notes:
		* If both tables have values on the same key, tableB takes precedence.
		* Complexity is O(Count(tableB)).

	Revisions:
		4.0.0 - Initial.
	]=]
	@UnionByKey: (tableA, tableB, inPlace using nil) ->
		tableA = InPlaceHelper tableA, inPlace

		for k, v in pairs tableB
			tableA[k] = v

		tableA

	[=[
	Function: UnionByKeyI
	Exactly the same as <UnionByKey> except that it uses fori instead of pairs. In general, this
	means that it only merges on numeric keys. See <A Discussion On fori>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@UnionByKeyI: (tableA, tableB, inPlace using nil) ->
		tableA = InPlaceHelperI tableA, inPlace

		for i=1, #tableB do
			if tableB[i] ~= nil
				tableA[i] = tableB[i]

		tableA

	[=[
	Function: Union
	Gets the union of two lists by value. If a value occurs once in listA and once in listB, the result of the union will only have one instance of that value as well.

	Parameters:
		listA - The first *list* in the union.
		listB - The second *list* in the union.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on tableA itself, rather than a copy.

	Returns:
		The union *list*. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:Union( { "apple", "pear", "kiwi" }, { "pear", "apple", "banana" } )

		returns...

		:{ "apple", "pear", "kiwi", "banana" }

	Notes:
		* This function operates over numeric indices. See <A Discussion On fori>.
		* The elements that in the returned table are in the same order they were in tableA and then tableB. See example above.
		* This function properly handles duplicate values in either list. All values will be unique in the resulting list.
		* Complexity is O( (#listA + #listB) * log( (#listA + #listB) ) )
		* You might want to consider using <SetFromList> combined with <UnionByKey> for large tables or if you plan on doing this often.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Union: (listA, listB, inPlace using nil) ->
		listA = self.Append listA, listB, inPlace
		self.RemoveDuplicateValues listA, true

	[=[
	Function: IntersectionByKey

	Gets the intersection of two tables by key.

	Parameters:
		tableA - The first *table* in the intersection.
		tableB - The second *table* in the intersection.

	Returns:
		The intersection *table*.

	Example:
		:IntersectionByKey( { apple="red", pear="green", kiwi="hairy" },
		:                   { apple="green", pear="green", banana="yellow" } )

		returns...

		:{ apple="green", pear="green" }

	Notes:
		* If both tables have values on the same key, tableB takes precedence.
		* Complexity is O(Count(tableA)).

	Revisions:
		4.0.0 - Initial.
	]=]
	@IntersectionByKey: (tableA, tableB using nil) ->
		result = {}

		-- Now just fill in each value with whatever the value in tableB is. This takes care of both
		-- elimination and making tableB take precedence when both tables have a value on key k.
		for k, v in pairs tableA
			result[k] = tableB[k]

		result

	[=[
	Function: IntersectionByKeyI
	Exactly the same as <IntersectionByKey> except that it uses fori instead of pairs. In
	general, this means that it only merges on numeric keys. See <A Discussion On fori>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@IntersectionByKeyI: (tableA, tableB using nil) ->
		result = {}

		-- Now just fill in each value with whatever the value in tableB is. This takes care of both
		-- elimination and making tableB take precedence when both tables have a value on key i.
		for i=1, #tableA
			if tableA[i] ~= nil
				result[i] = tableB[i]

		result

	[=[
	Function: Intersection
	Gets the intersection of two lists by value.

	Parameters:
		listA - The first *list* in the intersection.
		listB - The second *list* in the intersection.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on listA itself, rather than a copy.

	Returns:
		The intersection *list*. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:Intersection( { "apple", "pear", "kiwi" }, { "pear", "apple", "banana" } )

	returns...
		:{ "apple", "pear" }

	Notes:
		* This function operates over numeric indices. See <A Discussion On fori>.
		* The elements that are left in the returned table are in the same order they were in listA. See example above.
		* This function properly handles duplicate values in either list. All values will be unique in the resulting list.
		* Complexity is O(#ListA * #ListB).
		* You might want to consider using <SetFromList> combined with <IntersectionByKey> for large tables or if you plan on doing this often.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Intersection: (listA, listB, inPlace using nil) ->
		listA = self.RemoveDuplicateValues listA, inPlace

		i = 1
		while i <= #listA
			if self.HasValueI listB, listA[i]
				i += 1
			else
				table.remove listA, i

		listA

	[=[
	Function: DifferenceByKey
	Gets the difference of two tables by key. Difference is defined as all the keys in table A that are not in table B.

	Parameters:
		tableA - The first *table* in the difference.
		tableB - The second *table* in the difference.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on tableA itself, rather than a copy.

	Returns:
		The difference *table*. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:DifferenceByKey( { apple="red", pear="green", kiwi="hairy" },
		:                 { apple="green", pear="green", banana="yellow" } )

		returns...

		:{ kiwi="hairy" }

	Notes:
		* Complexity is O(Count(tableB)).

	Revisions:
		4.0.0 - Initial.
	]=]
	@DifferenceByKey: (tableA, tableB, inPlace using nil) ->
		tableA = InPlaceHelper tableA, inPlace

		for k, v in pairs tableB
			tableA[k] = nil

		tableA

	[=[
	Function: DifferenceByKeyI
	Exactly the same as <DifferenceByKey> except that it uses fori instead of pairs. In general,
	this means that it only performs the difference on numeric keys. See <A Discussion On fori>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@DifferenceByKeyI: (tableA, tableB, inPlace using nil) ->
		tableA = InPlaceHelperI tableA, inPlace

		for i=1, #tableB
			if tableB[i] ~= nil
				tableA[i] = nil

		tableA

	[=[
	Function: Difference
	Gets the difference of two lists by value.

	Parameters:
		listA - The first *list* in the difference.
		listB - The second *list* in the difference.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on tableA itself, rather than a copy.

	Returns:
		The difference *list*. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:Difference( { "apple", "pear", "kiwi" }, { "pear", "apple", "banana" } )

		returns...

		:{ "kiwi" }

	Notes:
		* This function operates over numeric indices. See <A Discussion On fori>.
		* The elements that are left in the returned table are in the same order they were in tableA. See example above.
		* This function properly handles duplicate values in either list. All values will be unique in the resulting list.
		* Complexity is O(#listA * #listB).
		* You might want to consider using <SetFromList> combined with <DifferenceByKey> for large tables or if you plan on doing this often.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Difference: (listA, listB, inPlace using nil) ->
		listA = self.RemoveDuplicateValues listA, inPlace

		for v in *listB
			hasValue, indexValue = self.HasValueI listA, v
			if hasValue
				table.remove listA, indexValue

		listA

	[=[
	Function: Append
	Appends values with numeric keys from one table to another.

	Parameters:
		listA - The first *list* in the append.
		listB - The second *list* in the append.
		inPlace - An *optional boolean*, defaults to _false_. If true, all operations occur on tableA itself, rather than a copy.

	Returns:
		The *list* result of appending tableB to tableA. Returns tableA if inPlace is true, a new table otherwise.

	Example:
		:Append( { "apple", "banana", "apple", "kiwi" },
		:        { "orange", "pear", "apple" } )

		returns...

		:{ "apple", "banana", "apple", "kiwi", "orange", "pear", "apple" }

	Notes:
		* This function uses fori. See <A Discussion On fori>.
		* Complexity is O(#listB).

	Revisions:
		4.0.0 - Initial.
	]=]
	@Append: (listA, listB, inPlace using nil) ->
		listA = InPlaceHelperI listA, inPlace

		for v in *listB
			table.insert listA, v

		listA

	[=[
	Function: HasValue
	Checks for the presence of a value in a table.

	Parameters:
		t     - The *table* to check for the value's presence within.
		value - *Any type*, the value to check for within t.

	Returns:
		1 - A *boolean*. True if the table has the value, false otherwise.
		2 - A value of *any type*. The first key the value was found under if it was found, nil otherwise.

	Example:
		:HasValue( { apple="red", pear="green", kiwi="hairy" }, "green" )

		returns...

		:true, "pear"

	Revisions:
		4.0.0 - Initial.
	]=]
	@HasValue: (t, value using nil) ->
		for k, v in pairs t
			if v == value
				return true, k

		false, nil

	[=[
	Function: HasValueI
	Exactly the same as <HasValue> except that it uses fori instead of pairs.
	In general, this means that it only merges on numeric keys. See <A Discussion On fori>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@HasValueI: (t, value using nil) ->
		for i=1, #t do
			if t[i] == value then
				return true, i

		false, nil

	[=[
	Function: SetFromList
	Creates a set from a list. A list is defined as a table with all numeric keys in sequential order (such as {"red", "yellow", "green"}).
	A set is defined as a table that only uses the boolean value true for keys that exist in the table.
	This function takes the values from the list and makes them the keys in a set, all with the value of 'true'.
	Note that you lose ordering and duplicates in the list during this conversion, but gain ease of testing for a value's existence in the table.
	One simply needs to test whether the value of a key is true or nil.

	Parameters:
		list - The *list*.

	Returns:
		The *table* representing the set.

	Example:
		:SetFromList( { "apple", "banana", "kiwi", "pear" } )

		returns...

		:{ apple=true, banana=true, kiwi=true, pear=true }

	Notes:
		* This function uses fori during the conversion process. See <A Discussion On fori>.
		* Complexity is O(#list).

	Revisions:
		4.0.0 - Initial.
	]=]
	@SetFromList: (list using nil) ->
		{ v, true for v in *list }