From fff2e9efb0d0c6d14e91e8e0ec2b2cf28bd1835f Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Sun, 6 Nov 2022 20:08:32 +0100 Subject: [PATCH] First draft of sending inputs Thanks a lot to @Swarfey for his tw-chatonly typescript package https://www.npmjs.com/package/teeworlds His simple calculation of the pred time was saving me a lot of time! --- lib/chunk.rb | 15 ++++++++++ lib/game_client.rb | 41 +++++++++++++++++++++++++- lib/teeworlds-client.rb | 64 ++++++++++++++++++++++++++++++----------- 3 files changed, 103 insertions(+), 17 deletions(-) diff --git a/lib/chunk.rb b/lib/chunk.rb index 750dcfd..a8f7917 100644 --- a/lib/chunk.rb +++ b/lib/chunk.rb @@ -34,6 +34,21 @@ class NetChunk " data: #{str_hex(data)}" end + def self.create_non_vital_header(data = { size: 0 }) + flag_bits = '00' + unused_bits = '00' + + size_bits = data[:size].to_s(2).rjust(12, '0') + header_bits = + flag_bits + + size_bits[0..5] + + unused_bits + + size_bits[6..] + header_bits.chars.groups_of(8).map do |eigth_bits| + eigth_bits.join.to_i(2) + end + end + ## # Create int array ready to be send over the network # diff --git a/lib/game_client.rb b/lib/game_client.rb index 12193ae..ce1cc3b 100644 --- a/lib/game_client.rb +++ b/lib/game_client.rb @@ -33,11 +33,13 @@ class Context end class GameClient - attr_accessor :players + attr_accessor :players, :pred_game_tick, :ack_game_tick def initialize(client) @client = client @players = {} + @ack_game_tick = -1 + @pred_game_tick = 0 end def on_client_info(chunk) @@ -115,6 +117,43 @@ class GameClient @client.hooks[:rcon_line]&.call(context) end + def on_snapshot(chunk) + u = Unpacker.new(chunk.data) + u.get_int + # msg = u.get_int + # msg >>= 1 + + # num_parts = 1 + # part = 0 + game_tick = u.get_int + # delta_tick = u.get_int + # part_size = 0 + # crc = 0 + # complete_size = 0 + # data = nil + + # TODO: state check + + # if msg == NETMSG_SNAP + # num_parts = u.get_int + # part = u.get_int + # end + + # unless msg == NETMSG_SNAPEMPTY + # crc = u.get_int + # part_size = u.get_int + # end + + # TODO: add get_raw(size) + # data = u.get_raw + + # ack every snapshot no matter how broken + @ack_game_tick = game_tick + return unless (@pred_game_tick - @ack_game_tick).abs > 10 + + @pred_game_tick = @ack_game_tick + 1 + end + def on_emoticon(chunk); end def on_map_change(chunk) diff --git a/lib/teeworlds-client.rb b/lib/teeworlds-client.rb index 1cd28a8..b0299ff 100755 --- a/lib/teeworlds-client.rb +++ b/lib/teeworlds-client.rb @@ -77,6 +77,10 @@ class TeeworldsClient @hooks[:rcon_line] = block end + def on_on_snapshot(&block) + @hooks[:snapshot] = block + end + def send_chat(str) @netbase.send_packet( NetChunk.create_vital_header({ vital: true }, 4 + str.length) + @@ -256,18 +260,37 @@ class TeeworldsClient end def send_input - header = [0x10, 0x0A, 0o1] + str_bytes(@token) - random_compressed_input = [ - 0x4D, 0xE9, 0x48, 0x13, 0xD0, 0x0B, 0x6B, 0xFC, 0xB7, 0x2B, 0x6E, 0x00, 0xBA - ] - # this wont work we need to ack the ticks - # and then compress it - # CMsgPacker Msg(NETMSG_INPUT, true); - # Msg.AddInt(m_AckGameTick); - # Msg.AddInt(m_PredTick); - # Msg.AddInt(Size); - msg = header + random_compressed_input - @s.send(msg.pack('C*'), 0, @ip, @port) + inp = { + direction: -1, + target_x: 10, + target_y: 10, + jump: rand(0..1), + fire: 0, + hook: 0, + player_flags: 0, + wanted_weapon: 0, + next_weapon: 0, + prev_weapon: 0 + } + + 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]) + msg = NetChunk.create_non_vital_header(size: data.size + 1) + + [pack_msg_id(NETMSG_INPUT, system: true)] + + data + @netbase.send_packet(msg, 1) end def on_msg_token(data) @@ -331,6 +354,8 @@ class TeeworldsClient # should we be in alert here? when NETMSG_RCON_LINE @game_client.on_rcon_line(chunk) + when NETMSG_SNAP, NETMSG_SNAPSINGLE, NETMSG_SNAPEMPTY + @game_client.on_snapshot(chunk) else puts "Unsupported system msg: #{chunk.msg}" exit(1) @@ -385,11 +410,18 @@ class TeeworldsClient process_server_packet(packet) end - @ticks += 1 send_ctrl_keepalive if (@ticks % 8).zero? - # if @ticks % 20 == 0 - # send_chat("hello world") - # end + 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? end def connection_loop