Server send game and server info
This commit is contained in:
parent
1baf3fcad0
commit
45d1361408
25
lib/game_info.rb
Normal file
25
lib/game_info.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'packer'
|
||||||
|
|
||||||
|
class GameInfo
|
||||||
|
attr_accessor :game_flags, :score_limit, :time_limit, :match_num, :match_current
|
||||||
|
|
||||||
|
def initialize(attr = {})
|
||||||
|
@game_flags = attr[:game_flags] || 0
|
||||||
|
@score_limit = attr[:score_limit] || 0
|
||||||
|
@time_limit = attr[:time_limit] || 0
|
||||||
|
@match_num = attr[:match_num] || 0
|
||||||
|
@match_current = attr[:match_current] || 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# basically to_network
|
||||||
|
# int array the server sends to the client
|
||||||
|
def to_a
|
||||||
|
Packer.pack_int(@game_flags) +
|
||||||
|
Packer.pack_int(@score_limit) +
|
||||||
|
Packer.pack_int(@time_limit) +
|
||||||
|
Packer.pack_int(@match_num) +
|
||||||
|
Packer.pack_int(@match_current)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'map'
|
require_relative 'map'
|
||||||
|
require_relative 'server_info'
|
||||||
|
require_relative 'game_info'
|
||||||
|
|
||||||
class GameServer
|
class GameServer
|
||||||
attr_accessor :pred_game_tick, :ack_game_tick, :map
|
attr_accessor :pred_game_tick, :ack_game_tick, :map
|
||||||
|
@ -28,4 +30,36 @@ class GameServer
|
||||||
|
|
||||||
@server.send_map(packet.addr)
|
@server.send_map(packet.addr)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def on_ready(_chunk, packet)
|
||||||
|
# vanilla server sends 3 chunks here usually
|
||||||
|
# - motd
|
||||||
|
# - server settings
|
||||||
|
# - ready
|
||||||
|
#
|
||||||
|
# We only send ready for now
|
||||||
|
@server.send_ready(packet.addr)
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_startinfo(_chunk, packet)
|
||||||
|
# vanilla server sends 3 chunks here usually
|
||||||
|
# - vote clear options
|
||||||
|
# - tune params
|
||||||
|
# - ready to enter
|
||||||
|
#
|
||||||
|
# We only send ready to enter for now
|
||||||
|
@server.send_ready_to_enter(packet.addr)
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_enter_game(_chunk, packet)
|
||||||
|
# vanilla server responds to enter game with two packets
|
||||||
|
# first:
|
||||||
|
# - server info
|
||||||
|
# second:
|
||||||
|
# - game info
|
||||||
|
# - client info
|
||||||
|
# - snap single
|
||||||
|
@server.send_server_info(packet.addr, ServerInfo.new.to_a)
|
||||||
|
@server.send_game_info(packet.addr, GameInfo.new.to_a)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
class NetBase
|
class NetBase
|
||||||
attr_accessor :peer_token, :ack
|
attr_accessor :peer_token, :ack
|
||||||
|
|
||||||
def initialize
|
def initialize(opts = {})
|
||||||
|
@verbose = opts[:verbose] || false
|
||||||
@ip = nil
|
@ip = nil
|
||||||
@port = nil
|
@port = nil
|
||||||
@s = nil
|
@s = nil
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Player
|
class Player
|
||||||
attr_accessor :id, :local, :team, :name, :clan, :country, :skin_parts, :skin_custom_colors, :skin_colors
|
attr_accessor :id, :local, :team, :name, :clan, :country, :skin_parts, :skin_custom_colors, :skin_colors, :score
|
||||||
|
|
||||||
def initialize(data = {})
|
def initialize(data = {})
|
||||||
@id = data[:id] || -1
|
@id = data[:id] || -1
|
||||||
|
@ -13,5 +13,7 @@ class Player
|
||||||
@skin_parts = data[:skin_parts] || Array.new(6, 'standard')
|
@skin_parts = data[:skin_parts] || Array.new(6, 'standard')
|
||||||
@skin_custom_colors = data[:skin_custom_colors] || Array.new(6, 0)
|
@skin_custom_colors = data[:skin_custom_colors] || Array.new(6, 0)
|
||||||
@skin_colors = data[:skin_colors] || Array.new(6, 0)
|
@skin_colors = data[:skin_colors] || Array.new(6, 0)
|
||||||
|
|
||||||
|
@score = data[:score] || 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,16 +1,64 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ServerInfo
|
require_relative 'network'
|
||||||
attr_reader :version, :name, :map, :gametype
|
require_relative 'player'
|
||||||
|
require_relative 'packer'
|
||||||
|
|
||||||
def initialize(infos)
|
class ServerInfo
|
||||||
@version = infos[0]
|
attr_accessor :version, :name, :hostname, :map, :gametype, :flags, :num_players, :max_players, :num_clients,
|
||||||
@name = infos[1]
|
:max_clients, :players
|
||||||
@map = infos[2]
|
|
||||||
@gametype = infos[3]
|
def initialize
|
||||||
|
# short tokenless version
|
||||||
|
@version = GAME_VERSION # '0.7.5'
|
||||||
|
@name = 'unnamed ruby server'
|
||||||
|
@hostname = 'localhost'
|
||||||
|
@map = 'dm1'
|
||||||
|
@gametype = 'dm'
|
||||||
|
@flags = 0
|
||||||
|
@num_players = 1
|
||||||
|
@max_players = MAX_PLAYERS
|
||||||
|
@num_clients = 1
|
||||||
|
@max_clients = MAX_CLIENTS
|
||||||
|
|
||||||
|
# token only
|
||||||
|
@players = [
|
||||||
|
Player.new(
|
||||||
|
id: 0,
|
||||||
|
local: 0,
|
||||||
|
team: 0,
|
||||||
|
name: 'sample player',
|
||||||
|
clan: '',
|
||||||
|
country: -1
|
||||||
|
)
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
"version=#{@version} gametype=#{gametype} map=#{map} name=#{name}"
|
"version=#{@version} gametype=#{gametype} map=#{map} name=#{name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# basically to_network
|
||||||
|
# int array the server sends to the client
|
||||||
|
def to_a
|
||||||
|
data = []
|
||||||
|
data = Packer.pack_str(@version) +
|
||||||
|
Packer.pack_str(@name) +
|
||||||
|
Packer.pack_str(@hostname) +
|
||||||
|
Packer.pack_str(@map) +
|
||||||
|
Packer.pack_str(@gametype) +
|
||||||
|
Packer.pack_int(@flags) +
|
||||||
|
Packer.pack_int(@num_players) +
|
||||||
|
Packer.pack_int(@max_players) +
|
||||||
|
Packer.pack_int(@num_clients) +
|
||||||
|
Packer.pack_int(@max_clients)
|
||||||
|
@players.each do |player|
|
||||||
|
data += Packer.pack_str(player.name)
|
||||||
|
data += Packer.pack_str(player.clan)
|
||||||
|
data += Packer.pack_int(player.country)
|
||||||
|
data += Packer.pack_int(player.score)
|
||||||
|
data += Packer.pack_int(0) # TODO: bot and spec flag
|
||||||
|
end
|
||||||
|
data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -106,7 +106,7 @@ class TeeworldsClient
|
||||||
@client_token = (1..4).to_a.map { |_| rand(0..255) }
|
@client_token = (1..4).to_a.map { |_| rand(0..255) }
|
||||||
@client_token = @client_token.map { |b| b.to_s(16) }.join
|
@client_token = @client_token.map { |b| b.to_s(16) }.join
|
||||||
puts "client token #{@client_token}"
|
puts "client token #{@client_token}"
|
||||||
@netbase = NetBase.new
|
@netbase = NetBase.new(verbose: @verbose)
|
||||||
NetChunk.reset
|
NetChunk.reset
|
||||||
@ip = ip
|
@ip = ip
|
||||||
@port = port
|
@port = port
|
||||||
|
|
|
@ -36,7 +36,7 @@ class TeeworldsServer
|
||||||
@server_token = (1..4).to_a.map { |_| rand(0..255) }
|
@server_token = (1..4).to_a.map { |_| rand(0..255) }
|
||||||
@server_token = @server_token.map { |b| b.to_s(16) }.join
|
@server_token = @server_token.map { |b| b.to_s(16) }.join
|
||||||
puts "server token #{@server_token}"
|
puts "server token #{@server_token}"
|
||||||
@netbase = NetBase.new
|
@netbase = NetBase.new(verbose: @verbose)
|
||||||
NetChunk.reset
|
NetChunk.reset
|
||||||
@ip = ip
|
@ip = ip
|
||||||
@port = port
|
@port = port
|
||||||
|
@ -51,19 +51,29 @@ class TeeworldsServer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def on_system_chunk(chunk)
|
def on_message(chunk, packet)
|
||||||
puts "got system chunk: #{chunk}"
|
puts "got game chunk: #{chunk}"
|
||||||
|
case chunk.msg
|
||||||
|
when NETMSGTYPE_CL_STARTINFO then @game_server.on_startinfo(chunk, packet)
|
||||||
|
else
|
||||||
|
puts "Unsupported game msg: #{chunk.msg}"
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_chunk(chunk, packet)
|
def process_chunk(chunk, packet)
|
||||||
unless chunk.sys
|
unless chunk.sys
|
||||||
on_system_chunk(chunk)
|
on_message(chunk, packet)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
puts "proccess chunk with msg: #{chunk.msg}"
|
puts "proccess chunk with msg: #{chunk.msg}"
|
||||||
case chunk.msg
|
case chunk.msg
|
||||||
when NETMSG_INFO
|
when NETMSG_INFO
|
||||||
@game_server.on_info(chunk, packet)
|
@game_server.on_info(chunk, packet)
|
||||||
|
when NETMSG_READY
|
||||||
|
@game_server.on_ready(chunk, packet)
|
||||||
|
when NETMSG_ENTERGAME
|
||||||
|
@game_server.on_enter_game(chunk, packet)
|
||||||
else
|
else
|
||||||
puts "Unsupported system msg: #{chunk.msg}"
|
puts "Unsupported system msg: #{chunk.msg}"
|
||||||
exit(1)
|
exit(1)
|
||||||
|
@ -109,12 +119,38 @@ class TeeworldsServer
|
||||||
data += Packer.pack_int(8) # chunk num?
|
data += Packer.pack_int(8) # chunk num?
|
||||||
data += Packer.pack_int(MAP_CHUNK_SIZE)
|
data += Packer.pack_int(MAP_CHUNK_SIZE)
|
||||||
data += @game_server.map.sha256_arr # poor mans pack_raw()
|
data += @game_server.map.sha256_arr # poor mans pack_raw()
|
||||||
msg = NetChunk.create_non_vital_header(size: data.size + 1) +
|
msg = NetChunk.create_vital_header({ vital: true }, data.size + 1) +
|
||||||
[pack_msg_id(NETMSG_MAP_CHANGE, system: true)] +
|
[pack_msg_id(NETMSG_MAP_CHANGE, system: true)] +
|
||||||
data
|
data
|
||||||
@netbase.send_packet(msg, 1, addr:)
|
@netbase.send_packet(msg, 1, addr:)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def send_ready(addr)
|
||||||
|
msg = NetChunk.create_vital_header({ vital: true }, 1) +
|
||||||
|
[pack_msg_id(NETMSG_CON_READY, system: true)]
|
||||||
|
@netbase.send_packet(msg, 1, addr:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_ready_to_enter(addr)
|
||||||
|
msg = NetChunk.create_vital_header({ vital: true }, 1) +
|
||||||
|
[pack_msg_id(NETMSGTYPE_SV_READYTOENTER, system: false)]
|
||||||
|
@netbase.send_packet(msg, 1, addr:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_server_info(addr, server_info)
|
||||||
|
msg = NetChunk.create_vital_header({ vital: true }, 1 + server_info.size) +
|
||||||
|
[pack_msg_id(NETMSG_SERVERINFO, system: true)] +
|
||||||
|
server_info
|
||||||
|
@netbase.send_packet(msg, 1, addr:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_game_info(addr, data)
|
||||||
|
msg = NetChunk.create_vital_header({ vital: true }, 1 + data.size) +
|
||||||
|
[pack_msg_id(NETMSGTYPE_SV_GAMEINFO, system: false)] +
|
||||||
|
data
|
||||||
|
@netbase.send_packet(msg, 1, addr:)
|
||||||
|
end
|
||||||
|
|
||||||
def on_ctrl_token(packet)
|
def on_ctrl_token(packet)
|
||||||
u = Unpacker.new(packet.payload[1..])
|
u = Unpacker.new(packet.payload[1..])
|
||||||
token = u.get_raw(4)
|
token = u.get_raw(4)
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
|
|
||||||
require_relative 'lib/teeworlds_server'
|
require_relative 'lib/teeworlds_server'
|
||||||
|
|
||||||
srv = TeeworldsServer.new(verbose: false)
|
srv = TeeworldsServer.new(verbose: true)
|
||||||
srv.run('127.0.0.1', 8303)
|
srv.run('127.0.0.1', 8303)
|
||||||
|
|
20
spec/06_server_info_spec.rb
Normal file
20
spec/06_server_info_spec.rb
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative '../lib/server_info'
|
||||||
|
|
||||||
|
describe 'ServerInfo', :server_info do
|
||||||
|
context 'Pack to network' do
|
||||||
|
it 'Should match expected array' do
|
||||||
|
arr = [
|
||||||
|
0x30, 0x2E, 0x37, 0x2E, 0x35, 0x00, 0x75, 0x6E, 0x6E, 0x61,
|
||||||
|
0x6D, 0x65, 0x64, 0x20, 0x72, 0x75, 0x62, 0x79, 0x20, 0x73,
|
||||||
|
0x65, 0x72, 0x76, 0x65, 0x72, 0x00, 0x6C, 0x6F, 0x63, 0x61,
|
||||||
|
0x6C, 0x68, 0x6F, 0x73, 0x74, 0x00, 0x64, 0x6D, 0x31, 0x00,
|
||||||
|
0x64, 0x6D, 0x00, 0x00, 0x01, 0x10, 0x01, 0x80, 0x01, 0x73,
|
||||||
|
0x61, 0x6D, 0x70, 0x6C, 0x65, 0x20, 0x70, 0x6C, 0x61, 0x79,
|
||||||
|
0x65, 0x72, 0x00, 0x00, 0x40, 0x00, 0x00
|
||||||
|
]
|
||||||
|
expect(ServerInfo.new.to_a).to eq(arr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
11
spec/07_game_info_spec.rb
Normal file
11
spec/07_game_info_spec.rb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative '../lib/game_info'
|
||||||
|
|
||||||
|
describe 'GameInfo', :game_info do
|
||||||
|
context 'Pack to network' do
|
||||||
|
it 'Should match expected array' do
|
||||||
|
expect(GameInfo.new.to_a).to eq([0, 0, 0, 0, 0])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue