-- Achievements by MikuAuahDark
-- Uses custom bit encoder and table.uneval

-- Maximal 32 hooks
HOOK_STARTROUND=0x1
HOOK_ENDROUND=0x2
HOOK_JOIN=0x4
HOOK_LEAVE=0x8
HOOK_SPAWN=0x10
HOOK_KILL=0x20
HOOK_ATTACK=0x40
HOOK_ATTACK2=0x80
HOOK_RELOAD=0x100
HOOK_USE=0x200
HOOK_PROJECTILE=0x400
HOOK_MENU=0x800
HOOK_OBJECTDAMAGE=0x1000
HOOK_OBJECTKILL=0x2000
HOOK_IMAGEHITZONE=0x4000
HOOK_MOVE=0x8000
HOOK_MOVETILE=0x10000
HOOK_HIT=0x20000
HOOK_NAME=0x40000
HOOK_TEAM=0x80000
HOOK_BUY=0x100000
HOOK_WALKOVER=0x200000
HOOK_COLLECT=0x400000
HOOK_DROP=0x800000
HOOK_BUILD=0x1000000
HOOK_FLAGTAKE=0x2000000
HOOK_FLAGCAPTURE=0x4000000
HOOK_BOMBPLANT=0x8000000
HOOK_BOMBDEFUSE=0x10000000
HOOK_BOMBEXPLODE=0x20000000
HOOK_HOSTAGERESCUE=0x40000000
HOOK_VIPESCAPE=0x80000000

-- User Configuration
SaveDir="sys/lua/achievements"		-- Achievement save directory
DefaultSuccessMessage="Achievement '%s' complete!"	-- Default achievement message

-- Example how to add new achievements
--[[
table.insert(Achievements.List,{
	"Attack 1 times!",		-- Achievement name
	"Just press left mouse button! %d time(s) left",	-- Description shown
	"Yay, Achievements 'Attack 1 times!' complete! +500$",	-- Achievements message. Put nil to use default message
	"Attack_Test1",
	"Complete",
	false,
	HOOK_JOIN+HOOK_LEAVE+HOOK_ATTACK,	-- Used Hooks.
	{
		-- Function to executed on hook occurs
		[HOOK_ATTACK]=function(aid,id)
			if not Achievements[id].Attack_Test1.Complete then
				Achievements[id].Attack_Test1.Complete=true
				parse("setmoney "..id.." "..player(id,"money")+500)
			end
		end,
		[HOOK_LEAVE]=function(aid,id)
			Achievements[id].Attack_Test1.Complete=false
		end,
		[HOOK_JOIN]=function(aid,id)
			Achievements[id].Attack_Test1.Complete=false
		end
	}
})
or
table.insert(Achievements.List,{
	-- Index name isn't case sensitive
	Name="Attack 1 times!",		-- Achievement name
	Desc="Just press left mouse button!",	-- Description shown
	Success="Yay, Achievements 'Attack 1 times!' complete! +500$",	-- Achievements message. Put nil to use default message
	Variable="Attack_Test1",
	CounterVariable="Complete",
	DefaultValue=false,
	Used_Hooks=HOOK_JOIN+HOOK_LEAVE+HOOK_ATTACK,	-- Used Hooks.
	Hooks={
		-- Function to executed on hook occurs
		[HOOK_ATTACK]=function(aid,id)
			if not Achievements[id].Attack_Test1.Complete then
				Achievements[id].Attack_Test1.Complete=true
				parse("setmoney "..id.." "..player(id,"money")+500)
			end
		end,
		[HOOK_LEAVE]=function(aid,id)
			Achievements[id].Attack_Test1.Complete=false
		end,
		[HOOK_JOIN]=function(aid,id)
			Achievements[id].Attack_Test1.Complete=false
		end
	}
})
or you can even mix it
]]

-- End User Configuration

-- Don't delete those 2 lines below
loadstring("\65\99\104\105\101\118\101\109\101\110\116\115\61\123\125\32\112\114\105\110\116\40\34\65\99\104\105\101\118\101\109\101\110\116\115\32\83\99\114\105\112\116\32\98\121\32\77\105\107\117\65\117\97\104\68\97\114\107\34\41")()	-- Achievements={}
loadstring("\105\102\32\110\111\116\32\65\99\104\105\101\118\101\109\101\110\116\115\32\116\104\101\110\32\101\114\114\111\114\40\34\85\110\107\110\111\119\110\32\69\114\114\111\114\34\41\32\101\110\100")()

Achievements.List={}
Achievements.Functions={}

getmetatable("string").__index=function(_,var)
	if type(var)=="number" then
		return string.sub(_,var,var):byte() or 0
	else
		return string[var]
	end
end

function DiscoverActualName(val,name)
	for n,v in pairs(val) do
		if n:lower()==name then
			return n
		end
	end
	return ""
end

