Skip to content

Commit

Permalink
commands implementation for Ghost++ and DotA support in Lua #35 (unte…
Browse files Browse the repository at this point in the history
…sted code)
  • Loading branch information
HarpyWar committed Jul 30, 2014
1 parent a49a95a commit f6a2049
Show file tree
Hide file tree
Showing 13 changed files with 776 additions and 9 deletions.
2 changes: 1 addition & 1 deletion lua/command/w3motd.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ local username = nil

function command_w3motd(account, text)
-- allow warcraft 3 client only
if not (account.clienttag == "W3XP" or account.clienttag == "WAR3") then
if not (account.clienttag == CLIENTTAG_WAR3XP or account.clienttag == CLIENTTAG_WARCRAFT3) then
return 1
end

Expand Down
8 changes: 7 additions & 1 deletion lua/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ config = {
-- AntiHack (Starcraft)
ah = true,
ah_interval = 60, -- interval for send memory request to all players in games


-- GHost++ (https://github.com/OHSystem/ohsystem)
ghost = false, -- enable GHost commands
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

}

115 changes: 115 additions & 0 deletions lua/extend/account_wrap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -521,3 +521,118 @@ end
function account_set_locale(username, value)
return api.account_set_attr(username, "Record\\WOL\\auth\\locale", attr_type_str, value)
end



--
-- Warcraft 3 (DotA)
--

function account_get_dotarating_3x3(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_3_rating", attr_type_num)
end
function account_set_dotarating_3x3(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_3_rating", attr_type_num, value)
end

function account_get_dotawins_3x3(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_3_wins", attr_type_num)
end
function account_set_dotawins_3x3(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_3_wins", attr_type_num, value)
end

function account_get_dotalosses_3x3(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_3_losses", attr_type_num)
end
function account_set_dotalosses_3x3(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_3_losses", attr_type_num, value)
end

function account_get_dotastreaks_3x3(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_3_streaks", attr_type_num)
end
function account_set_dotastreaks_3x3(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_3_streaks", attr_type_num, value)
end

function account_get_dotaleaves_3x3(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_3_leaves", attr_type_num)
end
function account_set_dotaleaves_3x3(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_3_leaves", attr_type_num, value)
end


function account_get_dotarating_5x5(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_5_rating", attr_type_num)
end
function account_set_dotarating_5x5(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_5_rating", attr_type_num, value)
end

function account_get_dotawins_5x5(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_5_wins", attr_type_num)
end
function account_set_dotawins_5x5(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_5_wins", attr_type_num, value)
end

function account_get_dotalosses_5x5(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_5_losses", attr_type_num)
end
function account_set_dotalosses_5x5(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_5_losses", attr_type_num, value)
end

function account_get_dotastreaks_5x5(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_5_streaks", attr_type_num)
end
function account_set_dotastreaks_5x5(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_5_streaks", attr_type_num, value)
end

function account_get_dotaleaves_5x5(username)
return api.account_get_attr(username, "Record\\W3XP\\dota_5_leaves", attr_type_num)
end
function account_set_dotaleaves_5x5(username, value)
return api.account_set_attr(username, "Record\\W3XP\\dota_5_leaves", attr_type_num, value)
end

function account_get_botping(username)
value = api.account_get_attr(username, "BNET\\acct\\botping", attr_type_str)
local pings = {}

-- deserialize and return table
-- data format: "unixtime,botname,ping;..."
for chunk in string.split(value,";") do
local item = {}
i = 1
for v in string.split(chunk,",") do
if (i == 1) then
item.date = v
elseif (i == 2) then
item.bot = v
elseif (i == 3) then
item.ping = v
end
i = i + 1
end
table.insert(pings, item)
end
-- sort by ping ascending
table.sort(pings, function(a,b) return tonumber(a.ping) < tonumber(b.ping) end)
return pings
end
-- pings is a table that received from account_get_botping()
function account_set_botping(username, pings)
local value = ""
-- serialize table
for k,v in pairs(pings) do
-- ignore expired pings
if (os.time() - v.date) < 60*60*24*ghost_ping_expire then
value = value .. string.format("%s,%s,%s;", v.date, v.bot, v.ping);
end
end
return api.account_set_attr(username, "BNET\\acct\\botping", attr_type_str, value)
end
220 changes: 220 additions & 0 deletions lua/ghost/command.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
--[[
Connection interface between PvPGN and GHost
https://github.com/OHSystem/ohsystem/issues/279
Copyright (C) 2014 HarpyWar ([email protected])
This file is a part of the PvPGN Project http://pvpgn.pro
Licensed under the same terms as Lua itself.
]]--



------------------------------
--- USER -> PVPGN -> GHOST ---
------------------------------

-- /host [mode] [type] [name]
function command_host(account, text)
if not config.ghost or not account.clienttag == CLIENTTAG_WAR3XP then return 1 end

local args = split_command(text, 3)
if not args[1] or not args[2] or not args[3] then
api.describe_command(account.name, args[0])
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 next(game) then
api.message_send_text(account.name, message_type_info, localize("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
gh_del_userbot(account.name)
end
end

-- get bot by ping
local botname = gh_select_bot(account.name)

-- redirect message to bot
message_send_ghost(botname, string.format("/pvpgn host %s %s %s %s", account.name, args[1], args[2], args[3]) )
return 0
end

-- /unhost
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."))
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
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]) )

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

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
if not gh_is_owner(account) then return 1 end

local args = split_command(text, 2)
if not args[1] or not args[2] then
api.describe_command(account.name, args[0])
return -1
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]) )
return 0
end

-- /open|close [slot]
function command_open_close(account, text)
if not config.ghost or not account.clienttag == CLIENTTAG_WAR3XP then return 1 end
if not gh_is_owner(account) then return 1 end

local args = split_command(text, 1)
if not args[1] then
api.describe_command(account.name, args[0])
return -1
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]) )
return 0
end

-- /start|abort|pub|priv
function command_start_abort_pub_priv(account, text)
if not config.ghost or not account.clienttag == CLIENTTAG_WAR3XP then return 1 end
if not gh_is_owner(account) then return 1 end

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

local useracc = account
local args = split_command(text, 1)
if args[1] then
useracc = 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."))
return -1
end
end

local win, loss = localize(account.name, "win"), localize(account.name, "loss")

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 rank5x5 = icon_get_rank(rating5x5, CLIENTTAG_WAR3XP)
local rank3x3 = icon_get_rank(rating3x3, CLIENTTAG_WAR3XP)

local leaves = leaves5x5 + leaves3x3
local leaves_percent = math.round(leaves / ((wins5x5+wins3x3+loses5x5+loses3x3)/100), 1)

local country = useracc.country
if not country then country = "-" end

local game = game_get_by_id(account.game_id)
-- user in game
if next(game) then

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

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

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))
end
api.message_send_text(account.name, message_type_info, localize(account.name, "Current leave count: {} ({}%)", leaves, leaves_percent))
end

return 0
end

Loading

13 comments on commit f6a2049

@cen1
Copy link
Collaborator

@cen1 cen1 commented on f6a2049 Jul 30, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about making the /host command more general? Maybe have dota as default map but have an optional parameter for other custom games?

@HarpyWar
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think custom maps can be hosted using command /chost [map] [gamename] like on iccup.

@anonbyte
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its record to different database like 3v3 and 5v5 ?

@HarpyWar
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is. I hope GHost++ records the same different stats or not?

@anonbyte
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im never think before record different database,
bcoz my ghost only host dota game for 5v5 user, before can start the game

but nice idea i think, 1v1 maybe too :D

so there are 3 different 1v1, 3v3, 5v5

@HarpyWar
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1x1 for rating games is not a good idea, because of no game balance.
3x3 and 5x5 are the most played game types, so I though GHost should support both. If not, there may be inconsistency and I need to know it exactly.

@anonbyte
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so its using matchmaking ?
btw, its integrate with Custom Game button in pvpgn?

just to clarify 1v1 for fun game and no need balance/matchmaking, just random user vs user.
in dota 2 for 1v1 game, no need to destroy the ancient. just get 2 points (kill hero or destroy the tower)

and im sure @Grief-Code will help for 1v1 ghost.

@HarpyWar
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I didn't explore matchmaking yet. This is iccup like system now (unfinished and untested).

@cen1
Copy link
Collaborator

@cen1 cen1 commented on f6a2049 Jul 31, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5v5 is more than enough. 3v3 and 1v1 are not played even nearly as much. GHost records all the data no matter the number of players.

@HarpyWar
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does GHost allow to create 3x3 game with 6 opened slots and autostart when lobby is full?

@cen1
Copy link
Collaborator

@cen1 cen1 commented on f6a2049 Jul 31, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. But most leagues block this and allow 5v5 only.

@anonbyte
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i will allow 3v3 :p

@HarpyWar
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.