171 lines
4.6 KiB
Ruby
171 lines
4.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative 'context'
|
|
require_relative 'models/map'
|
|
require_relative 'models/chat_message'
|
|
require_relative 'messages/game_info'
|
|
require_relative 'messages/server_info'
|
|
require_relative 'messages/server_settings'
|
|
require_relative 'messages/start_info'
|
|
require_relative 'messages/cl_say'
|
|
require_relative 'messages/cl_emoticon'
|
|
|
|
class GameServer
|
|
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: '98a0a4c50c', # decimal 64548818
|
|
size: 6793,
|
|
sha256: '491af17a510214506270904f147a4c30ae0a85b91bb854395bef8c397fc078c3'
|
|
)
|
|
end
|
|
|
|
##
|
|
# call_hook
|
|
#
|
|
# @param: hook_sym [Symbol] name of the symbol to call
|
|
# @param: context [Context] context object to pass on data
|
|
# @param: optional [Any] optional 2nd parameter passed to the callback
|
|
def call_hook(hook_sym, context, optional = nil)
|
|
@server.hooks[hook_sym].each do |hook|
|
|
hook.call(context, optional)
|
|
context.verify
|
|
return nil if context.canceld?
|
|
end
|
|
context
|
|
end
|
|
|
|
def on_emoticon(chunk, _packet)
|
|
message = ClEmoticon.new(chunk.data[1..])
|
|
p message
|
|
end
|
|
|
|
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: check version and password
|
|
|
|
@server.send_map(packet.client)
|
|
end
|
|
|
|
def on_ready(_chunk, packet)
|
|
# vanilla server sends 3 chunks here usually
|
|
# - motd
|
|
# - server settings
|
|
# - ready
|
|
#
|
|
@server.send_server_settings(packet.client, ServerSettings.new.to_a)
|
|
@server.send_ready(packet.client)
|
|
end
|
|
|
|
def on_start_info(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
|
|
info = StartInfo.new(chunk.data[1..])
|
|
packet.client.player.set_start_info(info)
|
|
info_str = info.to_s
|
|
puts "got start info: #{info_str}" if @verbose
|
|
@server.send_ready_to_enter(packet.client)
|
|
end
|
|
|
|
def on_say(chunk, packet)
|
|
say = ClSay.new(chunk.data[1..])
|
|
author = packet.client.player
|
|
msg = ChatMesage.new(say.to_h.merge(client_id: author.id, author:))
|
|
context = Context.new(say, chunk:)
|
|
return if call_hook(:chat, context, msg).nil?
|
|
|
|
puts msg
|
|
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
|
|
packet.client.in_game = true
|
|
@server.send_server_info(packet.client, ServerInfo.new.to_a)
|
|
@server.send_game_info(packet.client, GameInfo.new.to_a)
|
|
|
|
puts "'#{packet.client.player.name}' joined the game"
|
|
end
|
|
|
|
def on_rcon_cmd(chunk, packet)
|
|
u = Unpacker.new(chunk.data[1..])
|
|
command = u.get_string
|
|
return if call_hook(:rcon_cmd, Context.new(nil, chunk:, packet:, command:)).nil?
|
|
return unless packet.client.authed?
|
|
|
|
puts "[server] ClientID=#{packet.client.player.id} rcon='#{command}'"
|
|
if command == 'shutdown'
|
|
@server.shutdown!('Server shutdown')
|
|
else
|
|
puts "[console] No such command: #{command}:"
|
|
end
|
|
end
|
|
|
|
def on_rcon_auth(chunk, packet)
|
|
u = Unpacker.new(chunk.data[1..])
|
|
password = u.get_string
|
|
return if call_hook(:rcon_auth, Context.new(nil, chunk:, packet:, password:)).nil?
|
|
|
|
# TODO: we accept any password lol
|
|
puts "[server] ClientID=#{packet.client.player.id} addr=#{packet.client.addr} authed (admin)"
|
|
packet.client.authed = true
|
|
@server.send_rcon_auth_on(packet.client)
|
|
end
|
|
|
|
def on_input(chunk, packet)
|
|
# vanilla server responds to input with 2 chunks
|
|
# - input_timing
|
|
# - snap (empty)
|
|
|
|
# we do nothing for now
|
|
# TODO: do something
|
|
end
|
|
|
|
def on_client_drop(client, reason = nil)
|
|
reason = reason.nil? ? '' : " (#{reason})"
|
|
puts "'#{client.player.name}' left the game#{reason}"
|
|
end
|
|
|
|
def on_shutdown
|
|
return if call_hook(:shutdown, Context.new(nil)).nil?
|
|
|
|
puts '[gameserver] disconnecting all clients ...'
|
|
@server.clients.each_value do |client|
|
|
@server.send_ctrl_close(client, @server.shutdown_reason)
|
|
end
|
|
puts '[gameserver] shutting down ...'
|
|
end
|
|
|
|
def on_tick
|
|
now = Time.now
|
|
timeout_ids = []
|
|
@server.clients.each do |id, client|
|
|
diff = now - client.last_recv_time
|
|
timeout_ids.push(id) if diff > 10
|
|
end
|
|
|
|
timeout_ids.each do |id|
|
|
@server.drop_client(@server.clients[id], 'Timeout')
|
|
end
|
|
end
|
|
end
|