local function Analyze()
	local Hooks=0
	Achievements.Hooks={}
	for i=1,32 do
		Achievements[i]={}
	end
	for n,v in pairs(Achievements.List) do
		Hooks=OR(Hooks,v[7] or v[DiscoverActualName(v,"used_hooks")])
		for i=1,32 do
			local temp={}
			Achievements[i][v[4] or v[DiscoverActualName(v,"variable")]]=temp
			local r
			if v[6]~=nil then
				r=v[6]
			else
				r=v[DiscoverActualName(v,"defaultvalue")]
			end
			temp[v[5] or v[DiscoverActualName(v,"countervariable")]]=r
		end
	end
	for n,v in pairs(_G) do
		if n:sub(1,5)=="HOOK_" and type(v)=="number" and AND(Hooks,v)~=0 then
			Achievements.Hooks[n:sub(6,6)..n:sub(7):lower()]=function(...)	-- Create new function
				local HookNum=v
				for n,v in pairs(Achievements.List) do
					local UsedHooks=v[7] or v[DiscoverActualName(v,"used_hooks")]
					if AND(UsedHooks,HookNum)~=0 then
						(v[8] or v[DiscoverActualName(v,"hooks")])[HookNum](n,...)
					end
				end
			end
			addhook(n:sub(6):lower(),"Achievements.Hooks."..n:sub(6,6)..n:sub(7):lower())
		end
	end
	Achievements.Functions.Init=nil
end

function Unsigned_4(v)
	return v%2^32
end

function BitEnc_4(v)
	v=Unsigned_4(math.floor(v+0.5))
	local bit=""
	local t=v
	local mul=2^31
	local addr=0
	while t>0 do
		addr=addr+1
		if t>=mul then
			t=t-mul
			bit=bit.."1"
		else
			bit=bit.."0"
		end
		mul=math.floor(mul/2)
	end
	if addr<32 then bit=bit..string.rep("0",32-addr) end
	return bit
end

function OR(n,v)
	local b1,b2,br=BitEnc_4(n),BitEnc_4(v),""
	for i=1,32 do
		if b1[i]==49 or b2[i]==49 then
			br=br.."1"
		else
			br=br.."0"
		end
	end
	return tonumber(br,2)
end

function AND(n,v)
	local b1,b2,br=BitEnc_4(n),BitEnc_4(v),""
	for i=1,32 do
		if b1[i]==49 and b2[i]==49 then
			br=br.."1"
		else
			br=br.."0"
		end
	end
	return tonumber(br,2)
end

function table.uneval(t,l)
	local str="{\r\n"
	local redo=table.uneval
	l=l or 1
	for n,v in pairs(t) do
		local tostring=tostring
		if v==t then error("infinite-loop table detected!") end
		if tonumber(n) then
			str=str..string.rep("\t",l).."["..n.."]="
		elseif tostring(n):find("[^A-z_-]") then
			str=str..string.rep("\t",l).."[\""..n.."\"]="
		else
			str=str..string.rep("\t",l)..n.."="
		end
		local ty=type(v)
		if ty=="table" then str=str..redo(v,l+1)..",\r\n"
		elseif ty=="string" then str=str.."\""..v:gsub("\"","\\\"").."\",\r\n"
		elseif ty=="boolean" then str=str..tostring(v)..",\r\n"
		elseif ty=="number" then str=str..v..",\r\n"
		else str=str.."\""..tostring(v).."\",\r\n" end
	end
	return str..string.rep("\t",l-1).."}"
end

function Achievements.Functions.List(id)
	local temp={}
	for n,v in pairs(Achievements.List) do
		table.insert(temp,{
			v[1] or v[DiscoverActualName(v,"name")],
			string.format(v[2] or v[DiscoverActualName(v,"desc")],Achievements[id][v[4] or v[DiscoverActualName(v,"variable")]][v[5] or v[DiscoverActualName(v,"countervariable")]])
		})
	end
	return temp
end

function Achievements.Functions.Save(id)
	if player(id,"usgn")>0 then
		local f=assert(io.open(SaveDir..(SaveDir:sub(-1)=="/" and "" or "/")..player(id,"usgn")..".txt","w"))
		f:write(table.uneval(Achievements[id]))
		f:close()
	end
end

function Achievements.Functions.Load(id)
	if player(id,"usgn")>0 then
		local f=io.open(SaveDir.."/"..player(id,"usgn")..".txt","r")
		if f then
			Achievements[id]=loadstring("return "..f:read("*a"))()
			f:close()
		end
	end
end

function Achievements.Functions.Done(aid,id)
	local temp=Achievements.List[aid]
	local name=temp[1] or temp[DiscoverActualName(temp,"name")]
	msg2(id,"\169000255000"..name..":: "..(temp[2] or temp[DiscoverActualName(temp,"desc")]))
	msg2(id,"\169000255000"..string.format(temp[3] or temp[DiscoverActualName(temp,"success")] or DefaultSuccessMessage,name))
	Achievements.Functions.Save(id)
end

-- End
Achievements.Functions.Init=Analyze
return Analyze