-- local JSON = require( "component/dkjson")  --系统自带json
-- local sha = require("libraries/sha")

if Service == nil then
	Service = class({})
end


-- 配置项 start
	SERVER_KEY = GetDedicatedServerKeyV2("mydota")
	-- CustomNetTables:SetTableValue("CustomGameInfo", "ServerKey", {server_key = SERVER_KEY})
	SERVER_KEY = "9371E7F05AF5B2AB8F7B8D11EE95E8FAF13CA299" 

	REQUEST_TIME_OUT = 30

	Service.Config = table.loadkv("scripts/npc/Config.txt")

	-- 游戏配置项
	GameRules.GameData = {		
		ver = "S7",				
		admin = Service.Config.admin,		
		max_food = 12,
		code = "",  -- 0000为正常完成，其它为错误发生
		msg = "",	-- 出错消息
		game_code = "",	
		game_msg = "",
		luck_card = "",
		luck_crit = 0,
		new_card_list = "",					
		open_day_list = "",
		is_open_day = 0,
		server_time = GetRealDateTime(),			
	}	

-- endregion


-- 通用方法 start

	-- 通用请求，fTimeout超时秒，不传则默认；hFunc(iStatusCode, sBody)
	function Service:Request(method, url, hParams, hFunc, fTimeout)    
		local handle = CreateHTTPRequestScriptVM(method, url) -- 需要放在Load阶段后，即DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP及以后阶段
		handle:SetHTTPRequestHeaderValue("Content-Type", "application/json;charset=uft-8")
		if hParams == nil then hParams = {} end
		hParams.randomCode = tostring(RandomInt(1, 99999999))
		hParams.serverKey = SERVER_KEY	
		hParams.gameCode = self.Config.gameCode
		if method == "get" or method == "GET" then
			for k,v in pairs(hParams) do
				handle:SetHTTPRequestGetOrPostParameter(tostring(k), tostring(v))
			end
		else
			handle:SetHTTPRequestRawPostBody("application/json", json.encode(hParams))
		end
		handle:SetHTTPRequestAbsoluteTimeoutMS((fTimeout or REQUEST_TIME_OUT) * 1000)
		handle:Send(function(response)
			hFunc(response.StatusCode, response.Body)
		end)
	end

	-- 同步模式通用请求，需在 coroutine.wrap 中调用。
	function Service:RequestSync(method, url, hParams, fTimeout)
		local co = coroutine.running()
		self:Request(method, url, hParams, function(iStatusCode, sBody)
			coroutine.resume(co, iStatusCode, sBody)
		end, fTimeout)
		return coroutine.yield()
	end

	function Service:Get(url, params, func)
		local req = CreateHTTPRequestScriptVM("GET", url)  -- 需要放在Load阶段后，即DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP及以后阶段
		if params == nil then params = {} end
		params.randomCode = RandomInt(1, 99999999)
		params.serverKey = SERVER_KEY	
		params.gameCode = self.Config.gameCode
		for k,v in pairs(params) do
			req:SetHTTPRequestGetOrPostParameter(tostring(k), tostring(v))
		end      
		req:SetHTTPRequestAbsoluteTimeoutMS((REQUEST_TIME_OUT or 60) * 1000)
		req:Send(function(response)
			func(response.StatusCode, response.Body)
		end)
	end

	function Service:Post(url, data, func)
		local req = CreateHTTPRequestScriptVM("POST", url)  -- 需要放在Load阶段后，即DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP及以后阶段
		req:SetHTTPRequestHeaderValue("Content-Type", "application/json;charset=uft-8")
		if data == nil then data = {} end	
		data.serverKey = SERVER_KEY	
		data.gameCode = self.Config.gameCode		  
		req:SetHTTPRequestRawPostBody("application/json", json.encode(data))
		req:SetHTTPRequestAbsoluteTimeoutMS((REQUEST_TIME_OUT or 60) * 1000)
		req:Send(function(response)			
			func(response.StatusCode, response.Body)
		end)
	end

-- endregion


