2021-10-06 21:55:39 +00:00
defmodule T2ServerQuery do
@moduledoc """
2021-10-07 20:06:22 +00:00
Querying a Tribes 2 server actually requires sending 2 different packets to the server where the first byte is denoting the type of information we ' re asking for. The first is called the `info` packet which doesnt contain much more then the server name. The second is called the `status` packet which contains all the meat and potatoes.
The ` T2ServerQuery . query / 3 ` function makes requests for both ` info ` and ` status ` and combines them into a single response for easy consumption .
## Installation
def deps do
[
{ :t2_server_query , " ~> 0.1.0 " }
]
end
## Usage
# T2ServerQuery.query("35.239.88.241", port // 28_000, timeout // 3_500)
T2ServerQuery . query ( " 35.239.88.241 " )
-- -
2021-10-06 21:55:39 +00:00
"""
require Logger
2021-10-07 20:06:22 +00:00
alias T2ServerQuery.PacketParser
@doc """
Perform a server query . * * Results should be in the form of a tuple with either ` :ok ` or ` :error ` * *
{ :ok , % T2ServerQuery.QueryResult { ... } }
{ :error , % T2ServerQuery.QueryResult { ... } }
## Examples
iex > T2ServerQuery . query ( " 35.239.88.241 " )
{ :ok ,
% T2ServerQuery.QueryResult {
bot_count : 0 ,
game_type : " Classic " ,
map_name : " Canker " ,
max_player_count : 64 ,
mission_type : " LakRabbit " ,
player_count : 0 ,
players : [ %{ } ] ,
server_description : " Celebrating 20 Years of Tribes2! More information in Discord. <a:playt2.com/discord>playt2.com/discord</a> " ,
server_name : " Discord PUB " ,
server_status : :online ,
team_count : 1 ,
teams : [ %{ name : " Storm " , score : 0 } ]
} }
iex > T2ServerQuery . query ( " 127.0.0.1 " )
{ :error ,
% T2ServerQuery.QueryResult {
bot_count : 0 ,
game_type : " " ,
map_name : " " ,
max_player_count : 0 ,
mission_type : " " ,
player_count : 0 ,
players : [ ] ,
server_description : " Host unreachable, timed out. " ,
server_name : " 127.0.0.1:28000 " ,
server_status : :offline ,
team_count : 0 ,
teams : [ ]
} }
"""
def query ( server_ip , port \\ 28_000 , timeout \\ 3_500 ) do
Logger . info " query: #{ server_ip } "
{ :ok , socket } = :gen_udp . open ( 0 , [ :binary , { :active , false } ] )
# Convert a string ip from "127.0.0.1" into {127, 0, 0, 1}
{ :ok , s_ip } = server_ip
|> to_charlist ( )
|> :inet . parse_address ( )
qry_info_packet = << 14 , 2 , 1 , 2 , 3 , 4 >>
qry_status_packet = << 18 , 2 , 1 , 2 , 3 , 4 >>
# Requst info packet
:gen_udp . send ( socket , s_ip , port , qry_info_packet )
hex_info_packet = :gen_udp . recv ( socket , 0 , timeout )
|> handle_udp_response ( server_ip , port )
# Request status packet
:gen_udp . send ( socket , s_ip , port , qry_status_packet )
hex_status_packet = :gen_udp . recv ( socket , 0 , timeout )
|> handle_udp_response ( server_ip , port )
# Combine and parse results
PacketParser . init ( hex_info_packet , hex_status_packet )
end
defp handle_udp_response ( { :ok , { _ip , _port , packet } } , _server_ip , _port ) do
packet
|> Base . encode16
end
defp handle_udp_response ( { :error , :timeout } , server_ip , port ) do
Logger . error " TIMEOUT --> #{ server_ip } : #{ port } "
{ :error , " #{ server_ip } : #{ port } " }
end
@doc false
2021-10-06 21:55:39 +00:00
def log ( thing_to_log ) do
2021-10-07 20:06:22 +00:00
# Just a simple debug logging util
2021-10-06 21:55:39 +00:00
Logger . info ( inspect thing_to_log )
IO . puts " \n ____________________________________________ \n "
thing_to_log
end
end