2022-11-05 16:48:47 +00:00
|
|
|
# frozen_string_literal: true
|
2022-10-25 17:42:20 +00:00
|
|
|
|
|
|
|
require 'socket'
|
|
|
|
|
2022-11-01 13:25:56 +00:00
|
|
|
require_relative 'string'
|
|
|
|
require_relative 'array'
|
|
|
|
require_relative 'bytes'
|
|
|
|
require_relative 'network'
|
|
|
|
require_relative 'packet'
|
|
|
|
require_relative 'chunk'
|
2022-11-16 08:37:47 +00:00
|
|
|
require_relative 'messages/server_info'
|
2022-11-01 13:25:56 +00:00
|
|
|
require_relative 'net_base'
|
2022-11-04 10:39:41 +00:00
|
|
|
require_relative 'packer'
|
2022-11-13 07:54:46 +00:00
|
|
|
require_relative 'models/player'
|
2022-11-04 15:26:24 +00:00
|
|
|
require_relative 'game_client'
|
2022-11-24 09:39:30 +00:00
|
|
|
require_relative 'config'
|
2024-06-24 03:42:08 +00:00
|
|
|
require_relative 'connection'
|
2022-10-30 10:18:15 +00:00
|
|
|
|
2022-11-04 15:26:24 +00:00
|
|
|
class TeeworldsClient
|
2023-10-22 06:19:50 +00:00
|
|
|
attr_reader :state, :hooks, :game_client, :verbose_snap
|
2022-11-19 12:19:22 +00:00
|
|
|
attr_accessor :rcon_authed, :local_client_id
|
2022-10-25 17:42:20 +00:00
|
|
|
|
2022-10-31 07:45:43 +00:00
|
|
|
def initialize(options = {})
|
|
|
|
@verbose = options[:verbose] || false
|
2023-10-22 06:19:50 +00:00
|
|
|
@verbose_snap = options[:verbose_snap] || false
|
2022-10-25 17:42:20 +00:00
|
|
|
@state = NET_CONNSTATE_OFFLINE
|
|
|
|
@ip = 'localhost'
|
|
|
|
@port = 8303
|
2022-11-19 12:19:22 +00:00
|
|
|
@local_client_id = 0
|
2022-11-24 09:39:30 +00:00
|
|
|
@config = Config.new(file: options[:config])
|
2022-11-14 09:25:28 +00:00
|
|
|
@hooks = {
|
|
|
|
chat: [],
|
|
|
|
map_change: [],
|
|
|
|
client_info: [],
|
|
|
|
client_drop: [],
|
|
|
|
connected: [],
|
|
|
|
disconnect: [],
|
|
|
|
rcon_line: [],
|
2022-11-15 09:33:05 +00:00
|
|
|
snapshot: [],
|
2022-11-16 09:50:54 +00:00
|
|
|
input_timing: [],
|
|
|
|
auth_on: [],
|
|
|
|
auth_off: [],
|
|
|
|
rcon_cmd_add: [],
|
|
|
|
rcon_cmd_rem: [],
|
2023-09-17 17:50:09 +00:00
|
|
|
tick: [],
|
2022-11-16 09:50:54 +00:00
|
|
|
maplist_entry_add: [],
|
|
|
|
maplist_entry_rem: []
|
2022-11-14 09:25:28 +00:00
|
|
|
}
|
2022-11-01 14:27:39 +00:00
|
|
|
@thread_running = false
|
|
|
|
@signal_disconnect = false
|
2022-11-04 15:26:24 +00:00
|
|
|
@game_client = GameClient.new(self)
|
2022-11-04 12:22:29 +00:00
|
|
|
@start_info = {
|
2022-11-05 16:19:05 +00:00
|
|
|
name: 'ruby gamer',
|
|
|
|
clan: '',
|
2022-11-04 12:22:29 +00:00
|
|
|
country: -1,
|
2022-11-05 16:19:05 +00:00
|
|
|
body: 'spiky',
|
|
|
|
marking: 'duodonny',
|
|
|
|
decoration: '',
|
|
|
|
hands: 'standard',
|
|
|
|
feet: 'standard',
|
|
|
|
eyes: 'standard',
|
2022-11-04 12:22:29 +00:00
|
|
|
custom_color_body: 0,
|
|
|
|
custom_color_marking: 0,
|
|
|
|
custom_color_decoration: 0,
|
|
|
|
custom_color_hands: 0,
|
|
|
|
custom_color_feet: 0,
|
|
|
|
custom_color_eyes: 0,
|
|
|
|
color_body: 0,
|
|
|
|
color_marking: 0,
|
|
|
|
color_decoration: 0,
|
|
|
|
color_hands: 0,
|
|
|
|
color_feet: 0,
|
|
|
|
color_eyes: 0
|
|
|
|
}
|
2022-11-16 09:50:54 +00:00
|
|
|
@rcon_authed = false
|
|
|
|
end
|
|
|
|
|
|
|
|
def rcon_authed?
|
|
|
|
@rcon_authed
|
|
|
|
end
|
|
|
|
|
2023-09-17 17:50:09 +00:00
|
|
|
def on_tick(&block)
|
|
|
|
@hooks[:tick].push(block)
|
|
|
|
end
|
|
|
|
|
2022-11-16 09:50:54 +00:00
|
|
|
def on_auth_on(&block)
|
|
|
|
@hooks[:auth_on].push(block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def on_auth_off(&block)
|
|
|
|
@hooks[:auth_off].push(block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def on_rcon_cmd_add(&block)
|
|
|
|
@hooks[:rcon_cmd_add].push(block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def on_rcon_cmd_rem(&block)
|
|
|
|
@hooks[:rcon_cmd_rem].push(block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def on_maplist_entry_add(&block)
|
|
|
|
@hooks[:maplist_entry_add].push(block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def on_maplist_entry_rem(&block)
|
|
|
|
@hooks[:maplist_entry_rem].push(block)
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
2022-11-05 08:39:16 +00:00
|
|
|
def on_chat(&block)
|
2022-11-14 09:25:28 +00:00
|
|
|
@hooks[:chat].push(block)
|
2022-11-01 13:25:56 +00:00
|
|
|
end
|
|
|
|
|
2022-11-05 09:35:40 +00:00
|
|
|
def on_map_change(&block)
|
2022-11-14 09:25:28 +00:00
|
|
|
@hooks[:map_change].push(block)
|
2022-11-05 09:35:40 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def on_client_info(&block)
|
2022-11-14 09:25:28 +00:00
|
|
|
@hooks[:client_info].push(block)
|
2022-11-05 09:35:40 +00:00
|
|
|
end
|
|
|
|
|
2022-11-05 10:59:36 +00:00
|
|
|
def on_client_drop(&block)
|
2022-11-14 09:25:28 +00:00
|
|
|
@hooks[:client_drop].push(block)
|
2022-11-05 10:59:36 +00:00
|
|
|
end
|
|
|
|
|
2022-11-05 10:07:16 +00:00
|
|
|
def on_connected(&block)
|
2022-11-14 09:25:28 +00:00
|
|
|
@hooks[:connected].push(block)
|
2022-11-05 10:07:16 +00:00
|
|
|
end
|
|
|
|
|
2022-11-12 15:47:12 +00:00
|
|
|
def on_disconnect(&block)
|
2022-11-14 09:25:28 +00:00
|
|
|
@hooks[:disconnect].push(block)
|
2022-11-12 15:47:12 +00:00
|
|
|
end
|
|
|
|
|
2022-11-06 17:26:14 +00:00
|
|
|
def on_rcon_line(&block)
|
2022-11-14 09:25:28 +00:00
|
|
|
@hooks[:rcon_line].push(block)
|
2022-11-06 17:26:14 +00:00
|
|
|
end
|
|
|
|
|
2022-11-13 11:28:38 +00:00
|
|
|
def on_snapshot(&block)
|
2022-11-14 09:25:28 +00:00
|
|
|
@hooks[:snapshot].push(block)
|
2022-11-06 19:08:32 +00:00
|
|
|
end
|
|
|
|
|
2022-11-15 09:33:05 +00:00
|
|
|
def on_input_timing(&block)
|
|
|
|
@hooks[:input_timing].push(block)
|
|
|
|
end
|
|
|
|
|
2022-11-04 15:57:50 +00:00
|
|
|
def send_chat(str)
|
|
|
|
@netbase.send_packet(
|
2022-11-12 09:13:29 +00:00
|
|
|
NetChunk.create_header(vital: true, size: 4 + str.length) +
|
2022-11-04 15:57:50 +00:00
|
|
|
[
|
|
|
|
pack_msg_id(NETMSGTYPE_CL_SAY),
|
|
|
|
CHAT_ALL,
|
|
|
|
64 # should use TARGET_SERVER (-1) instead of hacking 64 in here
|
|
|
|
] +
|
|
|
|
Packer.pack_str(str)
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2022-11-01 14:32:47 +00:00
|
|
|
def connect(ip, port, options = {})
|
|
|
|
options[:detach] = options[:detach] || false
|
2022-11-05 16:19:05 +00:00
|
|
|
if options[:detach] && @thread_running
|
|
|
|
puts 'Error: connection thread already running call disconnect() first'
|
|
|
|
return
|
2022-11-01 14:27:39 +00:00
|
|
|
end
|
2022-11-04 12:04:51 +00:00
|
|
|
disconnect
|
|
|
|
@signal_disconnect = false
|
2022-11-04 09:12:23 +00:00
|
|
|
@ticks = 0
|
2022-11-04 15:26:24 +00:00
|
|
|
@game_client = GameClient.new(self)
|
2022-11-04 11:30:41 +00:00
|
|
|
# me trying to write cool code
|
|
|
|
@client_token = (1..4).to_a.map { |_| rand(0..255) }
|
2022-11-11 13:37:41 +00:00
|
|
|
@client_token = @client_token.map { |b| b.to_s(16).rjust(2, '0') }.join
|
2022-11-04 09:12:23 +00:00
|
|
|
puts "client token #{@client_token}"
|
2022-11-11 12:42:11 +00:00
|
|
|
@netbase = NetBase.new(verbose: @verbose)
|
2022-11-04 11:55:01 +00:00
|
|
|
NetChunk.reset
|
2022-11-01 13:25:56 +00:00
|
|
|
@ip = ip
|
|
|
|
@port = port
|
|
|
|
puts "connecting to #{@ip}:#{@port} .."
|
2022-11-01 14:27:39 +00:00
|
|
|
@s = UDPSocket.new
|
2022-11-01 13:25:56 +00:00
|
|
|
@s.connect(ip, port)
|
2022-11-01 14:27:39 +00:00
|
|
|
puts "client port: #{@s.addr[1]}"
|
2022-11-01 13:25:56 +00:00
|
|
|
@netbase.connect(@s, @ip, @port)
|
2022-11-01 14:27:39 +00:00
|
|
|
@token = nil
|
2022-11-01 13:25:56 +00:00
|
|
|
send_ctrl_with_token
|
2022-11-01 14:32:47 +00:00
|
|
|
if options[:detach]
|
|
|
|
@thread_running = true
|
|
|
|
Thread.new do
|
|
|
|
connection_loop
|
2022-11-01 14:27:39 +00:00
|
|
|
end
|
2022-11-01 14:32:47 +00:00
|
|
|
else
|
|
|
|
connection_loop
|
2022-11-01 13:25:56 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-11-12 11:08:25 +00:00
|
|
|
# TODO: this is same in client and server
|
|
|
|
# move to NetBase???
|
|
|
|
def send_ctrl_close
|
2022-11-13 07:28:27 +00:00
|
|
|
@netbase&.send_packet([NET_CTRLMSG_CLOSE], chunks: 0, control: true)
|
2022-11-12 11:08:25 +00:00
|
|
|
end
|
|
|
|
|
2022-11-01 13:25:56 +00:00
|
|
|
def disconnect
|
2022-11-05 16:19:05 +00:00
|
|
|
puts 'disconnecting.'
|
2023-09-17 16:22:38 +00:00
|
|
|
send_ctrl_close unless @s.nil?
|
2022-11-05 16:48:47 +00:00
|
|
|
@s&.close
|
2023-09-17 16:22:38 +00:00
|
|
|
@s = nil
|
2022-11-01 14:27:39 +00:00
|
|
|
@signal_disconnect = true
|
2022-11-01 13:25:56 +00:00
|
|
|
end
|
|
|
|
|
2022-11-04 12:22:29 +00:00
|
|
|
def set_startinfo(info)
|
|
|
|
info.each do |key, value|
|
|
|
|
unless @start_info.key?(key)
|
|
|
|
puts "Error: invalid start info key '#{key}'"
|
|
|
|
puts " valid keys: #{@start_info.keys}"
|
|
|
|
exit 1
|
|
|
|
end
|
|
|
|
@start_info[key] = value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-25 17:42:20 +00:00
|
|
|
def send_msg(data)
|
2022-10-30 10:18:15 +00:00
|
|
|
@netbase.send_packet(data)
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
2022-11-05 16:19:05 +00:00
|
|
|
def send_ctrl_keepalive
|
2022-11-13 07:28:27 +00:00
|
|
|
@netbase.send_packet([NET_CTRLMSG_KEEPALIVE], chunks: 0, control: true)
|
2022-10-29 15:04:35 +00:00
|
|
|
end
|
|
|
|
|
2022-11-05 16:19:05 +00:00
|
|
|
def send_msg_connect
|
2022-11-04 09:12:23 +00:00
|
|
|
msg = [NET_CTRLMSG_CONNECT] + str_bytes(@client_token) + Array.new(501, 0x00)
|
2022-11-13 07:28:27 +00:00
|
|
|
@netbase.send_packet(msg, chunks: 0, control: true)
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
2022-11-05 16:19:05 +00:00
|
|
|
def send_ctrl_with_token
|
2022-10-25 17:42:20 +00:00
|
|
|
@state = NET_CONNSTATE_TOKEN
|
2022-11-04 09:12:23 +00:00
|
|
|
msg = [NET_CTRLMSG_TOKEN] + str_bytes(@client_token) + Array.new(512, 0x00)
|
2022-11-13 07:28:27 +00:00
|
|
|
@netbase.send_packet(msg, chunks: 0, control: true)
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
2022-11-05 16:19:05 +00:00
|
|
|
def send_info
|
2022-11-04 11:30:41 +00:00
|
|
|
data = []
|
|
|
|
data += Packer.pack_str(GAME_NETVERSION)
|
2022-11-24 09:39:30 +00:00
|
|
|
data += Packer.pack_str(@config.password)
|
2022-11-04 11:30:41 +00:00
|
|
|
data += Packer.pack_int(CLIENT_VERSION)
|
2022-11-12 09:13:29 +00:00
|
|
|
msg = NetChunk.create_header(vital: true, size: data.size + 1) +
|
2022-11-05 16:19:05 +00:00
|
|
|
[pack_msg_id(NETMSG_INFO, system: true)] +
|
|
|
|
data
|
2022-11-04 11:30:41 +00:00
|
|
|
|
2022-11-13 07:28:27 +00:00
|
|
|
@netbase.send_packet(msg)
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
2022-11-06 17:26:14 +00:00
|
|
|
def rcon_auth(name, password = nil)
|
|
|
|
if name.instance_of?(Hash)
|
|
|
|
password = name[:password]
|
|
|
|
name = name[:name]
|
|
|
|
end
|
|
|
|
if password.nil?
|
|
|
|
raise "Error: password can not be empty\n" \
|
|
|
|
" provide two strings: name, password\n" \
|
|
|
|
" or a hash with the key :password\n" \
|
|
|
|
"\n" \
|
|
|
|
" rcon_auth('', '123')\n" \
|
|
|
|
" rcon_auth(password: '123')\n"
|
|
|
|
end
|
|
|
|
data = []
|
|
|
|
if name.nil? || name == ''
|
|
|
|
data += Packer.pack_str(password)
|
|
|
|
else # ddnet auth using name, password and some int?
|
|
|
|
data += Packer.pack_str(name)
|
|
|
|
data += Packer.pack_str(password)
|
|
|
|
data += Packer.pack_int(1)
|
|
|
|
end
|
2022-11-12 09:13:29 +00:00
|
|
|
msg = NetChunk.create_header(vital: true, size: data.size + 1) +
|
2022-11-06 17:26:14 +00:00
|
|
|
[pack_msg_id(NETMSG_RCON_AUTH, system: true)] +
|
|
|
|
data
|
2022-11-13 07:28:27 +00:00
|
|
|
@netbase.send_packet(msg)
|
2022-11-06 17:26:14 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def rcon(command)
|
|
|
|
data = []
|
|
|
|
data += Packer.pack_str(command)
|
2022-11-12 09:13:29 +00:00
|
|
|
msg = NetChunk.create_header(vital: true, size: data.size + 1) +
|
2022-11-06 17:26:14 +00:00
|
|
|
[pack_msg_id(NETMSG_RCON_CMD, system: true)] +
|
|
|
|
data
|
2022-11-13 07:28:27 +00:00
|
|
|
@netbase.send_packet(msg)
|
2022-11-06 17:26:14 +00:00
|
|
|
end
|
|
|
|
|
2022-11-13 09:37:46 +00:00
|
|
|
def send_msg_start_info
|
2022-11-04 10:39:41 +00:00
|
|
|
data = []
|
|
|
|
|
2022-11-04 12:22:29 +00:00
|
|
|
@start_info.each do |key, value|
|
2022-11-05 16:19:05 +00:00
|
|
|
if value.instance_of?(String)
|
2022-11-04 11:30:41 +00:00
|
|
|
data += Packer.pack_str(value)
|
2022-11-05 16:19:05 +00:00
|
|
|
elsif value.instance_of?(Integer)
|
2022-11-04 10:39:41 +00:00
|
|
|
data += Packer.pack_int(value)
|
|
|
|
else
|
2022-11-13 09:37:46 +00:00
|
|
|
puts "Error: invalid start info #{key}: #{value}"
|
2022-11-04 10:39:41 +00:00
|
|
|
exit 1
|
|
|
|
end
|
|
|
|
end
|
2022-11-04 09:12:23 +00:00
|
|
|
|
2022-11-04 10:39:41 +00:00
|
|
|
@netbase.send_packet(
|
2022-11-12 09:13:29 +00:00
|
|
|
NetChunk.create_header(vital: true, size: data.size + 1) +
|
2022-11-04 10:39:41 +00:00
|
|
|
[pack_msg_id(NETMSGTYPE_CL_STARTINFO, system: false)] +
|
|
|
|
data
|
|
|
|
)
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
2022-11-05 16:19:05 +00:00
|
|
|
def send_msg_ready
|
2022-11-04 09:12:23 +00:00
|
|
|
@netbase.send_packet(
|
2022-11-12 09:13:29 +00:00
|
|
|
NetChunk.create_header(vital: true, size: 1) +
|
2022-11-05 16:19:05 +00:00
|
|
|
[pack_msg_id(NETMSG_READY, system: true)]
|
|
|
|
)
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
2022-11-05 16:19:05 +00:00
|
|
|
def send_enter_game
|
2022-11-01 09:52:48 +00:00
|
|
|
@netbase.send_packet(
|
2022-11-12 09:13:29 +00:00
|
|
|
NetChunk.create_header(vital: true, size: 1) +
|
2022-11-05 16:19:05 +00:00
|
|
|
[pack_msg_id(NETMSG_ENTERGAME, system: true)]
|
|
|
|
)
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
2023-09-17 17:42:47 +00:00
|
|
|
def send_input(input = {})
|
2022-11-06 19:08:32 +00:00
|
|
|
inp = {
|
2023-09-17 17:42:47 +00:00
|
|
|
direction: input[:direction] || -1,
|
|
|
|
target_x: input[:target_x] || 10,
|
|
|
|
target_y: input[:target_y] || 10,
|
|
|
|
jump: input[:jump] || rand(0..1),
|
|
|
|
fire: input[:fire] || 0,
|
|
|
|
hook: input[:hook] || 0,
|
|
|
|
player_flags: input[:player_flags] || 0,
|
|
|
|
wanted_weapon: input[:wanted_weapon] || 0,
|
|
|
|
next_weapon: input[:next_weapon] || 0,
|
|
|
|
prev_weapon: input[:prev_weapon] || 0
|
2022-11-06 19:08:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
data = []
|
|
|
|
data += Packer.pack_int(@game_client.ack_game_tick)
|
|
|
|
data += Packer.pack_int(@game_client.pred_game_tick)
|
|
|
|
data += Packer.pack_int(40) # magic size 40
|
|
|
|
data += Packer.pack_int(inp[:direction])
|
|
|
|
data += Packer.pack_int(inp[:target_x])
|
|
|
|
data += Packer.pack_int(inp[:target_y])
|
|
|
|
data += Packer.pack_int(inp[:jump])
|
|
|
|
data += Packer.pack_int(inp[:fire])
|
|
|
|
data += Packer.pack_int(inp[:hook])
|
|
|
|
data += Packer.pack_int(inp[:player_flags])
|
|
|
|
data += Packer.pack_int(inp[:wanted_weapon])
|
|
|
|
data += Packer.pack_int(inp[:next_weapon])
|
|
|
|
data += Packer.pack_int(inp[:prev_weapon])
|
2022-11-12 11:51:09 +00:00
|
|
|
msg = NetChunk.create_header(vital: false, size: data.size + 1) +
|
2022-11-06 19:08:32 +00:00
|
|
|
[pack_msg_id(NETMSG_INPUT, system: true)] +
|
|
|
|
data
|
2022-11-13 07:28:27 +00:00
|
|
|
@netbase.send_packet(msg)
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
2023-09-17 12:49:19 +00:00
|
|
|
private
|
|
|
|
|
2022-10-25 17:42:20 +00:00
|
|
|
def on_msg_token(data)
|
2023-09-17 12:49:19 +00:00
|
|
|
# TODO: add hook
|
2022-11-05 16:19:05 +00:00
|
|
|
@token = bytes_to_str(data)
|
2022-11-12 11:51:09 +00:00
|
|
|
@netbase.set_peer_token(@token)
|
2022-11-05 16:19:05 +00:00
|
|
|
puts "Got token #{@token}"
|
|
|
|
send_msg_connect
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def on_msg_accept
|
2023-09-17 12:49:19 +00:00
|
|
|
# TODO: add hook
|
2022-11-05 16:19:05 +00:00
|
|
|
puts 'got accept. connection online'
|
2022-10-25 17:42:20 +00:00
|
|
|
@state = NET_CONNSTATE_ONLINE
|
|
|
|
send_info
|
|
|
|
end
|
|
|
|
|
2022-11-24 08:53:21 +00:00
|
|
|
def on_msg_close(data)
|
|
|
|
@game_client.on_disconnect(data)
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
|
|
|
|
2022-10-29 10:09:10 +00:00
|
|
|
# CClient::ProcessConnlessPacket
|
|
|
|
def on_ctrl_message(msg, data)
|
|
|
|
case msg
|
|
|
|
when NET_CTRLMSG_TOKEN then on_msg_token(data)
|
|
|
|
when NET_CTRLMSG_ACCEPT then on_msg_accept
|
2022-11-24 08:53:21 +00:00
|
|
|
when NET_CTRLMSG_CLOSE then on_msg_close(data)
|
2022-11-05 16:19:05 +00:00
|
|
|
when NET_CTRLMSG_KEEPALIVE # silently ignore keepalive
|
2022-10-29 10:09:10 +00:00
|
|
|
else
|
2022-11-05 16:19:05 +00:00
|
|
|
puts "Uknown control message #{msg}"
|
|
|
|
exit(1)
|
2022-10-29 10:09:10 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-30 18:44:49 +00:00
|
|
|
def on_message(chunk)
|
|
|
|
case chunk.msg
|
2022-11-05 09:35:40 +00:00
|
|
|
when NETMSGTYPE_SV_READYTOENTER then @game_client.on_ready_to_enter(chunk)
|
|
|
|
when NETMSGTYPE_SV_CLIENTINFO then @game_client.on_client_info(chunk)
|
2022-11-05 10:59:36 +00:00
|
|
|
when NETMSGTYPE_SV_CLIENTDROP then @game_client.on_client_drop(chunk)
|
2022-11-05 09:35:40 +00:00
|
|
|
when NETMSGTYPE_SV_EMOTICON then @game_client.on_emoticon(chunk)
|
2022-11-04 15:26:24 +00:00
|
|
|
when NETMSGTYPE_SV_CHAT then @game_client.on_chat(chunk)
|
2022-10-30 18:44:49 +00:00
|
|
|
else
|
2022-11-05 16:19:05 +00:00
|
|
|
puts "todo non sys chunks. skipped msg: #{chunk.msg}" if @verbose
|
2022-10-30 18:44:49 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-29 14:18:07 +00:00
|
|
|
def process_chunk(chunk)
|
2022-11-05 16:19:05 +00:00
|
|
|
unless chunk.sys
|
2022-10-30 18:44:49 +00:00
|
|
|
on_message(chunk)
|
2022-10-29 14:18:07 +00:00
|
|
|
return
|
|
|
|
end
|
2022-11-16 09:50:54 +00:00
|
|
|
puts "proccess chunk with msg: #{chunk.msg}" if @verbose
|
2022-10-29 14:18:07 +00:00
|
|
|
case chunk.msg
|
|
|
|
when NETMSG_MAP_CHANGE
|
2022-11-05 09:35:40 +00:00
|
|
|
@game_client.on_map_change(chunk)
|
2022-10-30 18:44:49 +00:00
|
|
|
when NETMSG_SERVERINFO
|
2022-11-05 16:19:05 +00:00
|
|
|
puts 'ignore server info for now'
|
2022-10-29 14:18:07 +00:00
|
|
|
when NETMSG_CON_READY
|
2022-11-05 09:35:40 +00:00
|
|
|
@game_client.on_connected
|
2022-10-31 07:54:13 +00:00
|
|
|
when NETMSG_NULL
|
|
|
|
# should we be in alert here?
|
2022-11-06 17:26:14 +00:00
|
|
|
when NETMSG_RCON_LINE
|
|
|
|
@game_client.on_rcon_line(chunk)
|
2022-11-06 19:08:32 +00:00
|
|
|
when NETMSG_SNAP, NETMSG_SNAPSINGLE, NETMSG_SNAPEMPTY
|
|
|
|
@game_client.on_snapshot(chunk)
|
2022-11-15 09:33:05 +00:00
|
|
|
when NETMSG_INPUTTIMING
|
|
|
|
@game_client.on_input_timing(chunk)
|
2022-11-16 09:50:54 +00:00
|
|
|
when NETMSG_RCON_AUTH_ON
|
|
|
|
@game_client.on_auth_on
|
|
|
|
when NETMSG_RCON_AUTH_OFF
|
|
|
|
@game_client.on_auth_off
|
|
|
|
when NETMSG_RCON_CMD_ADD
|
|
|
|
@game_client.on_rcon_cmd_add(chunk)
|
|
|
|
when NETMSG_RCON_CMD_REM
|
|
|
|
@game_client.on_rcon_cmd_rem(chunk)
|
|
|
|
when NETMSG_MAPLIST_ENTRY_ADD
|
|
|
|
@game_client.on_maplist_entry_add(chunk)
|
|
|
|
when NETMSG_MAPLIST_ENTRY_REM
|
|
|
|
@game_client.on_maplist_entry_rem(chunk)
|
2022-10-29 14:18:07 +00:00
|
|
|
else
|
|
|
|
puts "Unsupported system msg: #{chunk.msg}"
|
2022-11-16 09:50:54 +00:00
|
|
|
p str_hex(chunk.full_raw)
|
2022-10-29 14:18:07 +00:00
|
|
|
exit(1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-11-04 08:27:36 +00:00
|
|
|
def process_server_packet(packet)
|
|
|
|
data = packet.payload
|
2024-01-20 14:37:18 +00:00
|
|
|
if data.empty?
|
2022-11-05 16:19:05 +00:00
|
|
|
puts 'Error: packet payload is empty'
|
2024-01-20 14:33:25 +00:00
|
|
|
puts packet
|
2022-11-04 08:27:36 +00:00
|
|
|
return
|
|
|
|
end
|
2022-10-29 14:18:07 +00:00
|
|
|
chunks = BigChungusTheChunkGetter.get_chunks(data)
|
|
|
|
chunks.each do |chunk|
|
2024-06-24 03:42:08 +00:00
|
|
|
if chunk.flags_vital
|
|
|
|
if chunk.seq == (@netbase.ack + 1) % NET_MAX_SEQUENCE
|
|
|
|
# in sequence
|
|
|
|
@netbase.ack = (@netbase.ack + 1) % NET_MAX_SEQUENCE
|
|
|
|
else
|
2024-06-24 03:45:45 +00:00
|
|
|
puts 'warning: got chunk out of sequence! ' \
|
|
|
|
"seq=#{chunk.seq} expected_seq=#{(@netbase.ack + 1) % NET_MAX_SEQUENCE}"
|
2024-06-24 03:42:08 +00:00
|
|
|
if seq_in_backroom?(chunk.seq, @netbase.ack)
|
|
|
|
puts ' dropping known chunk ...'
|
|
|
|
next
|
|
|
|
end
|
|
|
|
# TODO: request resend
|
|
|
|
puts ' REQUESTING RESEND NOT IMPLEMENTED'
|
|
|
|
end
|
2022-10-30 18:00:13 +00:00
|
|
|
end
|
2022-10-29 14:18:07 +00:00
|
|
|
process_chunk(chunk)
|
|
|
|
end
|
2022-10-29 10:09:10 +00:00
|
|
|
end
|
|
|
|
|
2022-10-25 17:42:20 +00:00
|
|
|
def tick
|
|
|
|
# puts "tick"
|
|
|
|
begin
|
|
|
|
pck = @s.recvfrom_nonblock(1400)
|
2022-11-20 09:13:07 +00:00
|
|
|
rescue Errno::ECONNREFUSED
|
|
|
|
puts 'connection problems ...'
|
|
|
|
pck = nil
|
2022-11-01 14:27:39 +00:00
|
|
|
rescue IO::EAGAINWaitReadable
|
2022-10-25 17:42:20 +00:00
|
|
|
pck = nil
|
2023-12-13 18:19:32 +00:00
|
|
|
rescue IO::EWOULDBLOCKWaitReadable # windows
|
|
|
|
pck = nil
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
2022-11-01 14:27:39 +00:00
|
|
|
if pck.nil? && @token.nil?
|
2022-11-05 16:19:05 +00:00
|
|
|
@wait_for_token ||= 0
|
2022-11-01 14:27:39 +00:00
|
|
|
@wait_for_token += 1
|
|
|
|
if @wait_for_token > 6
|
|
|
|
@token = nil
|
|
|
|
send_ctrl_with_token
|
2022-11-05 16:19:05 +00:00
|
|
|
puts 'retrying connection ...'
|
2022-11-01 14:27:39 +00:00
|
|
|
end
|
|
|
|
end
|
2023-09-17 17:50:09 +00:00
|
|
|
@game_client.on_tick
|
2022-10-25 17:42:20 +00:00
|
|
|
return unless pck
|
|
|
|
|
|
|
|
data = pck.first
|
2022-10-29 10:09:10 +00:00
|
|
|
|
2022-10-30 10:18:15 +00:00
|
|
|
packet = Packet.new(data, '<')
|
2024-01-20 14:33:25 +00:00
|
|
|
puts packet if @verbose
|
2022-10-29 10:09:10 +00:00
|
|
|
|
|
|
|
# process connless packets data
|
2022-10-29 10:16:44 +00:00
|
|
|
if packet.flags_control
|
2022-11-05 16:19:05 +00:00
|
|
|
msg = data[PACKET_HEADER_SIZE].unpack1('C*')
|
2022-10-29 11:17:42 +00:00
|
|
|
on_ctrl_message(msg, data[(PACKET_HEADER_SIZE + 1)..])
|
2022-10-29 10:16:44 +00:00
|
|
|
else # process non-connless packets
|
2022-11-04 08:27:36 +00:00
|
|
|
process_server_packet(packet)
|
2022-10-29 10:09:10 +00:00
|
|
|
end
|
2022-10-29 15:04:35 +00:00
|
|
|
|
2022-11-05 16:48:47 +00:00
|
|
|
send_ctrl_keepalive if (@ticks % 8).zero?
|
2022-11-06 19:08:32 +00:00
|
|
|
if @game_client.ack_game_tick.positive?
|
|
|
|
now = Time.now
|
|
|
|
@@last_pred ||= now
|
|
|
|
diff = now - @@last_pred
|
|
|
|
# @Swarfey does js setInterval(20) in his lib
|
|
|
|
# not sure if it makes sense to do a diff > 0.2 here then xd
|
|
|
|
@game_client.pred_game_tick += 1 if diff > 0.2
|
|
|
|
end
|
|
|
|
|
|
|
|
@ticks += 1
|
|
|
|
send_input if (@ticks % 20).zero?
|
2022-10-25 17:42:20 +00:00
|
|
|
end
|
2022-11-05 09:35:40 +00:00
|
|
|
|
|
|
|
def connection_loop
|
2022-11-05 16:19:05 +00:00
|
|
|
until @signal_disconnect
|
|
|
|
tick
|
|
|
|
# TODO: proper tick speed sleep
|
|
|
|
sleep 0.001
|
|
|
|
end
|
|
|
|
@thread_running = false
|
|
|
|
@signal_disconnect = false
|
2022-11-05 09:35:40 +00:00
|
|
|
end
|
2022-10-31 07:45:43 +00:00
|
|
|
end
|