-- 编码 start

    -- 卡组数据明细解码，hex编码的卡组名称#卡牌及数量#卡牌及数量#...，卡牌及数量为卡牌物品后4位加数量，如00011表示item_0001，数量1个。
    -- 输入 string： e68891e79a84e58da1e7bb84#20084#00023#00801#00922#000410#00571#20044#20194#20204
    -- 返回 data : {"name":"我的卡组","item_2008":4,"item_0002":3,"item_0080":1,"item_0092":2,...}
    function Service:decodeCardGroup(cardString) 
        local retData = {}
        if cardString == nil or cardString == "" then return retData end
        local a = string.split(cardString, "#")
        if a == nil or #a < 2 then return retData end
        retData["name"] = string.fromhex(a[1])
        for i=2, #a do	
            retData["item_"..string.sub(a[i],1,4)] = tonumber(string.sub(a[i],5,6))
        end
        return retData
    end

    -- 卡组数据明细编码，hex编码的卡组名称#卡牌及数量#卡牌及数量#...，卡牌及数量为卡牌物品后4位加数量，如00011表示item_0001，数量1个。
    -- 输入data = {"name":"我的卡组","item_2008":4,"item_0002":3,"item_0080":1,"item_0092":2,...}
    -- 返回string: e68891e79a84e58da1e7bb84#20084#00023#00801#00922#000410#00571#20044#20194#20204#00583#20174#00362#20074#00522#20154#20241#20014#20064#00885#20231#00077#20164#00068#00973#00341#00962#20021#00162#00412
    function Service:encodeCardGroup(cardGroup) 
        local retString = ""
        if cardGroup["name"] ~= nil then retString = string.tohex(cardGroup["name"]) end
        for k,v in pairs(cardGroup) do 
            if k ~= "name" then
                retString = retString.."#"..string.sub(k,6,9)..tostring(v)        
            end
        end
        return retString 
    end

    -- 等级解码
    function Service:decodeCardLevel(cardString) 
        local retData = {}
        if cardString == nil or cardString == "" then return retData end
        local a = string.split(cardString, "#")
        if a == nil or #a < 1 then return retData end   
        for i=1, #a do	
            retData["item_0"..string.sub(a[i],1,3)] = tonumber(string.sub(a[i],4,9))
        end
        return retData
    end

    -- 等级编码
    function Service:encodeCardLevel(cardLevel) 
        local retString = ""    
        for k,v in pairs(cardLevel) do   
            if retString == "" then
                retString = string.sub(k,7,9)..tostring(v) 
            elseif v ~= nil then 
                retString = retString.."#"..string.sub(k,7,9)..tostring(v)
            end     
        end
        return retString 
    end

-- endregion


-- 加载游戏数据 start

	-- 获取游戏用户数据，按顺序
	function Service:LoadGameData()	
		coroutine.wrap(function()
			print("----> start request game config : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
			local iStatusCode, sBody = Service:RequestSync("get", self.Config.urlGameConfig)
			print("<---- end request game config : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100)) 
			self:InitGameConfig(iStatusCode, sBody)
			
			local input = {method = "get", ids = {}}  
			for i=0, PlayerResource:GetPlayerCount()-1 do
				input.ids[i] = tostring(PlayerResource:GetSteamID(i))
			end			
			print("----> start request player data : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
			local iStatusCode, sBody = Service:RequestSync("post", self.Config.urlPlayerData, input)
			print("<---- end request player data : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100)) 
			self:InitPlayerData(iStatusCode, sBody, input)
		end)()
	end

	-- 游戏配置
	function Service:InitGameConfig(iStatusCode, sBody)	      
		if iStatusCode == 200 then               
			local data = json.decode(sBody)                
			if data then            
				GameRules.GameData.code = data.code
				GameRules.GameData.msg = data.msg                  
				GameRules.GameData.game_code = data.game_code or ""
				GameRules.GameData.game_msg = data.game_msg or ""
				GameRules.GameData.luck_card = data.luck_card or ""
				GameRules.GameData.luck_crit = data.luck_crit or 0
				GameRules.GameData.new_card_list = data.new_card_list or ""
				GameRules.GameData.open_day_list = data.open_day_list or ""
				GameRules.GameData.is_open_day = data.is_open_day or 0
				GameRules.GameData.server_time = data.server_time				
			else
				GameRules.GameData.code = "0003"
				GameRules.GameData.msg = "游戏配置数据解析失败"				
			end
		else
			GameRules.GameData.code = "0004"
			GameRules.GameData.msg = "无法访问服务器"			
		end

		CustomNetTables:SetTableValue("CustomGameInfo", "GameData", GameRules.GameData)
	end

	-- 玩家数据
	function Service:InitPlayerData(iStatusCode, sBody, input)	   
		if iStatusCode == 200 then            
			local data = json.decode(sBody)			
			if data then 
				for k,v in pairs(data.bo) do 
					for i=1,20 do
						v["cardgroup"..i] = self:decodeCardGroup(v["cardgroup"..i])
					end
					v["level_list"] = self:decodeCardLevel(v["level_list"])

					local index = tonumber(k)
					for key,value in pairs(v) do 
						GameRules.PlayerData[index][key] = value
					end										
				end                     
			else
				for k,v in pairs(input.ids) do					
					GameRules.PlayerData[k].code = "0003"
					GameRules.PlayerData[k].msg = "用户数据解析失败"
				end
			end
		else
			for k,v in pairs(input.ids) do				
				GameRules.PlayerData[k].code = "0004"
				GameRules.PlayerData[k].msg = "无法访问服务器"
			end
		end     

		for k,v in pairs(input.ids) do
			CustomNetTables:SetTableValue("CustomGameInfo", "PlayerData_"..v, GameRules.PlayerData[k])
			-- print("--------- PlayerData "..tostring(k))
			-- DeepPrintTable(GameRules.PlayerData[k])
		end
	end

-- endregion


-- 加载排行榜数据 start

	-- 获取排行榜通用方法
	function Service:GetRank(rankType)		
		-- 初始化
		if GameRules.players_rank_ok == nil then 
			GameRules.players_rank_ok = false  	-- 是否载入成功

			GameRules.players_rank = {}			-- 排行榜基本数据
			GameRules.players_rank_data = {}	-- 排行榜明细数据

			GameRules.players_team_rank = {}
			GameRules.players_team_rank_data = {}
		end	

		print("----> start request get rank : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
	
		local input = {}
		input["rank_type"] = rankType or "all"  --或取值SDT中一个， S 原始榜，D 巅峰榜，T 挑战榜
		
		self:Get(
			self.Config.urlRankData,    
			input,
			function(iStatusCode, sBody)         
				print("<---- end request get rank : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)  					
					if data then         
						if data.code == "0000" then                        
							GameRules.players_rank_ok = true

							local datas = data.bo["S"]
							if #datas > 0 then
								local ranks = {}
								for i = 1, #datas do
									ranks[i] = {                            
										['steamid'] = datas[i].steamid,
										['userid'] = datas[i].userid,
										['username'] = string.fromhex(datas[i].username),
										['wave'] = datas[i].wave,
										['damage'] = datas[i].damage,
										['version'] = datas[i].version,
										['host'] = datas[i].host,										
										['updatetime'] = datas[i].update_time                                                                          
									}
									for k,v in pairs(datas[i]) do
										if string.sub(k,1,4) == "card" then
											datas[i][k] = json.decode(string.fromhex(v))
										end
									end

									for id=0, PlayerResource:GetPlayerCount()-1 do
										if PlayerResource:IsValidPlayerID(id) then	
											if GameRules.PlayerData[id].steamid == datas[i].steamid then 
												GameRules.PlayerData[id].max_wave = datas[i].wave
												print("----- 玩家"..tostring(id).."原始榜波数："..tostring(datas[i].wave))
												break
											end											 
										end
									end	
								end 
								GameRules.players_rank = ranks     
								GameRules.players_rank_data = datas   
								ranks = {} 
								datas = {}
								CustomNetTables:SetTableValue("CustomGameInfo", "PlayersRank", GameRules.players_rank)
							end

							local datas = data.bo["D"]
							if #datas > 0 then
								local ranks = {}
								for i = 1, #datas do
									ranks[i] = {                            
										['steamid'] = datas[i].steamid,
										['userid'] = datas[i].userid,
										['username'] = string.fromhex(datas[i].username),
										['wave'] = datas[i].wave,
										['damage'] = datas[i].damage,
										['version'] = datas[i].version,
										['host'] = datas[i].host,										
										['updatetime'] = datas[i].update_time                                                                          
									}
									for k,v in pairs(datas[i]) do
										if string.sub(k,1,4) == "card" then
											datas[i][k] = json.decode(string.fromhex(v))
										end
									end

									for id=0, PlayerResource:GetPlayerCount()-1 do
										if PlayerResource:IsValidPlayerID(id) then	
											if GameRules.PlayerData[id].steamid == datas[i].steamid then 
												GameRules.PlayerData[id].max_team_wave = datas[i].wave
												print("----- 玩家"..tostring(id).."巅峰榜波数："..tostring(datas[i].wave))
												break
											end											 
										end
									end	
								end 
								GameRules.players_team_rank = ranks     
								GameRules.players_team_rank_data = datas   
								ranks = {} 
								datas = {}
								CustomNetTables:SetTableValue("CustomGameInfo", "PlayersTeamRank", GameRules.players_team_rank)
							end
						else
							CustomGameEventManager:Send_ServerToAllClients("thtd_server_msg", {code = "0005", msg = data.msg, duration = 10, pos = 200})    
						end
					else                                      
						CustomGameEventManager:Send_ServerToAllClients("thtd_server_msg", {code = "0003", msg = "获取排行榜数据出错，本次游戏将无法上传原始榜，数据解析失败", duration = 10, pos = 200}) 
					end
				else                           
					CustomGameEventManager:Send_ServerToAllClients("thtd_server_msg", {code = "0004", msg = "获取排行榜数据出错，本次游戏将无法上传原始榜，StatusCode: "..tostring(iStatusCode), duration = 10, pos = 200})
				end     
			end
		)
	end

	-- 重置排行榜
	function Service:ResetRank(index, rankType)	
		print("----> start request reset rank : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))		
		
		local input = {method = "reset"}
		input["rank_type"] = rankType
		if rankType == "S" then
			input["steamid"] = GameRules.players_rank[index].steamid
			input["userid"] = GameRules.players_rank[index].userid
		elseif rankType == "D" then
			input["steamid"] = GameRules.players_team_rank[index].steamid
			input["userid"] = GameRules.players_team_rank[index].userid
		else
			return
		end
		
		self:Post(
			self.Config.urlRankData,    
			input,
			function(iStatusCode, sBody)     
				print("<---- end request reset rank : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)					
					if data then         
						if data.code == "0000" then
							if rankType == "S" then
                				CustomGameEventManager:Send_ServerToAllClients("thtd_reset_rank", {index = index, msg = ""})
							elseif rankType == "D" then
								CustomGameEventManager:Send_ServerToAllClients("thtd_reset_team_rank", {index = index, msg = ""})   
							end
						else
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = data.code, msg = "重置排行榜失败，"..data.msg, duration = 5, pos = 100})				
						end						
					else     
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0003", msg = "重置排行榜失败，无法解析数据", duration = 5, pos = 100}) 
					end
				else    
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0004", msg = "重置排行榜失败，无法连接服务器", duration = 5, pos = 100}) 
				end     
			end
		)
	end

	-- 上传排行榜
	function Service:UploadRank(playerId, rankType, wavedata)	
		print("----> start request upload rank : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))		
		
		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "update"}
		input["rank_type"] = rankType
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))
		input["username"] = string.tohex(PlayerResource:GetPlayerName(playerId))
		input["version"] = GameRules.GameData.ver 
		input["wave"] = wavedata["wave"]
		input["damage"] = wavedata["damage"]		
		for k,v in pairs(wavedata) do
			if string.sub(k,1,4) == "card" then            
				input[k] = string.tohex(json.encode(v))
			end
		end		
		local teamText = ""
        if rankType == "D" then 
			teamText = "巅峰" 
		elseif rankType == "S" then 
			teamText = "原始"
		end

		local bonus = 10
		if wavedata["rank_index"] <= 4 then 
			bonus = 500 - (wavedata["rank_index"] - 1) * 100
		elseif wavedata["rank_index"] <= 9 then 
			bonus = 170 - (wavedata["rank_index"] - 5) * 10
		elseif wavedata["rank_index"] <= 15 then 
			bonus = 125 - (wavedata["rank_index"] - 10) * 5
		elseif wavedata["rank_index"] <= 25 then 
			bonus = 96 - (wavedata["rank_index"] - 16) * 4
		elseif wavedata["rank_index"] <= 50 then 
			bonus = 58 - (wavedata["rank_index"] - 26) * 2
		end		
		CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0000", msg = "恭喜上榜，排名：".. wavedata["rank_index"] .."，此排名赛季结算奖励：符卡精华 ×"..tostring(bonus), duration = 30, pos = -100})	

		self:Post(
			self.Config.urlRankData,    
			input,
			function(iStatusCode, sBody)     
				print("<---- end request upload rank : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)					
					if data then         
						if data.code == "0000" then 							
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0000", msg = "恭喜！你打破了历史记录，达到了新的最高波数，上传"..teamText.."排行榜成功!", duration = 30})	
                    		if SpawnSystem:GetCount() == 0 then Service:ClearSameRank() end
						else
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = data.code, msg = "上传排行榜失败，"..data.msg, duration = 10})				
						end						
					else     
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0003", msg = "上传排行榜失败，无法解析数据", duration = 10}) 
					end
				else    
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0004",msg = "上传排行榜失败，无法连接服务器", duration = 10}) 
				end     
			end
		)
	end

	-- 清除相同的排行榜
	function Service:ClearSameRank()
		if GameRules.GameData.is_clear_rank == true then return end
		GameRules.GameData.is_clear_rank = true 

		local toplist
		local isTeam = GameRules.HeroList[0].is_team_mode
		if isTeam == true then
			toplist = GameRules.players_team_rank_data	
		else
			toplist = GameRules.players_rank_data
		end
		if #toplist == 0 then return end

		local toDelList = {}   
		for i=1,#toplist-1 do		
			local wavedata = toplist[i]
			if table.hasvalue(toDelList, i) == false then 
				for j=i+1,#toplist do
					local topdata = toplist[j]  
					local isSameRank = false                

					if wavedata["card1"] ~= "" and wavedata["card1"] ~= nil and topdata["card1"] ~= "" and topdata["card1"] ~= nil and wavedata["card1"]["itemname"] == topdata["card1"]["itemname"] then
						if wavedata["card1"]["damage"] / (10000 * wavedata["damage"]) >= 0.6 and topdata["card1"]["damage"] / (10000 * topdata["damage"]) >= 0.6 then
							isSameRank = true
						end						
					end

					if isSameRank == false then                    
						local topDamage1 = 0
						local topDamage2 = 0
						local topNum = 0
						for x=1,12 do
							if wavedata["card"..tostring(x)] ~= "" and wavedata["card"..tostring(x)] ~= nil then
								topDamage1 = topDamage1 + wavedata["card"..tostring(x)]["damage"]
							end
							if topdata["card"..tostring(x)] ~= "" and topdata["card"..tostring(x)] ~= nil then
								topDamage2 = topDamage2 + topdata["card"..tostring(x)]["damage"]
							end						
							if topDamage1 / (10000 * wavedata["damage"]) >= 0.8 and topDamage2 / (10000 * topdata["damage"]) >= 0.8 then
								topNum = x
								break
							end					
						end
						if topNum > 0 then
							local sameCount = 0
							local usedIndex = ""
							for x=1,topNum do
								for y=1,topNum do
									if string.find(usedIndex,tostring(y)) == nil and wavedata["card"..tostring(x)] ~= "" and topdata["card"..tostring(y)] ~= "" and topdata["card"..tostring(y)] ~= nil and wavedata["card"..tostring(x)]["itemname"] == topdata["card"..tostring(y)]["itemname"] then
										sameCount = sameCount + 1
										usedIndex = usedIndex..tostring(y)..","
										break
									end					
								end
							end	
							if sameCount == topNum then isSameRank = true end
						end
					end 

					if isSameRank == true then
						table.insert(toDelList, j)				
					end 
				end	
			end
		end

		for k,v in pairs(toDelList) do
			if isTeam == true then
				Service:ResetRank(v, "D")				
			else
				Service:ResetRank(v, "S")
			end
		end
		print("---------- the same rank : ", json.encode(toDelList))   
	end


-- endregion


-- 玩家数据 start

	-- 保存玩家卡组
	function Service:SaveCardGroup(playerId, groupKey, groupData)
		print("----> start request save card group : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
		
		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "save_card_group"}
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))
		input["group_key"] = groupKey
		input["group_data"] = self:encodeCardGroup(groupData)  

		self:Post(
			self.Config.urlPlayerData,    
			input,
			function(iStatusCode, sBody)           
				print("<---- end request save card group : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)					
					if data then         
						if data.code == "0000" then 
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_save_cardgroup", {code = data.code, msg = ""})
                    		GameRules.PlayerData[playerId][groupKey] = groupData
                    		CustomNetTables:SetTableValue("CustomGameInfo", "PlayerData_"..steamid, GameRules.PlayerData[playerId])          
						else
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_save_cardgroup", {code = data.code, msg = data.msg})
						end
					else                                      
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_save_cardgroup", {code = "0005", msg = "数据解析失败："..sBody})
					end
				else                           
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_save_cardgroup", {code = "0009", msg = "无法连接服务器，StatusCode: "..tostring(iStatusCode)})
				end     
			end
		)
	end

	-- 获取玩家基础数据
	function Service:GetPlayerBaseData(playerId)
		local steamid = tostring(PlayerResource:GetSteamID(playerId))   
		print("----> start request player base data : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
		self:Get(
			self.Config.urlPlayerBaseData,    
			{id = steamid},
			function(iStatusCode, sBody)
				print("<---- end request player base data : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)                
					if data then
						if data.code == "0000" then 
							self:UpdatePlayerData(playerId, data)
						end
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_update_player_data", data)						
					else
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_update_player_data", {code = "0003", msg = "返回数据解析失败"}) 						
					end
				else
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_update_player_data", {code = "0004", msg = "无法连接服务器，StatusCode: "..tostring(iStatusCode)})     
				end
			end
		)
	end

	-- 更新玩家基础数据信息
	function Service:UpdatePlayerData(playerId, data)
		GameRules.PlayerData[playerId].point = data.point
		GameRules.PlayerData[playerId].level_list = self:decodeCardLevel(data.level_list)							
		GameRules.PlayerData[playerId].pet_level = data.pet_level
		GameRules.PlayerData[playerId].end_time = data.end_time
		GameRules.PlayerData[playerId].key_total = data.key_total
		GameRules.PlayerData[playerId].key_save_date = data.key_save_date
		GameRules.PlayerData[playerId].vip = data.vip

		if GameRules:State_Get() == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then 
			-- 更新宠物等级
			local hero = GameRules.HeroList[playerId]
			local pet = hero.reimu_pet
			if pet ~= nil and pet.pet_level ~= data.pet_level then 
				local oldLevel = pet.pet_level
				pet.pet_level = data.pet_level
				pet:FindModifierByName("modifier_reimu_pet_01_level"):SetStackCount(data.pet_level)
				pet:CreatureLevelUp(data.pet_level - oldLevel)
			end

			-- 更新钥匙数量
			local item = hero:FindItemInInventory("item_3121")			
			if item ~= nil then 
				if data.key_total ~= item:GetCurrentCharges() then 
					item:SetCurrentCharges(data.key_total)
					if data.key_total <= 0 then 
						hero:RemoveItem(item)
					end
				end				
			elseif data.key_total > 0 then 
				item = CreateItem("item_3121", nil, nil) 					
				if item ~= nil then	
					item.owner_player_id = playerId
					item:SetPurchaser(hero)
					item:SetCurrentCharges(data.key_total)
					hero:AddItem(item)										
				end
			end	
		end
		
		CustomNetTables:SetTableValue("CustomGameInfo", "PlayerData_"..tostring(PlayerResource:GetSteamID(playerId)), GameRules.PlayerData[playerId])
	end

	-- 培养卡牌
	function Service:SaveCardLevel(playerId, card, level)
		print("----> start request save card group : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
		
		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "save_card_level"}
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))
		input["card"] = card  --item_0001
		input["level"] = level --增加等级
	
		self:Post(
			self.Config.urlPlayerData,    
			input,
			function(iStatusCode, sBody)     
				print("<---- end request save card group : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)					
					if data then         
						if data.code == "0000" then 							
							GameRules.PlayerData[playerId].point = GameRules.PlayerData[playerId].point - data.cost_point
							GameRules.PlayerData[playerId]["level_list"][card] = (GameRules.PlayerData[playerId]["level_list"][card] or 0) + level							              
							CustomNetTables:SetTableValue("CustomGameInfo", "PlayerData_"..steamid, GameRules.PlayerData[playerId])
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_update_game_point", {point = GameRules.PlayerData[playerId].point})
							data.level_list = GameRules.PlayerData[playerId]["level_list"]
						end
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_train_card_complete", data) 
					else                                      
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_train_card_complete", {code = "0003", msg = "返回数据解析失败"}) 	
					end
				else                           
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_train_card_complete", {code = "0004", msg = "无法连接服务器，StatusCode: "..tostring(iStatusCode)}) 
				end     
			end
		)
	end

	-- 保存宠物
	function Service:SavePetCustom(playerId)
		print("----> start request save pet custom : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
		
		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "save_pet_custom"}
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))
		input["pet_model"] = GameRules.PlayerData[playerId].pet_model
		input["pet_effect"] = GameRules.PlayerData[playerId].pet_effect
	
		self:Post(
			self.Config.urlPlayerData,    
			input,
			function(iStatusCode, sBody)     
				print("<---- end request save pet custom : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)					
					if data then         
						if data.code == "0000" then 							
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "show_message", {msg="pet_cumstom_saved", duration=15, params={}, color="#ff0"})	
						else
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = data.code, msg = data.msg}) 					
						end						
					else     
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "display_custom_error", {msg="user_server_error"})
					end
				else    
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "display_custom_error", {msg="connect_server_error"})
				end     
			end
		)
	end

	-- 购买魔法钥匙
	function Service:BuyMagicKey(playerId, count)
		print("----> start request buy magic key : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
		
		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "buy_key"}
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))
		input["count"] = count  
	
		self:Post(
			self.Config.urlPlayerData,    
			input,
			function(iStatusCode, sBody)     
				print("<---- end request buy magic key : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)					
					if data then         
						if data.code == "0000" then 							
							GameRules.PlayerData[playerId]["point"] = GameRules.PlayerData[playerId]["point"] - data.cost_point
							CustomNetTables:SetTableValue("CustomGameInfo", "PlayerData_"..steamid, GameRules.PlayerData[playerId])
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_update_game_point", {point = GameRules.PlayerData[playerId]["point"]})                           
								
							local hero = GameRules.HeroList[playerId]
							local item = hero:FindItemInInventory("item_3121")			
							if item ~= nil then 
								item:SetCurrentCharges(item:GetCurrentCharges() + count)						
							else
								item = CreateItem("item_3121", nil, nil) 					
								if item ~= nil then	
									item.owner_player_id = playerId							
									item:SetPurchaser(hero)
									item:SetCurrentCharges(count)
									hero:AddItem(item)									
								end
							end	
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "show_message", {msg="key_made_tip", duration=3, params={}, color="#ff0"})	
							ParticleManager:DestroyParticleSystem(hero.thtd_emoji_effect,true)
							hero.thtd_emoji_effect = ParticleManager:CreateParticle("particles/thtd/emoji/thtd_msg_hongliange.vpcf", PATTACH_OVERHEAD_FOLLOW, hero) 
							ParticleManager:SetParticleControlEnt(hero.thtd_emoji_effect , 0, hero, PATTACH_OVERHEAD_FOLLOW, "attach_hitloc", Vector(0,0,0), true)  --灵梦用 attach_emoji
							ParticleManager:SetParticleControl(hero.thtd_emoji_effect, 3, Vector(1,0,0))
							ParticleManager:DestroyParticleSystemTime(hero.thtd_emoji_effect,5.0)	
						else
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = data.code, msg = "购买钥匙失败，"..data.msg}) 					
						end						
					else     
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "display_custom_error", {msg="user_server_error"})
					end
				else    
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "display_custom_error", {msg="connect_server_error"})
				end     
			end
		)
	end

	-- 保存魔法钥匙
	function Service:SaveKeyCount(playerId)
		if GameRules.PlayerData[playerId].code ~= "0000" then 
			return 
		end

		local keyCount = 0		
		local hero = GameRules.HeroList[playerId]		
		local itemTpSlot = hero:GetItemInSlot(DOTA_ITEM_TP_SCROLL) --15
		if itemTpSlot then			
			keyCount = itemTpSlot:GetCurrentCharges()
		end
		if keyCount == GameRules.PlayerData[playerId]["key_total"] then
			return 
		end

		print("----> start request save key count : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
		
		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "save_key_count"}
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))
		input["key_total"] = keyCount
		input["key_use_count"] = GameRules.PlayerData[playerId]["key_use_count"]
	
		self:Post(
			self.Config.urlPlayerData,    
			input,
			function(iStatusCode, sBody)     
				print("<---- end request save key count : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)					
					if data then         
						if data.code == "0000" then 							
							GameRules.PlayerData[playerId]["key_total"] = keyCount
						else
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = data.code, msg = "保存钥匙数量失败，"..data.msg, duration = 10, pos = 150})				
						end						
					else     
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0003", msg = "保存钥匙数量失败，无法解析数据", duration = 10, pos = 150}) 
					end
				else    
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0004", msg = "保存钥匙数量失败，无法连接服务器", duration = 10, pos = 150}) 
				end     
			end
		)
	end

	-- 保存凤凰之灵
	function Service:SavePowerMaxCount(playerId, isAdd)	
		print("----> start request save power max count : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
		
		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "save_power_max"}
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))
		if isAdd == true then 
			input["op"] = "add"
		else 
			input["op"] = "sub"
		end
	
		self:Post(
			self.Config.urlPlayerData,    
			input,
			function(iStatusCode, sBody)     
				print("<---- end request save power max count : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)					
					if data then         
						if data.code == "0000" then 							
							print("----- 玩家"..tostring(playerId).."的凤凰之灵已保存")
						else
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = data.code, msg = "保存凤凰之灵失败，"..data.msg, duration = 10})				
						end						
					else     
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0003", msg = "保存凤凰之灵失败，无法解析数据", duration = 10}) 
					end
				else    
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0004", msg = "保存凤凰之灵失败，无法连接服务器", duration = 10}) 
				end     
			end
		)
	end

	-- 首胜奖励
	function Service:GiveDayFirstWinBonus(playerId)
		print("----> start request day first win : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
    
		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "day_first_win"}
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))	
	
		self:Post(
			self.Config.urlPlayerData,    
			input,
			function(iStatusCode, sBody)     
				print("<---- end request day first win : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)					
					if data then         
						if data.code == "0000" then
							GameRules.PlayerData[playerId]["point"] = GameRules.PlayerData[playerId]["point"] + data.bonus_point
							CustomNetTables:SetTableValue("CustomGameInfo", "PlayerData_"..steamid, GameRules.PlayerData[playerId])
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_update_game_point", {point = GameRules.PlayerData[playerId]["point"]})
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0000", msg = "获得 符卡精华 x"..tostring(data.bonus_point)})
						else
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = data.code, msg = "每日开箱奖励失败，"..data.msg})				
						end						
					else     
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0003", msg = "每日开箱奖励失败，无法解析数据"}) 
					end
				else    
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_server_msg", {code = "0004", msg = "每日开箱奖励失败，无法连接服务器"}) 
				end     
			end
		)
	end


