diff --git a/lib/game_server.rb b/lib/game_server.rb index 571e8bf..631eb36 100644 --- a/lib/game_server.rb +++ b/lib/game_server.rb @@ -9,6 +9,7 @@ require_relative 'messages/start_info' require_relative 'messages/cl_say' require_relative 'messages/cl_emoticon' +require_relative 'messages/cl_info' class GameServer attr_accessor :pred_game_tick, :ack_game_tick, :map @@ -41,19 +42,17 @@ def call_hook(hook_sym, context, optional = nil) end def on_emoticon(chunk, _packet) - message = ClEmoticon.new(chunk.data[1..]) - p message + msg = ClEmoticon.new(chunk.data[1..]) + return if call_hook(:emote, Context.new(msg, chunk:, packet:)).nil? 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}" + msg = ClInfo.new(chunk.data[1..]) - # TODO: check version and password + return if call_hook(:info, Context.new(msg, chunk:, packet:)).nil? + # TODO: check version and password + puts "vers=#{msg.net_version} vers=#{msg.client_version} pass=#{msg.password}" @server.send_map(packet.client) end @@ -63,6 +62,8 @@ def on_ready(_chunk, packet) # - server settings # - ready # + return if call_hook(:ready, Context.new(nil, chunk: nil, packet:)).nil? + @server.send_server_settings(packet.client, ServerSettings.new.to_a) @server.send_ready(packet.client) end @@ -75,6 +76,8 @@ def on_start_info(chunk, packet) # # We only send ready to enter for now info = StartInfo.new(chunk.data[1..]) + return if call_hook(:start_info, Context.new(info, chunk: nil, packet:)).nil? + packet.client.player.set_start_info(info) info_str = info.to_s puts "got start info: #{info_str}" if @verbose @@ -99,6 +102,8 @@ def on_enter_game(_chunk, packet) # - game info # - client info # - snap single + return if call_hook(:enter_game, Context.new(nil, chunk: nil, packet:)).nil? + 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) @@ -136,12 +141,17 @@ def on_input(chunk, packet) # - input_timing # - snap (empty) - # we do nothing for now - # TODO: do something + msg = ClInput.new(chunk.data[1..]) + return if call_hook(:input, Context.new(msg, chunk:, packet:)).nil? + + dir = msg.direction + puts "#{packet.client.player.id} tried to move #{dir}" unless dir.zero? end def on_client_drop(client, reason = nil) reason = reason.nil? ? '' : " (#{reason})" + return if call_hook(:client_drop, Context.new(nil, chunk:, packet:, reason:)).nil? + puts "'#{client.player.name}' left the game#{reason}" end @@ -156,6 +166,8 @@ def on_shutdown end def on_tick + return if call_hook(:tick, Context.new(nil, chunk:, packet:)).nil? + now = Time.now timeout_ids = [] @server.clients.each do |id, client| diff --git a/lib/messages/cl_info.rb b/lib/messages/cl_info.rb new file mode 100644 index 0000000..6278416 --- /dev/null +++ b/lib/messages/cl_info.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require_relative '../packer' + +## +# ClInfo +# +# Client -> Server +class ClInfo + attr_accessor :net_version, :password, :client_version + + def initialize(hash_or_raw) + if hash_or_raw.instance_of?(Hash) + init_hash(hash_or_raw) + else + init_raw(hash_or_raw) + end + end + + def init_raw(data) + u = Unpacker.new(data) + @net_version = u.get_string + @password = u.get_string + @client_version = u.get_int + end + + def init_hash(attr) + @net_version = attr[:net_version] || 'TODO: fill default' + @password = attr[:password] || 'TODO: fill default' + @client_version = attr[:client_version] || 0 + end + + def to_h + { + net_version: @net_version, + password: @password, + client_version: @client_version + } + end + + # basically to_network + # int array the Client sends to the Server + def to_a + Packer.pack_str(@net_version) + + Packer.pack_str(@password) + + Packer.pack_int(@client_version) + end + + def to_s + to_h + end +end diff --git a/lib/messages/cl_input.rb b/lib/messages/cl_input.rb new file mode 100644 index 0000000..6ecc7fc --- /dev/null +++ b/lib/messages/cl_input.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require_relative '../packer' + +## +# ClInput +# +# Client -> Server +class ClInput + attr_accessor :ack_game_tick, :prediction_tick, :size, :direction, :target_x, :target_y, :jump, :fire, :hook, :player_flags, :wanted_weapon, :next_weapon, :prev_weapon, :ping + + def initialize(hash_or_raw) + if hash_or_raw.instance_of?(Hash) + init_hash(hash_or_raw) + else + init_raw(hash_or_raw) + end + end + + def init_raw(data) + u = Unpacker.new(data) + @ack_game_tick = u.get_int + @prediction_tick = u.get_int + @size = u.get_int + @direction = u.get_int + @target_x = u.get_int + @target_y = u.get_int + @jump = u.get_int + @fire = u.get_int + @hook = u.get_int + @player_flags = u.get_int + @wanted_weapon = u.get_int + @next_weapon = u.get_int + @prev_weapon = u.get_int + @ping = u.get_int + end + + def init_hash(attr) + @ack_game_tick = attr[:ack_game_tick] || 0 + @prediction_tick = attr[:prediction_tick] || 0 + @size = attr[:size] || 0 + @direction = attr[:direction] || 0 + @target_x = attr[:target_x] || 0 + @target_y = attr[:target_y] || 0 + @jump = attr[:jump] || 0 + @fire = attr[:fire] || 0 + @hook = attr[:hook] || 0 + @player_flags = attr[:player_flags] || 0 + @wanted_weapon = attr[:wanted_weapon] || 0 + @next_weapon = attr[:next_weapon] || 0 + @prev_weapon = attr[:prev_weapon] || 0 + @ping = attr[:ping] || 0 + end + + def to_h + { + ack_game_tick: @ack_game_tick, + prediction_tick: @prediction_tick, + size: @size, + direction: @direction, + target_x: @target_x, + target_y: @target_y, + jump: @jump, + fire: @fire, + hook: @hook, + player_flags: @player_flags, + wanted_weapon: @wanted_weapon, + next_weapon: @next_weapon, + prev_weapon: @prev_weapon, + ping: @ping + } + end + + # basically to_network + # int array the Client sends to the Server + def to_a + Packer.pack_int(@ack_game_tick) + + Packer.pack_int(@prediction_tick) + + Packer.pack_int(@size) + + Packer.pack_int(@direction) + + Packer.pack_int(@target_x) + + Packer.pack_int(@target_y) + + Packer.pack_int(@jump) + + Packer.pack_int(@fire) + + Packer.pack_int(@hook) + + Packer.pack_int(@player_flags) + + Packer.pack_int(@wanted_weapon) + + Packer.pack_int(@next_weapon) + + Packer.pack_int(@prev_weapon) + + Packer.pack_int(@ping) + end + + def to_s + to_h + end +end diff --git a/lib/teeworlds_server.rb b/lib/teeworlds_server.rb index 7b6d88d..e5da521 100644 --- a/lib/teeworlds_server.rb +++ b/lib/teeworlds_server.rb @@ -78,7 +78,15 @@ def initialize(options = {}) chat: [], rcon_auth: [], rcon_cmd: [], - shutdown: [] + shutdown: [], + emote: [], + info: [], + ready: [], + start_info: [], + enter_game: [], + input: [], + client_drop: [], + tick: [] } @thread_running = false @is_shutting_down = false @@ -106,6 +114,38 @@ def on_shutdown(&block) @hooks[:shutdown].push(block) end + def on_emote(&block) + @hooks[:emote].push(block) + end + + def on_info(&block) + @hooks[:info].push(block) + end + + def on_ready(&block) + @hooks[:ready].push(block) + end + + def on_start_info(&block) + @hooks[:start_info].push(block) + end + + def on_enter_game(&block) + @hooks[:enter_game].push(block) + end + + def on_input(&block) + @hooks[:input].push(block) + end + + def on_client_drop(&block) + @hooks[:client_drop].push(block) + end + + def on_tick(&block) + @hooks[:tick].push(block) + end + def main_loop loop do break if @is_shutting_down