Start working on server side map packet
This commit is contained in:
parent
0a04af1cb6
commit
1baf3fcad0
|
@ -1,5 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# I JUST REALIZED I ALREADY USED .scan(/../)
|
||||
# TO GET .groups_of(2)
|
||||
# AND DUUUUH .scan() IS BASICALLY ALREADY
|
||||
# .groups_of()
|
||||
# TODO: get rid of it?!
|
||||
class Array
|
||||
def groups_of(max_size)
|
||||
return [] if max_size < 1
|
||||
|
|
|
@ -1,22 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'map'
|
||||
|
||||
class GameServer
|
||||
attr_accessor :pred_game_tick, :ack_game_tick
|
||||
attr_accessor :pred_game_tick, :ack_game_tick, :map
|
||||
|
||||
def initialize(server)
|
||||
@server = server
|
||||
@ack_game_tick = -1
|
||||
@pred_game_tick = 0
|
||||
@map = Map.new(
|
||||
name: 'dm1',
|
||||
crc: 1_683_261_464,
|
||||
size: 6793,
|
||||
sha256: '491af17a510214506270904f147a4c30ae0a85b91bb854395bef8c397fc078c3'
|
||||
)
|
||||
end
|
||||
|
||||
def on_info(chunk)
|
||||
def on_info(chunk, packet)
|
||||
u = Unpacker.new(chunk.data[1..])
|
||||
net_version = u.get_string
|
||||
password = u.get_string
|
||||
client_version = u.get_int
|
||||
puts "vers=#{net_version} vers=#{client_version} pass=#{password}"
|
||||
|
||||
# TODO: respond with map info
|
||||
# here tho? Check tw code when to send map info
|
||||
# TODO: check version and password
|
||||
|
||||
@server.send_map(packet.addr)
|
||||
end
|
||||
end
|
||||
|
|
46
lib/map.rb
Normal file
46
lib/map.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'bytes'
|
||||
|
||||
class Map
|
||||
attr_reader :name, :crc, :size, :sha256, :sha256_str, :sha256_arr
|
||||
|
||||
def initialize(attr = {})
|
||||
# map name as String
|
||||
@name = attr[:name]
|
||||
|
||||
# crc has to be a positive Integer
|
||||
@crc = attr[:crc]
|
||||
|
||||
# size has to be a positive Integer
|
||||
@size = attr[:size]
|
||||
|
||||
# sha256 can be:
|
||||
# hex encoded string (64 characters / 32 bytes)
|
||||
# '491af17a510214506270904f147a4c30ae0a85b91bb854395bef8c397fc078c3'
|
||||
#
|
||||
# raw string (32 characters)
|
||||
# array of integers representing the bytes (32 elements)
|
||||
@sha256 = attr[:sha256]
|
||||
|
||||
if @sha256.instance_of?(String)
|
||||
if @sha256.match(/[a-fA-F0-9]{64}/) # str encoded hex
|
||||
@sha256_str = @sha256
|
||||
@sha256_arr = str_bytes(@sha256)
|
||||
@sha256 = @sha256_arr.pack('C*')
|
||||
elsif @sha256.length == 32 # raw byte string
|
||||
@sha256_arr = @sha256
|
||||
@sha256 = @sha256_arr.pack('C*')
|
||||
@sha256_str = str_hex(@sha256).gsub(' ', '')
|
||||
else
|
||||
raise "Error: map raw string expects size 32 but got #{@sha256.size}"
|
||||
end
|
||||
elsif @sha256.instance_of?(Array) # int byte array
|
||||
raise "Error: map sha256 array expects size 32 but got #{@sha256.size}" if @sha256.size != 32
|
||||
|
||||
@sha256_arr = @sha256
|
||||
@sha256 = @sha256.pack('C*')
|
||||
@sha256_str = @sha256.map { |b| b.to_s(16) }.join
|
||||
end
|
||||
end
|
||||
end
|
11
lib/message.rb
Normal file
11
lib/message.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
##
|
||||
# Turns int into network byte
|
||||
#
|
||||
# Takes a NETMSGTYPE_CL_* integer
|
||||
# and returns a byte that can be send over
|
||||
# the network
|
||||
def pack_msg_id(msg_id, options = { system: false })
|
||||
(msg_id << 1) | (options[:system] ? 1 : 0)
|
||||
end
|
|
@ -11,4 +11,8 @@ class NetAddr
|
|||
def to_s
|
||||
"#{@ip}:#{@port}"
|
||||
end
|
||||
|
||||
def eq(addr)
|
||||
@ip == addr.ip && @port == addr.port
|
||||
end
|
||||
end
|
||||
|
|
|
@ -87,7 +87,14 @@ NET_CONNSTATE_PENDING = 3
|
|||
NET_CONNSTATE_ONLINE = 4
|
||||
NET_CONNSTATE_ERROR = 5
|
||||
|
||||
NET_MAX_CHUNKHEADERSIZE = 3
|
||||
|
||||
NET_PACKETHEADERSIZE = 7
|
||||
NET_PACKETHEADERSIZE_CONNLESS = NET_PACKETHEADERSIZE + 2
|
||||
NET_MAX_PACKETHEADERSIZE = NET_PACKETHEADERSIZE_CONNLESS
|
||||
|
||||
NET_MAX_PACKETSIZE = 1400
|
||||
NET_MAX_PAYLOAD = NET_MAX_PACKETSIZE - NET_MAX_PACKETHEADERSIZE
|
||||
|
||||
CHAT_NONE = 0
|
||||
CHAT_ALL = 1
|
||||
|
@ -99,3 +106,8 @@ TARGET_SERVER = -1
|
|||
|
||||
PACKET_HEADER_SIZE = 7
|
||||
CHUNK_HEADER_SIZE = 3
|
||||
|
||||
MAX_CLIENTS = 64
|
||||
MAX_PLAYERS = 16
|
||||
|
||||
MAP_CHUNK_SIZE = NET_MAX_PAYLOAD - NET_MAX_CHUNKHEADERSIZE - 4 # msg type
|
||||
|
|
|
@ -1,53 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'net_addr'
|
||||
require_relative 'packet_flags'
|
||||
|
||||
require 'huffman_tw'
|
||||
|
||||
class PacketFlags
|
||||
attr_reader :bits, :hash
|
||||
|
||||
def initialize(data)
|
||||
@hash = {}
|
||||
@bits = ''
|
||||
if data.instance_of?(Hash)
|
||||
@bits = parse_hash(data)
|
||||
@hash = data
|
||||
elsif data.instance_of?(String)
|
||||
@hash = parse_bits(data)
|
||||
@bits = data
|
||||
else
|
||||
raise 'Flags have to be hash or string'
|
||||
end
|
||||
end
|
||||
|
||||
def parse_hash(hash)
|
||||
bits = ''
|
||||
bits += hash[:connection] ? '1' : '0'
|
||||
bits += hash[:compressed] ? '1' : '0'
|
||||
bits += hash[:resend] ? '1' : '0'
|
||||
bits += hash[:control] ? '1' : '0'
|
||||
bits
|
||||
end
|
||||
|
||||
def parse_bits(four_bit_str)
|
||||
# takes a 4 character string
|
||||
# representing the middle of the first byte sent
|
||||
# in binary representation
|
||||
#
|
||||
# and creates a hash out of it
|
||||
hash = {}
|
||||
hash[:connection] = four_bit_str[0] == '1'
|
||||
hash[:compressed] = four_bit_str[1] == '1'
|
||||
hash[:resend] = four_bit_str[2] == '1'
|
||||
hash[:control] = four_bit_str[3] == '1'
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
# Class holding the parsed packet data
|
||||
class Packet
|
||||
attr_reader :flags, :payload, :addr
|
||||
attr_accessor :client_id
|
||||
|
||||
def initialize(data, prefix = '')
|
||||
# @data and @payload
|
||||
|
@ -60,6 +21,7 @@ class Packet
|
|||
@prefix = prefix
|
||||
@addr = NetAddr.new(nil, nil)
|
||||
@huffman = Huffman.new
|
||||
@client_id = nil
|
||||
@data = data
|
||||
flags_byte = @data[0].unpack('B*')
|
||||
@flags = PacketFlags.new(flags_byte.first[2..5]).hash
|
||||
|
|
42
lib/packet_flags.rb
Normal file
42
lib/packet_flags.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PacketFlags
|
||||
attr_reader :bits, :hash
|
||||
|
||||
def initialize(data)
|
||||
@hash = {}
|
||||
@bits = ''
|
||||
if data.instance_of?(Hash)
|
||||
@bits = parse_hash(data)
|
||||
@hash = data
|
||||
elsif data.instance_of?(String)
|
||||
@hash = parse_bits(data)
|
||||
@bits = data
|
||||
else
|
||||
raise 'Flags have to be hash or string'
|
||||
end
|
||||
end
|
||||
|
||||
def parse_hash(hash)
|
||||
bits = ''
|
||||
bits += hash[:connection] ? '1' : '0'
|
||||
bits += hash[:compressed] ? '1' : '0'
|
||||
bits += hash[:resend] ? '1' : '0'
|
||||
bits += hash[:control] ? '1' : '0'
|
||||
bits
|
||||
end
|
||||
|
||||
def parse_bits(four_bit_str)
|
||||
# takes a 4 character string
|
||||
# representing the middle of the first byte sent
|
||||
# in binary representation
|
||||
#
|
||||
# and creates a hash out of it
|
||||
hash = {}
|
||||
hash[:connection] = four_bit_str[0] == '1'
|
||||
hash[:compressed] = four_bit_str[1] == '1'
|
||||
hash[:resend] = four_bit_str[2] == '1'
|
||||
hash[:control] = four_bit_str[3] == '1'
|
||||
hash
|
||||
end
|
||||
end
|
|
@ -13,6 +13,7 @@ require_relative 'net_base'
|
|||
require_relative 'packer'
|
||||
require_relative 'player'
|
||||
require_relative 'game_client'
|
||||
require_relative 'message'
|
||||
|
||||
class TeeworldsClient
|
||||
attr_reader :state, :hooks, :game_client
|
||||
|
@ -246,16 +247,6 @@ class TeeworldsClient
|
|||
)
|
||||
end
|
||||
|
||||
##
|
||||
# Turns int into network byte
|
||||
#
|
||||
# Takes a NETMSGTYPE_CL_* integer
|
||||
# and returns a byte that can be send over
|
||||
# the network
|
||||
def pack_msg_id(msg_id, options = { system: false })
|
||||
(msg_id << 1) | (options[:system] ? 1 : 0)
|
||||
end
|
||||
|
||||
def send_input
|
||||
inp = {
|
||||
direction: -1,
|
||||
|
|
|
@ -12,6 +12,16 @@ require_relative 'net_base'
|
|||
require_relative 'net_addr'
|
||||
require_relative 'packer'
|
||||
require_relative 'game_server'
|
||||
require_relative 'message'
|
||||
|
||||
class Client
|
||||
attr_accessor :id, :addr
|
||||
|
||||
def initialize(attr = {})
|
||||
@id = attr[:id]
|
||||
@addr = attr[:addr]
|
||||
end
|
||||
end
|
||||
|
||||
class TeeworldsServer
|
||||
def initialize(options = {})
|
||||
|
@ -19,6 +29,7 @@ class TeeworldsServer
|
|||
@ip = '127.0.0.1'
|
||||
@port = 8303
|
||||
@game_server = GameServer.new(self)
|
||||
@clients = {}
|
||||
end
|
||||
|
||||
def run(ip, port)
|
||||
|
@ -44,7 +55,7 @@ class TeeworldsServer
|
|||
puts "got system chunk: #{chunk}"
|
||||
end
|
||||
|
||||
def process_chunk(chunk)
|
||||
def process_chunk(chunk, packet)
|
||||
unless chunk.sys
|
||||
on_system_chunk(chunk)
|
||||
return
|
||||
|
@ -52,7 +63,7 @@ class TeeworldsServer
|
|||
puts "proccess chunk with msg: #{chunk.msg}"
|
||||
case chunk.msg
|
||||
when NETMSG_INFO
|
||||
@game_server.on_info(chunk)
|
||||
@game_server.on_info(chunk, packet)
|
||||
else
|
||||
puts "Unsupported system msg: #{chunk.msg}"
|
||||
exit(1)
|
||||
|
@ -66,7 +77,7 @@ class TeeworldsServer
|
|||
@netbase.ack = (@netbase.ack + 1) % NET_MAX_SEQUENCE
|
||||
puts "got ack: #{@netbase.ack}" if @verbose
|
||||
end
|
||||
process_chunk(chunk)
|
||||
process_chunk(chunk, packet)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -90,6 +101,20 @@ class TeeworldsServer
|
|||
@netbase.send_packet(msg, 0, control: true, addr:)
|
||||
end
|
||||
|
||||
def send_map(addr)
|
||||
data = []
|
||||
data += Packer.pack_str(@game_server.map.name)
|
||||
data += Packer.pack_int(@game_server.map.crc)
|
||||
data += Packer.pack_int(@game_server.map.size)
|
||||
data += Packer.pack_int(8) # chunk num?
|
||||
data += Packer.pack_int(MAP_CHUNK_SIZE)
|
||||
data += @game_server.map.sha256_arr # poor mans pack_raw()
|
||||
msg = NetChunk.create_non_vital_header(size: data.size + 1) +
|
||||
[pack_msg_id(NETMSG_MAP_CHANGE, system: true)] +
|
||||
data
|
||||
@netbase.send_packet(msg, 1, addr:)
|
||||
end
|
||||
|
||||
def on_ctrl_token(packet)
|
||||
u = Unpacker.new(packet.payload[1..])
|
||||
token = u.get_raw(4)
|
||||
|
@ -106,7 +131,15 @@ class TeeworldsServer
|
|||
end
|
||||
|
||||
def on_ctrl_connect(packet)
|
||||
puts "Got connect from #{packet.addr}"
|
||||
puts 'got connection, sending accept'
|
||||
|
||||
id = get_next_client_id
|
||||
if id == -1
|
||||
puts 'server full drop packet. TODO: tell the client'
|
||||
return
|
||||
end
|
||||
client = Client.new(id:, addr: packet.addr)
|
||||
@clients[id] = client
|
||||
@netbase.send_packet([NET_CTRLMSG_ACCEPT], 0, control: true, addr: packet.addr)
|
||||
end
|
||||
|
||||
|
@ -119,18 +152,33 @@ class TeeworldsServer
|
|||
end
|
||||
end
|
||||
|
||||
def get_next_client_id
|
||||
(0..MAX_CLIENTS).each do |i|
|
||||
next if @clients[i]
|
||||
|
||||
return i
|
||||
end
|
||||
-1
|
||||
end
|
||||
|
||||
def tick
|
||||
begin
|
||||
data, client = @s.recvfrom_nonblock(1400)
|
||||
data, sender_inet_addr = @s.recvfrom_nonblock(1400)
|
||||
rescue IO::EAGAINWaitReadable
|
||||
data = nil
|
||||
client = nil
|
||||
sender_inet_addr = nil
|
||||
end
|
||||
return unless data
|
||||
|
||||
packet = Packet.new(data, '<')
|
||||
packet.addr.ip = client[2] # or 3 idk bot 127.0.0.1 in my local test case
|
||||
packet.addr.port = client[1]
|
||||
packet.addr.ip = sender_inet_addr[2] # or 3 idk bot 127.0.0.1 in my local test case
|
||||
packet.addr.port = sender_inet_addr[1]
|
||||
@clients.each do |id, client|
|
||||
next unless packet.addr.eq(client.addr)
|
||||
|
||||
packet.client_id = id
|
||||
end
|
||||
|
||||
puts packet.to_s if @verbose
|
||||
on_packet(packet)
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue