fix implementation for ghost commands, now it mostly tested and work https://github.com/HarpyWar/pvpgn/issues/35

This commit is contained in:
HarpyWar 2014-09-13 12:27:25 +04:00
parent 9b2e7fda4e
commit 0c6738fe89
15 changed files with 408 additions and 220 deletions

View file

@ -19,7 +19,7 @@ ah_mh_value = 139
function ah_init()
timer_add("ah_timer", config.ah_interval, ah_timer_tick)
TRACE("Antihack activated")
INFO("Starcraft Antihack activated")
end
-- send memory check request to all players in games
@ -50,7 +50,7 @@ function ah_handle_client(account, request_id, data)
if (request_id == ah_mh_request_id) then
-- read value from the memory
local value = bytes_to_int(data, 0, 2)
DEBUG(account.name .. " memory value: " .. value)
--TRACE(account.name .. " memory value: " .. value)
if not (value == ah_mh_value) then
is_cheater = true

16
lua/command/ping.lua Normal file
View file

@ -0,0 +1,16 @@
--[[
Copyright (C) 2014 HarpyWar (harpywar@gmail.com)
This file is a part of the PvPGN Project http://pvpgn.pro
Licensed under the same terms as Lua itself.
]]--
function command_ping(account, text)
-- allow warcraft 3 client only
if (account.clienttag == CLIENTTAG_WAR3XP) and (config.ghost) then
return gh_command_ping(account, text)
end
return 1
end

15
lua/command/stats.lua Normal file
View file

@ -0,0 +1,15 @@
--[[
Copyright (C) 2014 HarpyWar (harpywar@gmail.com)
This file is a part of the PvPGN Project http://pvpgn.pro
Licensed under the same terms as Lua itself.
]]--
function command_stats(account, text)
if config.ghost_dota_server and config.ghost and (account.clienttag == CLIENTTAG_WAR3XP) then
return gh_command_stats(account, text)
end
return 1
end

View file

