From c726756b5cdab4d186ed7d56a6e269d15ca20ba6 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Wed, 21 Feb 2024 14:18:01 +0800 Subject: [PATCH] Draft out snap builder --- lib/packer.rb | 1 + lib/snapshot/builder.rb | 61 ++++++++++++++++++++++++++++++++++++ lib/snapshot/snapshot.rb | 30 ++++++++++++++++++ lib/snapshot/unpacker.rb | 12 ++----- lib/teeworlds_server.rb | 28 +++++++++++++++-- spec/09_snap_builder_spec.rb | 39 +++++++++++++++++++++++ 6 files changed, 160 insertions(+), 11 deletions(-) create mode 100644 lib/snapshot/builder.rb create mode 100644 lib/snapshot/snapshot.rb create mode 100644 spec/09_snap_builder_spec.rb diff --git a/lib/packer.rb b/lib/packer.rb index 7abca09..458def9 100644 --- a/lib/packer.rb +++ b/lib/packer.rb @@ -8,6 +8,7 @@ SKIP_START_WHITESPACES = 4 class Packer # Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign + # @return [Array] def self.pack_int(num) # the first byte can fit 6 bits # because the first two bits are extended and sign diff --git a/lib/snapshot/builder.rb b/lib/snapshot/builder.rb new file mode 100644 index 0000000..d3a79ae --- /dev/null +++ b/lib/snapshot/builder.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require_relative 'snapshot' + +# should be merged with SnapItemBase +class SnapItem + # @param type [Integer] type of the item for example 5 is obj_flag + # @param id [Integer] id of said item for characters thats the ClientID + # @param fields [Array] array of uncompressed integers + # for example [0, 0, 1] for obj_flag + # would set + # m_X = 0 + # m_Y = 0 + # m_Team = 1 + def initialize(type, id, size, fields) + @type = type + @id = id + @size = size + @fields = fields + end + + # basically to_network + # tee int array that will be sent over + # the wire + def to_a + Packer.pack_int(@type) + + Packer.pack_int(@id) + + fields.map { |field| Packer.pack_int(field) } + end +end + +class SnapshotBuilder + def initialize + @data_size = 0 + @num_items = 0 + @items = [] + end + + ## + # insert new snap item into the snap + # + # https://chillerdragon.github.io/teeworlds-protocol/07/snap_items.html + # + # @param type [Integer] type of the item for example 5 is obj_flag + # @param id [Integer] id of said item for characters thats the ClientID + # @param fields [Array] array of uncompressed integers + # for example [0, 0, 1] for obj_flag + # would set + # m_X = 0 + # m_Y = 0 + # m_Team = 1 + def new_item(type, id, size, fields) + item = SnapItem.new(type, id, size, fields) + @items.push(item) + end + + # @return [Snapshot] + def finish + Snapshot.new + end +end diff --git a/lib/snapshot/snapshot.rb b/lib/snapshot/snapshot.rb new file mode 100644 index 0000000..7c6031f --- /dev/null +++ b/lib/snapshot/snapshot.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# shared by client and server +class Snapshot + attr_accessor :game_tick, :items + + def initialize(items) + # @type game_tick [Integer] + @game_tick = 0 + # @type items [Array] + @items = items + end + + # @return [Integer] cyclic redundancy check a checksum of all snap items + def crc + sum = 0 + @items.each do |item| + sum += item.to_a.sum + end + sum + end + + def to_a + data = [] + @items.each do |item| + data += item.to_a + end + data + end +end diff --git a/lib/snapshot/unpacker.rb b/lib/snapshot/unpacker.rb index b95e1d5..7dce1c5 100644 --- a/lib/snapshot/unpacker.rb +++ b/lib/snapshot/unpacker.rb @@ -19,15 +19,7 @@ require_relative 'events/damage' require_relative 'events/death' require_relative 'events/hammer_hit' require_relative '../packer' - -class Snapshot - attr_accessor :game_tick, :items - - def initialize(items) - @game_tick = 0 - @items = items - end -end +require_relative 'snapshot' class DDNetSnapItem attr_accessor :notes, :name @@ -76,6 +68,7 @@ end class SnapshotUnpacker def initialize(client) @client = client + # @type verbose [Boolean] @verbose = client.verbose_snap end @@ -181,6 +174,7 @@ class SnapshotUnpacker invalid = false item_type = u.get_int id_parsed = u.parsed.last + # @type snap_items [Array] snap_items = [] while item_type obj = nil diff --git a/lib/teeworlds_server.rb b/lib/teeworlds_server.rb index 2b8d6ba..0ec45ce 100644 --- a/lib/teeworlds_server.rb +++ b/lib/teeworlds_server.rb @@ -65,7 +65,7 @@ end class TeeworldsServer attr_accessor :clients - attr_reader :hooks, :shutdown_reason + attr_reader :hooks, :shutdown_reason, :current_game_tick def initialize(options = {}) @verbose = options[:verbose] || false @@ -448,7 +448,7 @@ class TeeworldsServer # m_GameStartTime + (time_freq()*Tick)/SERVER_TICK_SPEED; end - def do_snapshot + def do_snap_empty delta_tick = -1 # DeltaTick = m_aClients[i].m_LastAckedSnapshot; data = [] @@ -470,6 +470,30 @@ class TeeworldsServer end end + def do_snap_single + builder = SnapshotBuilder.new + snap = builder.finish + items = snap.to_a + + data = [] + # Game tick Int + data += Packer.pack_int(@current_game_tick) + # Delta tick Int + data += Packer.pack_int(@current_game_tick - delta_tick) + # Crc Int + data += Packer.pack_int(snap.crc) + # Part size Int The size of this part. Meaning the size in bytes of the next raw data field. + data += Packer.pack_int(items.size) + # Data + data += items + + p data + end + + def do_snapshot + do_snap_empty + end + def get_player_by_id(id) @clients[id]&.player end diff --git a/spec/09_snap_builder_spec.rb b/spec/09_snap_builder_spec.rb new file mode 100644 index 0000000..c50e74c --- /dev/null +++ b/spec/09_snap_builder_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# require_relative '../lib/snapshot/builder.rb' +# +# describe 'SnapshotBuilder', :snapshot do +# context 'finish' do +# it 'Should create correct snap' do +# builder = SnapshotBuilder.new +# snap = builder.finish +# expected_payload = [ +# 0x00, 0x01, 0x00, 0x0a, # removed_items=0 num_item_deltas=1 _zero=0 type=10 NetObj::Character +# 0x00, 0x29, 0x00, 0x0d, # id=0 tick=41 x=0 y=13 +# 0x00, 0xb3, 0x36, 0x00, # vel_x=0 vel_y=3507 angle=0 +# 0x00, 0x40, 0x00, 0x00, # direction=0 jumped=-1 hooked_player=0 hook_state=0 +# 0x00, 0x00, 0x00, 0x00, # hook_tick=0 hook_x=0 hook_y=0 hook_dx=0 +# 0x00, 0x00, 0x00, 0x00, # hook_dy=0 health=0 armor=0 ammo_count=0 +# 0x00, 0x00, 0x00, 0x00, # weapon=0 emote=0 attack_tick=0 triggered_events=0 +# ] +# expect(snap.to_a).to eq(expected_payload) +# end +# end +# end + +# >>> snap NETMSG_SNAPSINGLE (8) +# id=8 game_tick=1908 delta_tick=38 +# num_parts=1 part=0 crc=16846 part_size=28 +# +# header: +# 11 b4 1d 26 ...& int 17 >> 1 = 8 int 1908 int 38 +# 8e 87 02 1c .... int 16846 int 28 +# +# payload: +# 00 01 00 0a .... removed_items=0 num_item_deltas=1 _zero=0 type=10 NetObj::Character +# 00 29 00 0d .).. id=0 tick=41 x=0 y=13 +# 00 b3 36 00 ..6. vel_x=0 vel_y=3507 angle=0 +# 00 40 00 00 .@.. direction=0 jumped=-1 hooked_player=0 hook_state=0 +# 00 00 00 00 .... hook_tick=0 hook_x=0 hook_y=0 hook_dx=0 +# 00 00 00 00 .... hook_dy=0 health=0 armor=0 ammo_count=0 +# 00 00 00 00 .... weapon=0 emote=0 attack_tick=0 triggered_events=0