-- endregion


-- 赞助 start

	-- 创建订单
	function Service:CreateShopOrder(playerId, payType, amount, price, goods)
		print("----> start request buy order : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))   
		
		local current = 0
		local tips = ""
		if goods == "fairy" then 
			current = GameRules.PlayerData[playerId].point
			tips = "获得：符卡精华 ×"..tostring(amount)
		elseif goods == "pet" then 
			current = GameRules.PlayerData[playerId].pet_level
			tips = "宠物等级提升 "..tostring(amount).." 级，特别奖励时间增加 "..tostring(amount*2).." 个月"
		end

		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "create_shop_order"}
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))
		input["pay_type"] = payType  -- 1支付宝，2微信
		input["amount"] = amount  -- 订单数量
		input["price"] = price  -- 价格，整数，分
		input["goods"] = goods  -- 商品
		input["current"] = current  -- 当前数量

		GameRules.PlayerData[playerId].order_tips = "没有生成订单"

		self:Post(
			self.Config.urlPlayerData,    
			input,
			function(iStatusCode, sBody)           
				print("<---- end request buy order : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))            
				if iStatusCode == 200 then               
					local data = json.decode(sBody)  					
					if data then         
						if data.code == "0000" then       
							GameRules.PlayerData[playerId].order_no = data.order_no
							GameRules.PlayerData[playerId].order_tips = tips
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_pay_post", {code = 1, method = payType})

							local time = 45	
							local tick = 1.5				
							GameRules:GetGameModeEntity():SetContextThink(DoUniqueString("Query_Order_State"),
								function()
									if GameRules.PlayerData[playerId].order_no == nil then return nil end									
									if time < 0 then return nil end                           
									self:PayAutoCheck(playerId)
									time = time - tick
									return tick
								end,
							tick)
						else
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_pay_post", {code = 505, msg = data.msg})  
						end
					else                                      
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_pay_post", {code = 505, msg = "无法解析数据，"..sBody})  
					end
				else                           
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_pay_post", {code = 201, msg = "无法连接，StatusCode: "..tostring(iStatusCode)})  
				end     
			end
		)
	end

	-- 支付检查，自动模式
	function Service:PayAutoCheck(playerId)	
		
		if GameRules.PlayerData[playerId].order_no == nil then
			return
		end

		if GameRules.PlayerData[playerId].query_order == 1 then
			return
		end	
		GameRules.PlayerData[playerId].query_order = 1

		print("----> start request query pay order : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100)) 

		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "query_order"}
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))
		input["order_no"] = GameRules.PlayerData[playerId].order_no

		self:Post(
			self.Config.urlPlayerData,    
			input,
			function(iStatusCode, sBody)
				print("<---- end request query pay order : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))  
				GameRules.PlayerData[playerId].query_order = nil     

				if iStatusCode == 200 then               
					local data = json.decode(sBody)                
					if data then
						if data.code == "0000" then 
							data.msg = GameRules.PlayerData[playerId].order_tips
							GameRules.PlayerData[playerId].order_no = nil
							GameRules.PlayerData[playerId].order_tips = "订单已经处理完成"							
							self:UpdatePlayerData(playerId, data)
							data.hide = 1
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_update_player_data", data)
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_pay_result", data)																			
						end	
					end
				end
			end
		)
	end

	-- 支付检查
	function Service:PayCheck(playerId)	
		if GameRules.PlayerData[playerId].order_no == nil then 		
			local data = {code = "0001", msg = GameRules.PlayerData[playerId].order_tips}
			if data.msg == "订单已经处理完成" then data.code = "0000" end
			CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_pay_result", data)      
			return
		end

		print("----> start request query pay order : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100)) 

		local steamid = tostring(PlayerResource:GetSteamID(playerId))    
		local input = {method = "query_order"}
		input["steamid"] = steamid
		input["userid"] = tostring(PlayerResource:GetSteamAccountID(playerId))
		input["order_no"] = GameRules.PlayerData[playerId].order_no

		self:Post(
			self.Config.urlPlayerData,    
			input,
			function(iStatusCode, sBody)
				print("<---- end request query pay order : ", tostring(math.floor(GameRules:GetGameTime()*100 + 0.5)/100))
				if iStatusCode == 200 then               
					local data = json.decode(sBody)                
					if data then
						if data.code == "0000" then 
							data.msg = GameRules.PlayerData[playerId].order_tips
							GameRules.PlayerData[playerId].order_no = nil
							GameRules.PlayerData[playerId].order_tips = "订单已经处理完成"							
							self:UpdatePlayerData(playerId, data)
							data.hide = 1
							CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_update_player_data", data)																			
						end	
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_pay_result", data)
					else
						CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_pay_result", {code = "0003", msg = "查询订单返回数据解析失败，稍后会人工处理，处理完请点击刷新即可，加急请进群联系作者", hide = hideMsg}) 												
					end
				else					
					CustomGameEventManager:Send_ServerToPlayer(PlayerResource:GetPlayer(playerId), "thtd_pay_result", {code = "0004", msg = "查询订单无法连接服务器，稍后会人工处理，处理完请点击刷新即可，加急请进群联系作者", hide = hideMsg})     					
				end
			end
		)
	end

-- endregion