@ -9,6 +9,12 @@
-- Config table can be extended here with your own variables
-- values are preloaded from bnetd.conf
config = {
-- Path to "var" directory (with slash at the end)
-- Usage: config.vardir()
vardir = function()
return string.replace(config.statusdir, "status", "")
end,
flood_immunity_users = { "admin", "" }, -- ignore flood protection for these users
-- Quiz settings
@ -27,7 +33,7 @@ config = {
-- GHost++ (https://github.com/OHSystem/ohsystem)
ghost = false, -- enable GHost commands
ghost_bots = { hostbot1, hostbot2 }, -- list of authorized bots
ghost_bots = { "hostbot1", "hostbot2" }, -- list of authorized bots
ghost_dota_server = true, -- replace normal Warcraft 3 stats with DotA
ghost_ping_expire = 90, -- interval when outdated botpings should be removed (bot ping updates for each user when he join a game hosted by ghost); game list shows to user depending on the best ping to host bot

View file

@ -15,7 +15,7 @@ function users_count()
return count
end
-- Get count of all server account
-- Get count of all server account that were used by the server
function accounts_count()
local count = 0
for i,account in pairs(api.server_get_users(true)) do

View file

@ -91,10 +91,10 @@ function account_set_auth_lock(username, value)
end
function account_get_auth_locktime(username)
return api.account_get_attr(username, "BNET\\auth\\locktime", attr_type_int)
return api.account_get_attr(username, "BNET\\auth\\locktime", attr_type_num)
end
function account_set_auth_locktime(username, value)
return api.account_set_attr(username, "BNET\\auth\\locktime", attr_type_int, value)
return api.account_set_attr(username, "BNET\\auth\\locktime", attr_type_num, value)
end
function account_get_auth_lockreason(username)
@ -119,10 +119,10 @@ function account_set_auth_mute(username, value)
end
function account_get_auth_mutetime(username)
return api.account_get_attr(username, "BNET\\auth\\mutetime", attr_type_int)
return api.account_get_attr(username, "BNET\\auth\\mutetime", attr_type_num)
end
function account_set_auth_mutetime(username, value)
return api.account_set_attr(username, "BNET\\auth\\mutetime", attr_type_int, value)
return api.account_set_attr(username, "BNET\\auth\\mutetime", attr_type_num, value)
end
function account_get_auth_mutereason(username)
@ -140,17 +140,17 @@ function account_set_auth_muteby(username, value)
end
function account_get_auth_command_groups(username)
return api.account_get_attr(username, "BNET\\auth\\command_groups", attr_type_int)
return api.account_get_attr(username, "BNET\\auth\\command_groups", attr_type_num)
end
function account_set_auth_command_groups(username, value)
return api.account_set_attr(username, "BNET\\auth\\command_groups", attr_type_int, value)
return api.account_set_attr(username, "BNET\\auth\\command_groups", attr_type_num, value)
end
function account_get_acct_lastlogin_time(username)
return api.account_get_attr(username, "BNET\\acct\\lastlogin_time", attr_type_int)
return api.account_get_attr(username, "BNET\\acct\\lastlogin_time", attr_type_num)
end
function account_set_acct_lastlogin_time(username, value)
return api.account_set_attr(username, "BNET\\acct\\lastlogin_time", attr_type_int, value)
return api.account_set_attr(username, "BNET\\acct\\lastlogin_time", attr_type_num, value)
end
function account_get_acct_lastlogin_owner(username)
@ -161,10 +161,10 @@ function account_set_acct_lastlogin_owner(username, value)
end
function account_get_acct_createtime(username)
return api.account_get_attr(username, "BNET\\acct\\ctime", attr_type_int)
return api.account_get_attr(username, "BNET\\acct\\ctime", attr_type_num)
end
function account_set_acct_createtime(username, value)
return api.account_set_attr(username, "BNET\\acct\\ctime", attr_type_int, value)
return api.account_set_attr(username, "BNET\\acct\\ctime", attr_type_num, value)
end
function account_get_acct_lastlogin_clienttag(username)
@ -529,7 +529,9 @@ end
--
function account_get_dotarating_3x3(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_3_rating", attr_type_num)
value = api.account_get_attr(username, "Record\\W3XP\\dota_3_rating", attr_type_num)
if (value == 0) then value = 1000 end
return value
end
function account_set_dotarating_3x3(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_3_rating", attr_type_num, value)
@ -565,7 +567,9 @@ end
function account_get_dotarating_5x5(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_5_rating", attr_type_num)
value = api.account_get_attr(username, "Record\\W3XP\\dota_5_rating", attr_type_num)
if (value == 0) then value = 1000 end
return value
end
function account_set_dotarating_5x5(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_5_rating", attr_type_num, value)
@ -602,6 +606,8 @@ end
function account_get_botping(username)
value = api.account_get_attr(username, "BNET\\acct\\botping", attr_type_str)
local pings = {}
-- if pings were not set yet then return empty table
if string.empty(value) then return pings end
-- deserialize and return table
-- data format: "unixtime,botname,ping;..."
@ -630,7 +636,7 @@ function account_set_botping(username, pings)
-- serialize table
for k,v in pairs(pings) do
-- ignore expired pings
if (os.time() - v.date) < 60*60*24*ghost_ping_expire then
if (os.time() - tonumber(v.date)) < 60*60*24*config.ghost_ping_expire then
value = value .. string.format("%s,%s,%s;", v.date, v.bot, v.ping);
end
end

View file

@ -5,6 +5,9 @@
Licensed under the same terms as Lua itself.
]]--
-- Strings used here - not numbers, because these values will be
-- compare with string values returned from API.
-- For example "0" != 0 in Lua.
game_type_none,
game_type_all,
@ -27,7 +30,7 @@ game_type_diablo,
game_type_diablo2open,
game_type_diablo2closed,
game_type_anongame
= 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20
= "0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20"
game_status_started,
@ -35,7 +38,7 @@ game_status_full,
game_status_open,
game_status_loaded,
game_status_done
= 0,1,2,3,4
= "0","1","2","3","4"
game_result_none,
@ -45,7 +48,7 @@ game_result_draw,
game_result_disconnect,
game_result_observer,
game_result_playing
= 0,1,2,3,4,5,6
= "0","1","2","3","4","5","6"
game_option_none,
@ -81,7 +84,7 @@ game_option_topvbot_4,
game_option_topvbot_3,
game_option_topvbot_2,
game_option_topvbot_1
= 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32
= "0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32"
game_maptype_none,
@ -91,7 +94,7 @@ game_maptype_ladder,
game_maptype_pgl,
game_maptype_kbk,
game_maptype_compusa
= 0,1,2,3,4,5,6
= "0","1","2","3","4","5","6"
game_tileset_none,
@ -103,7 +106,7 @@ game_tileset_jungle,
game_tileset_desert,
game_tileset_ice,
game_tileset_twilight
= 0,1,2,3,4,5,6,7,8,9
= "0","1","2","3","4","5","6","7","8","9"
game_speed_none,
@ -114,7 +117,7 @@ game_speed_normal,
game_speed_fast,
game_speed_faster,
game_speed_fastest
= 0,1,2,3,4,5,6,7
= "0","1","2","3","4","5","6","7"
game_difficulty_none,
@ -124,10 +127,10 @@ game_difficulty_hell,
game_difficulty_hardcore_normal,
game_difficulty_hardcore_nightmare,
game_difficulty_hardcore_hell
= 0,1,2,3,4,5,6
= "0","1","2","3","4","5","6"
game_flag_none,
game_flag_private
= 0,1
= "0","1"

View file

@ -25,11 +25,12 @@ function command_host(account, text)
return -1
end
if gh_get_userbot(account.name) then
local gamename = gh_get_usergame(account.name)
local game = game_get_by_name(gamename, account.clienttag, game_type_all)
-- if user already host a game
if gh_get_userbot_name(account.name) then
local gamename = gh_get_userbot_game(account.name)
local game = api.game_get_by_name(gamename, account.clienttag, game_type_all)
if next(game) then
api.message_send_text(account.name, message_type_info, localize("You already host a game \"{}\". Use /unhost to destroy it.", gamename))
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "You already host a game \"{}\". Use /unhost to destroy it.", gamename))
return -1
else
-- if game doesn't exist then remove mapped bot for user
@ -37,30 +38,23 @@ function command_host(account, text)
end
end
-- get bot by ping
-- get available bot by ping
local botname = gh_select_bot(account.name)
if not botname then
api.message_send_text(account.name, message_type_error, nil, localize(account.name, "Enable to create game. HostBots are temporary offline."))
return -1
end
-- redirect message to bot
message_send_ghost(botname, string.format("/pvpgn host %s %s %s %s", account.name, args[1], args[2], args[3]) )
gh_message_send(botname, string.format("/pvpgn host %s %s %s %s", account.name, args[1], args[2], args[3]) )
return 0
end
gh_maplist = {}
-- /chost [code] [gamename]
function command_chost(account, text)
if not config.ghost or not account.clienttag == CLIENTTAG_WAR3XP then return 1 end
local filename = gh_directory() .. "/maplist.txt"
-- load maps from the file
file_load(filename, file_load_dictionary_callback,
function(a,b)
-- split second value to get name and filename
local mapname,mapfile = string.split(b,"|")
table.insert(gh_maplist, { code = a, name = mapname, filename = mapfile })
end)
local args = split_command(text, 2)
if not args[1] or not args[2] then
@ -68,7 +62,7 @@ function command_chost(account, text)
-- send user each map on a new line
for i,map in pairs(gh_maplist) do
api.message_send_text(account.name, message_type_info, string.format("%s = %s", map.code, map.name) )
api.message_send_text(account.name, message_type_info, nil, string.format("%s = %s", map.code, map.name) )
end
return -1
end
@ -76,20 +70,20 @@ function command_chost(account, text)
-- find map by code
local mapfile = nil
for i,map in pairs(gh_maplist) do
if (args[1] == map.code) then mapfile = map.filename end
if string.lower(args[1]) == string.lower(map.code) then mapfile = map.filename end
end
if not mapfile then
api.message_send_text(account.name, message_type_info, localize(account.name, "Invalid map code.") )
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "Invalid map code.") )
return -1
end
if gh_get_userbot(account.name) then
local gamename = gh_get_usergame(account.name)
local game = game_get_by_name(gamename, account.clienttag, game_type_all)
-- if user already host a game
if gh_get_userbot_name(account.name) then
local gamename = gh_get_userbot_game(account.name)
local game = api.game_get_by_name(gamename, account.clienttag, game_type_all)
if next(game) then
api.message_send_text(account.name, message_type_info, localize("You already host a game \"{}\". Use /unhost to destroy it.", gamename))
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "You already host a game \"{}\". Use /unhost to destroy it.", gamename))
return -1
else
-- if game doesn't exist then remove mapped bot for user
@ -97,11 +91,15 @@ function command_chost(account, text)
end
end
-- get bot by ping
-- get available bot by ping
local botname = gh_select_bot(account.name)
if not botname then
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "Enable to create game. HostBots are temporary offline."))
return -1
end
-- redirect message to bot
message_send_ghost(botname, string.format("/pvpgn chost %s %s %s", account.name, mapfile, args[2]) )
gh_message_send(botname, string.format("/pvpgn chost %s %s %s", account.name, mapfile, args[2]) )
return 0
end
@ -109,46 +107,36 @@ end
function command_unhost(account, text)
if not config.ghost or not account.clienttag == CLIENTTAG_WAR3XP then return 1 end
-- check if user has a mapped bot
if gh_get_userbot(account.name) then
api.message_send_text(account.name, message_type_info, localize("You don't host a game."))
local botname = gh_get_userbot_name(account.name)
-- check if user hasn't a mapped bot
if not botname then
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "You don't host a game."))
return -1
end
-- do not allow unhost if the game is started
local game = game_get_by_id(account.game_id)
if next(game) and (game.status == game_status_started) then
-- do not allow unhost if the game is started (and owner is a mapped user bot -
-- it is necessary because we can get duplicate name of another's game,
-- due to save/restore table state when Lua rehash)
local game = api.game_get_by_id(account.game_id)
if next(game) and (game.status == game_status_started) and (game.owner == botname) then
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "You can't unhost a started game."))
return -1
end
-- redirect message to bot
local botname = gh_get_userbot(account.name)
message_send_ghost(botname, string.format("/pvpgn unhost %s %s %s %s", account.name, args[1], args[2], args[3]) )
local botname = gh_get_userbot_name(account.name)
gh_message_send(botname, string.format("/pvpgn unhost %s", account.name) )
-- remove mapped bot anyway to make sure that it was removed
-- (even if bot casual shutdown before send callback)
gh_del_userbot(account.name)
return 0
end
-- /ping
function command_ping(account, text)
if not config.ghost or not account.clienttag == CLIENTTAG_WAR3XP then return 1 end
local game = game_get_by_id(account.game_id)
-- if user not in game
if not next(game) then return 1 end
-- check if game owner is ghost bot
if not gh_is_bot(game.owner) then return 1 end
-- redirect message to bot
message_send_ghost(game.owner, string.format("/pvpgn ping %s", account.name))
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "Your game was destroyed."))
return 0
end
-- /swap [slot1] [slot2]
function command_swap(account, text)
if not config.ghost or not account.clienttag == CLIENTTAG_WAR3XP then return 1 end
@ -161,8 +149,8 @@ function command_swap(account, text)
end
-- redirect message to bot
local botname = gh_get_userbot(account.name)
message_send_ghost(botname, string.format("/pvpgn swap %s %s %s %s", account.name, args[1], args[2]) )
local botname = gh_get_userbot_name(account.name)
gh_message_send(botname, string.format("/pvpgn swap %s %s %s", account.name, args[1], args[2]) )
return 0
end
@ -178,8 +166,8 @@ function command_open_close(account, text)
end
-- redirect message to bot
local botname = gh_get_userbot(account.name)
message_send_ghost(botname, string.format("/pvpgn %s %s %s", args[0], account.name, args[1]) )
local botname = gh_get_userbot_name(account.name)
gh_message_send(botname, string.format("/pvpgn %s %s %s", args[0], account.name, args[1]) )
return 0
end
@ -191,90 +179,136 @@ function command_start_abort_pub_priv(account, text)
local args = split_command(text, 0)
-- redirect message to bot
local botname = gh_get_userbot(account.name)
message_send_ghost(botname, string.format("/pvpgn %s %s %s", args[0], account.name) )
local botname = gh_get_userbot_name(account.name)
gh_message_send(botname, string.format("/pvpgn %s %s", args[0], account.name) )
return 0
end
-- /p
function gh_command_ping(account, text)
-- if user not in game then do not override command
if not account.game_id then return false end
local game = api.game_get_by_id(account.game_id)
-- check if game owner is ghost bot
if not gh_is_bot(game.owner) then return 1 end
-- redirect message to bot
gh_message_send(game.owner, string.format("/pvpgn ping %s", account.name))
return 0
end
-- /stats
function command_stats(account, text)
if not config.ghost or not config.ghost_dota_server or not account.clienttag == CLIENTTAG_WAR3XP then return 1 end
function gh_command_stats(account, text)
local useracc = account
local args = split_command(text, 1)
if args[1] then
useracc = account_get_by_name(args[1])
useracc = api.account_get_by_name(args[1])
-- if user not found
if not next(useracc) then
api.message_send_text(account.name, localize(account, "Invalid user."))
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "Invalid user."))
return -1
end
end
local stats = gh_get_dotastats(useracc)
-- localized strings
local win, loss = localize(account.name, "win"), localize(account.name, "loss")
local pts = localize(account.name, "pts")
local rating5x5 = account_get_dotarating_5x5(useracc.name)
local rating3x3 = account_get_dotarating_3x3(useracc.name)
local wins5x5 = account_get_dotawins_5x5(useracc.name)
local wins3x3 = account_get_dotawins_3x3(useracc.name)
local loses5x5 = account_get_dotaloses_5x5(useracc.name)
local loses3x3 = account_get_dotaloses_3x3(useracc.name)
local streaks5x5 = account_get_dotastreaks_5x5(useracc.name)
local streaks3x3 = account_get_dotastreaks_3x3(useracc.name)
local leaves5x5 = account_get_dotaleaves_5x5(useracc.name)
local leaves3x3 = account_get_dotaleaves_3x3(useracc.name)
local game = api.game_get_by_id(account.game_id)
local rank5x5 = icon_get_rank(rating5x5, CLIENTTAG_WAR3XP)
local rank3x3 = icon_get_rank(rating3x3, CLIENTTAG_WAR3XP)
-- user who is owner of the hostbot in the current game
local owner = gh_find_userbot_by_game(game.name)
local gametype = "5x5"--gh_get_userbot_gametype(owner)
local leaves = leaves5x5 + leaves3x3
local leaves_percent = math.round(leaves / ((wins5x5+wins3x3+loses5x5+loses3x3)/100), 1)
-- user given in args or (user in game and game is ladder)
if not args[1] and next(game) and not string:empty(gametype) then
-- iterate all users in the game
for u in string.split("harpywar,admin",",") do
-- get new stats for the player
stats = gh_get_dotastats(api.account_get_by_name(u))
local country = useracc.country
if not country then country = "-" end
local rank = stats.rank5x5
local rating = stats.rating5x5
local leaves = stats.leaves5x5
local leaves_percent = stats.leaves5x5_percent
local game = game_get_by_id(account.game_id)
-- user in game
if next(game) then
-- switch gametype if game name has substr "3x3"
if gametype == "3x3" then
rank = stats.rank3x3
rating = stats.rating3x3
leaves = stats.leaves3x3
leaves_percent = stats.leaves3x3_percent
end
local gametype = "5x5"
local rank = rank5x5
local rating = rating5x5
-- switch gametype if game name has substr "3x3"
if string.find(game.name, "3x3") then
gametype = "3x3"
rank = rank3x3
rating = rating3x3
-- bnproxy stats output format (DO NOT MODIFY!!!)
api.message_send_text(account.name, message_type_info, nil, string.format("[%s] %s DotA (%s): [%s] %d pts. Leave count: %d (%f%%)",
stats.country, u, gametype,
rank, rating,
leaves, leaves_percent))
end
-- bnproxy stats output format
api.message_send_text(account.name, message_type_info, string.format("[%s] %s DotA (%s): [%s] %d pts. Leave count: %d (%f%%)",
country, useracc.name, gametype,
rank, rating,
leaves, leaves_percent))
else -- in chat
api.message_send_text(account.name, message_type_info, localize(account.name, "[{}] {}'s record:", country, useracc.name))
api.message_send_text(account.name, message_type_info, localize(account.name, "DotA games ({}): {}-{} [{}] {} pts", "5x5", wins5x5, loses5x5, rank5x5, rating5x5))
api.message_send_text(account.name, message_type_info, localize(account.name, "DotA games ({}): {}-{} [{}] {} pts", "3x3", wins3x3, loses3x3, rank3x3, rating3x3))
api.message_send_text(account.name, message_type_info, nil, string.format("[%s] ", stats.country) .. localize(account.name, "{}'s record:", useracc.name))
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "DotA games") .. string.format(" (%s): %d-%d [%s] %d %s", "5x5", stats.wins5x5, stats.losses5x5, stats.rank5x5, stats.rating5x5, pts))
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "DotA games") .. string.format(" (%s): %d-%d [%s] %d %s", "3x3", stats.wins3x3, stats.losses3x3, stats.rank3x3, stats.rating3x3, pts))
-- display streaks in self user stats
if not args[1] then
local streaks5x5_result, streaks3x3_result = win, win
if (streaks5x5 < 0) then streaks5x5_result = loss end
if (streaks3x3 < 0) then streaks3x3_result = loss end
if (stats.streaks5x5 < 0) then streaks5x5_result = loss end
if (stats.streaks3x3 < 0) then streaks3x3_result = loss end
api.message_send_text(account.name, message_type_info, localize(account.name, "Current {} streak ({}): {}", "5x5", streaks5x5_result, streaks5x5))
api.message_send_text(account.name, message_type_info, localize(account.name, "Current {} streak ({}): {}", "3x3", streaks3x3_result, streaks3x3))
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "Current {} streak", "5x5") .. string.format(" (%s): %s", streaks5x5_result, stats.streaks5x5))
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "Current {} streak", "3x3") .. string.format(" (%s): %s", streaks3x3_result, stats.streaks3x3))
end
api.message_send_text(account.name, message_type_info, localize(account.name, "Current leave count: {} ({}%)", leaves, leaves_percent))
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "Current leave count:") .. string.format(" %d (%s%%)", stats.leaves, stats.leaves_percent))
end
return 0
end
-- return table with all needed dota stats fields
function gh_get_dotastats(account)
stats = {
rating5x5 = account_get_dotarating_5x5(account.name),
rating3x3 = account_get_dotarating_3x3(account.name),
wins5x5 = account_get_dotawins_5x5(account.name),
wins3x3 = account_get_dotawins_3x3(account.name),
losses5x5 = account_get_dotalosses_5x5(account.name),
losses3x3 = account_get_dotalosses_3x3(account.name),
streaks5x5 = account_get_dotastreaks_5x5(account.name),
streaks3x3 = account_get_dotastreaks_3x3(account.name),
leaves5x5 = account_get_dotaleaves_5x5(account.name),
leaves3x3 = account_get_dotaleaves_3x3(account.name),
country = account.country
}
if not stats.country then stats.country = "-" end
stats.country = stats.country:sub(1,2) -- two first symbols
stats.leaves5x5_percent = math.round(stats.leaves5x5 / ((stats.wins5x5+stats.losses5x5)/100), 1)
stats.leaves3x3_percent = math.round(stats.leaves3x3 / ((stats.wins3x3+stats.losses3x3)/100), 1)
if math.isnan(stats.leaves5x5_percent) then stats.leaves5x5_percent = 0 end
if math.isnan(stats.leaves3x3_percent) then stats.leaves3x3_percent = 0 end
stats.leaves = stats.leaves5x5 + stats.leaves3x3
stats.leaves_percent = math.round(stats.leaves5x5_percent + stats.leaves3x3_percent, 1)
stats.rank5x5 = api.icon_get_rank(stats.rating5x5, CLIENTTAG_WAR3XP)
stats.rank3x3 = api.icon_get_rank(stats.rating3x3, CLIENTTAG_WAR3XP)
return stats
end

View file

@ -20,7 +20,7 @@ function command_ghost(account, text)
if not config.ghost then return 1 end
-- restrict commands for authorized bots only
if not gh_is_bot_authorized(account.name) then return 1 end
if not gh_is_bot(account.name) then return 1 end
local args = split_command(text, 5)
local cmd = args[1]
@ -29,62 +29,88 @@ function command_ghost(account, text)
if code ~= "ok" then
if code == "err" then
local err_message = args[3]
-- HINT: handle error here
-- HINT: you can handle error here
end
return -1
end
-- /ghost init
if (cmd == "init") then
DEBUG("(callback) pvpgn mode is activated on bot " .. account.name)
elseif (cmd == "gameresult") then
gh_callback_gameresult(args[2], args[3], args[4])
INFO("GHost bot is connected (" .. account.name .. ")")
elseif (cmd == "host" or cmd == "chost") then
-- /ghost gameresult [players] [ratings] [results]
elseif (cmd == "gameresult") then
gh_callback_gameresult(args[3], args[4], args[5])
-- /ghost host [code] [user] [gamename]
elseif (cmd == "host") then
args = split_command(text, 4) -- support game name with spaces
gh_callback_host(args[3], account.name, args[4])
-- /ghost chost [code] [user] [gamename]
elseif (cmd == "chost") then
args = split_command(text, 4) -- support game name with spaces
gh_callback_chost(args[3], account.name, args[4])
-- /ghost unhost [code] [user] [gamename]
elseif (cmd == "unhost") then
gh_callback_unhost(args[3], args[4])
-- /ghost ping [code] [user] [players] [pings]
elseif (cmd == "ping") then
gh_callback_ping(args[3], args[4], args[5])
-- /ghost swap [code] [user] [username1] [username2]
elseif (cmd == "swap") then
-- HINT: notify user about success usage
-- HINT: you can notify user about success usage
-- /ghost open [code] [user] [slot]
elseif (cmd == "open") or (cmd == "close") then
-- HINT: notify user about success usage
-- HINT: you can notify user about success usage
-- /ghost [cmd] [code] [user] [message]
elseif (cmd == "start") or (cmd == "abort")
or (cmd == "pub") or (cmd == "priv") then
-- HINT: notify user about success usage
-- HINT: you can notify user about success usage
end
-- FIXME: may be do not log bot commands? (return -1)
-- FIXME: may be do not log commands from bot? (return -1)
return 0
end
function gh_callback_host(username, botname, gamename)
gh_set_userbot(username, botname, gamename)
api.message_send_text(username, message_type_info, "Game " .. gamename .. " created")
local gametype = "5x5"
if string.find(game.name, "3x3") then gametype = "3x3" end
gh_set_userbot(username, botname, gamename, gametype)
api.message_send_text(username, message_type_info, nil, localize(username, "Game \"{}\" is created by {}. You are an owner.", gamename, botname))
end
function gh_callback_chost(username, botname, gamename)
gh_set_userbot(username, botname, gamename, "")
api.message_send_text(username, message_type_info, nil, localize(username, "Game \"{}\" is created by {}. You are an owner.", gamename, botname))
end
function gh_callback_unhost(username, gamename)
gh_del_userbot(username)
api.message_send_text(username, message_type_info, "Game " .. gamename .. " destroyed")
api.message_send_text(username, message_type_info, nil, localize(username, "The game \"{}\" was destroyed.", gamename))
end
function gh_callback_ping(username, players, pings)
-- make sure that user is in a game
-- make sure that user is still in a game
local account = api.account_get_by_name(username)
local game = api.game_get_by_id(account.game_id)
if not next(game) then return end
-- make sure game owner is a bot
if not gh_is_bot(game.owner) then return end
-- silent flag (do not send result to user, just update in db)
local silent = gh_read_silentping(username)
-- get silent flag (do not send result to user)
-- it's needed to do not show ping when user join a game, but we want to save the ping into database
local silent = gh_get_silentflag(username)
local users = {}
-- fill table users
i = 1
-- fill usernames
for v in string.split(players, ",") do
-- init each user table
users[i] = {}
@ -92,9 +118,8 @@ function gh_callback_ping(username, players, pings)
i = i + 1
end
i = 1
-- fill pings
for v in string.split(pings, ",") do
-- init each user table
users[i] = {}
users[i]["ping"] = v
i = i + 1
end
@ -107,13 +132,13 @@ function gh_callback_ping(username, players, pings)
local pings = account_get_botping(u["name"])
local is_found = false
for k,v in pairs(pings) do
-- update user pings for current bot
-- update user ping for current bot
if (v.bot == game.owner) then
pings[k].ping = u["ping"]
is_found = true
end
end
-- if row not found for the botname then add a new one
-- if bot not found in database pings then add a new row
if not is_found then
local item = {
date = os.time(),
@ -124,20 +149,19 @@ function gh_callback_ping(username, players, pings)
end
-- save updated pings
account_set_ping2bot(u["name"], pings)
account_set_botping(u["name"], pings)
latency_all = latency_all .. string.format("{}: [{} {}]; ", u["name"], u["ping"], ms)
latency_all = latency_all .. string.format("%s: [%s %s]; ", u["name"], u["ping"], ms)
-- if game started send ping of each players on a new line
if (game.status == game_status_started) and not (silent) then
api.message_send_text(username, message_type_info, localize(username, "{}'s latency to {}: {} {}", u["name"], game.owner, u["ping"], ms))
api.message_send_text(username, message_type_info, nil, localize(username, "{}'s latency to {}: {} {}", u["name"], game.owner, u["ping"], ms))
end
end
-- if game not started send pings of all players in a single line
if not (game.status == game_status_started) and not (silent) then
api.message_send_text(u["name"], message_type_info, localize(username, "Latency: {}", latency_all))
api.message_send_text(username, message_type_info, nil, localize(username, "Latency: {}", latency_all))
end
end
@ -155,12 +179,12 @@ function gh_callback_gameresult(players, ratings, results)
end
i = 1
for v in string.split(ratings, ",") do
users[i]["rating"] = v
users[i]["rating"] = tonumber(v)
i = i + 1
end
i = 1
for v in string.split(results, ",") do
users[i]["result"] = v
users[i]["result"] = tonumber(v)
i = i + 1
end
@ -203,21 +227,24 @@ function gh_callback_gameresult(players, ratings, results)
losses = losses + 1
end
if streaks >= 0 then -- win
local result_str = localize(u["name"], "won")
if streaks >= 0 then -- win streak
if u["result"] > 0 then
streaks = streaks + 1
else
streaks = 1
streaks = -1
end
else -- loss
else -- loss streak
if u["result"] <= 0 then
streaks = streaks - 1
else
streaks = -1
streaks = 1
end
result_str = localize(u["name"], "lose")
end
-- update stats
-- update stats
if #users == 6 then
account_set_dotarating_3x3(u["name"], u["rating"])
account_set_dotawins_3x3(u["name"], wins)
@ -233,12 +260,11 @@ function gh_callback_gameresult(players, ratings, results)
end
-- diff between old and new rating
local points = rating-u["rating"]
local result_str = localize("won")
if pts < 0 then result_str = localize("lose") end
local points = u["rating"]-rating
-- inform user about new stats
api.message_send_text(u["name"], message_type_info, localize(u["name"], "You {} {} points. New DotA ({}) rating: {} pts. Current {} streak: {}", result_str, points, rating, gametype, result_str, streaks))
api.message_send_text(u["name"], message_type_info, nil, localize(u["name"], "You {} {} points.", result_str, points))
api.message_send_text(u["name"], message_type_info, nil, localize(u["name"], "New DotA ({}) rating: {} pts. Current {} streak: {}", gametype, rating, result_str, streaks))
end

View file

@ -5,25 +5,53 @@
Licensed under the same terms as Lua itself.
]]--
-- custom map list
gh_maplist = {}
function gh_load()
local filename = gh_directory() .. "/users.dmp"
if not file_exists(filename) then
return
-- load ghost state
local filename = config.vardir() .. "ghost_users.dmp"
if file_exists(filename) then
gh_load_userbots(filename)
DEBUG("Loaded GHost state from " .. filename)
end
-- load ghost state
gh_load_userbot(filename)
-- preload custom map list
local mapfile = gh_directory() .. "/maplist.txt"
if file_exists(mapfile) then
gh_load_maps(mapfile)
DEBUG("Loaded " .. #gh_maplist .. " GHost custom maps")
else
WARN("File with GHost custom map list is not found: " .. mapfile)
end
DEBUG("Loaded GHost state from " .. filename)
end
function gh_unload()
-- save ghost state
local filename = gh_directory() .. "/users.dmp"
gh_save_userbot(filename)
local filename = config.vardir() .. "ghost_users.dmp"
gh_save_userbots(filename)
DEBUG("Saved GHost state to " .. filename)
end
function gh_load_maps(filename)
-- load maps from the file
file_load(filename, file_load_dictionary_callback,
function(a,b)
-- split second part of the line to get mapname and filename
local idx = 0
for v in string.split(b, "|") do
if idx == 0 then
mapname = string:trim(v)
else
mapfile = string:trim(v)
end
idx = idx + 1
end
table.insert(gh_maplist, { code = a, name = mapname, filename = mapfile })
end)
end

View file

@ -11,26 +11,35 @@ function gh_handle_game_userjoin(game, account)
if not gh_is_bot(game.owner) then return end
-- send ping silently
gh_write_silentping(account.name)
gh_set_silentflag(account.name)
local botaccount = account_get_by_name(game.owner)
local botaccount = api.account_get_by_name(game.owner)
-- send ping to bot (to save result)
command_ping(botaccount, "/ping")
if (config.ghost_dota_server) then
-- show stats of each player in the game
-- user who is owner of the hostbot in the current game
local owner = gh_find_userbot_by_game(game.name)
-- if game is not ladder
if string:empty(gh_get_userbot_gametype(owner)) then return end
for u in string.split(game.players, ",") do
-- (except current player)
if (account.name ~= u) then
command_stats(botaccount, "/stats " .. u)
end
-- show stats of the player who joined the game for each other player
local useracc = api.account_get_by_name(u)
command_stats(useracc, "/stats " .. account.name)
-- show stats of each player in the game to a player who joined the game
command_stats(account, "/stats " .. u)
end
api.message_send_text(account.name, message_type_info, nil, localize(account.name, "Joined ladder game. Game owner: {}", owner))
end
end
function gh_handle_user_login(account)
if gh_is_bot(account.name) then
-- activate pvpgn mode on ghost side
gh_message_send(botname, "/pvpgn init");
-- activate pvpgn mode on ghost side
gh_message_send(account.name, "/pvpgn init");
DEBUG("init bot " .. account.name)
end
end

View file

@ -13,43 +13,65 @@ gh_user2bot = {}
-- array with users who will not receive a response from /ping
silentpings = {}
-- return a user mapped bot
-- return a user mapped bot name
-- (nil if bot not mapped)
function gh_get_userbot(username)
if not next(user2bot) or not user2bot[username] then return nil end
return user2bot[username].bot
function gh_get_userbot_name(username)
if not next(gh_user2bot) or not gh_user2bot[username] then return nil end
return gh_user2bot[username].bot
end
function gh_get_usergame(username)
if not next(user2bot) or not user2bot[username] then return nil end
return user2bot[username].game
-- return a user mapped game name that created by bot
function gh_get_userbot_game(username)
if not next(gh_user2bot) or not gh_user2bot[username] then return nil end
return gh_user2bot[username].game
end
function gh_set_userbot(username, botname, gamename)
user2bot[username] = { bot = botname, game = gamename }
-- return a user mapped game type that created by bot
function gh_get_userbot_gametype(username)
if not next(gh_user2bot) or not gh_user2bot[username] then return nil end
return gh_user2bot[username].gametype
end
-- find gamename in games and return owner of the game (username)
-- return false if not found
function gh_find_userbot_by_game(gamename)
for u,bot in pairs(gh_user2bot) do
if (bot.game == gamename) then return u end
end
return false
end
function gh_set_userbot(username, botname, gamename, gametype)
gh_user2bot[username] = { bot = botname, game = gamename, type = gametype, time = os.time() }
end
function gh_del_userbot(username)
user2bot[username] = nil
gh_user2bot[username] = nil
end
-- save table to file
function gh_save_userbot(filename)
-- do not save empty table
if not next(gh_user2bot) then return end
-- save table to file
function gh_save_userbots(filename)
table.save(gh_user2bot, filename)
return true
end
-- load table from file
function gh_load_userbot(filename)
function gh_load_userbots(filename)
gh_user2bot = table.load(filename)
-- debug info
if next(gh_user2bot) then
TRACE("Users assigned to bots:")
for u,bot in pairs(gh_user2bot) do
TRACE(u..": "..bot.bot.." ("..bot.game..", "..bot.time..")")
end
end
end
-- add user into a silenttable
function gh_write_silentping(username)
function gh_set_silentflag(username)
table.insert(silentpings, account.name)
end
-- return true if user in a silenttable, and remove it
function gh_read_silentping(username)
function gh_get_silentflag(username)
for k,v in pairs(silentpings) do
if v == username then
table.remove(silentpings, k)
@ -74,7 +96,7 @@ function gh_is_owner(account)
local game = game_get_by_id(account.game_id)
-- 2) user owner of the bot in current game
if not game.owner == gh_get_userbot(account.name) then return false end
if not game.owner == gh_get_userbot_name(account.name) then return false end
return true
end
@ -85,7 +107,7 @@ end
-- is bot in authorized config list?
function gh_is_bot(username)
for i,bot in pairs(config.ghost_bots) do
if (bot == username) then return true end
if string.lower(bot) == string.lower(username) then return true end
end
return false
end
@ -93,14 +115,14 @@ end
-- redirect command from user to bot through PvPGN
function gh_message_send(botname, text)
-- send message from the server to ghost
api.message_send_text(nil, message_type_whisper, botname, text)
-- send message to ghost from the server
api.message_send_text(botname, message_type_whisper, nil, text)
end
-- return bot name (the best for user by ping)
-- return nil if no available bots
function gh_select_bot(username)
local pings = account_get_ping2bot(username)
local pings = account_get_botping(username)
local botname = nil
-- iterate all bots in config
@ -121,6 +143,17 @@ function gh_select_bot(username)
botname = pings[1].bot
end
local botacc = api.account_get_by_name(botname)
-- if bot is offline then use first available
-- FIXME: use next available by the best ping?
if botacc.online == "false" then
botname = nil
for i,bot in pairs(config.ghost_bots) do
botacc = api.account_get_by_name(bot)
if botacc.online == "true" then return bot end
end
end
return botname
end

View file

@ -11,3 +11,6 @@ function math.round(num, idp)
local mult = 10^(idp or 0)
return math.floor(num * mult + 0.5) / mult
end
function math.isnan(x) return x ~= x end
function math.isinf(x) return (x+1)==x end

View file

@ -20,6 +20,7 @@ end
-- Check string is nil or empty
-- bool
-- Usage example: string:empty("") -> true, string:empty(nil) -> true
function string:empty(str)
return str == nil or str == ''
end
@ -37,6 +38,7 @@ function string.ends(str, ends)
end
-- Replace string
-- Usage example: string.replace("hello world","world","Joe") -> "hello Joe"
function string.replace(str, pattern, replacement)
if string:empty(str) then return str end
local s, n = string.gsub(str, pattern, replacement)

View file

@ -60,6 +60,13 @@ function q_command_start(account, filename)
end
quiz:start(channel.name, filename)
i =0
for t=0,1000000 do
file_save(t, "test.txt")
end
return 0
end