teeworlds_network 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/lib/array.rb +28 -0
  3. data/lib/bytes.rb +139 -0
  4. data/lib/chunk.rb +177 -0
  5. data/lib/config.rb +44 -0
  6. data/lib/context.rb +29 -0
  7. data/lib/game_client.rb +196 -0
  8. data/lib/game_server.rb +122 -0
  9. data/lib/messages/cl_emoticon.rb +63 -0
  10. data/lib/messages/cl_say.rb +52 -0
  11. data/lib/messages/client_info.rb +141 -0
  12. data/lib/messages/game_info.rb +25 -0
  13. data/lib/messages/input_timing.rb +48 -0
  14. data/lib/messages/maplist_entry_add.rb +44 -0
  15. data/lib/messages/maplist_entry_rem.rb +44 -0
  16. data/lib/messages/rcon_cmd_add.rb +52 -0
  17. data/lib/messages/rcon_cmd_rem.rb +44 -0
  18. data/lib/messages/rcon_line.rb +44 -0
  19. data/lib/messages/server_info.rb +64 -0
  20. data/lib/messages/server_settings.rb +23 -0
  21. data/lib/messages/start_info.rb +129 -0
  22. data/lib/messages/sv_client_drop.rb +57 -0
  23. data/lib/models/chat_message.rb +39 -0
  24. data/lib/models/map.rb +57 -0
  25. data/lib/models/net_addr.rb +18 -0
  26. data/lib/models/packet_flags.rb +42 -0
  27. data/lib/models/player.rb +56 -0
  28. data/lib/models/token.rb +20 -0
  29. data/lib/net_base.rb +106 -0
  30. data/lib/network.rb +148 -0
  31. data/lib/packer.rb +194 -0
  32. data/lib/packet.rb +73 -0
  33. data/lib/snapshot/events/damage.rb +24 -0
  34. data/lib/snapshot/events/death.rb +20 -0
  35. data/lib/snapshot/events/explosion.rb +16 -0
  36. data/lib/snapshot/events/hammer_hit.rb +16 -0
  37. data/lib/snapshot/events/sound_world.rb +20 -0
  38. data/lib/snapshot/events/spawn.rb +16 -0
  39. data/lib/snapshot/items/character.rb +43 -0
  40. data/lib/snapshot/items/client_info.rb +22 -0
  41. data/lib/snapshot/items/flag.rb +22 -0
  42. data/lib/snapshot/items/game_data.rb +22 -0
  43. data/lib/snapshot/items/game_data_flag.rb +24 -0
  44. data/lib/snapshot/items/game_data_team.rb +21 -0
  45. data/lib/snapshot/items/laser.rb +24 -0
  46. data/lib/snapshot/items/pickup.rb +22 -0
  47. data/lib/snapshot/items/player_info.rb +22 -0
  48. data/lib/snapshot/items/player_input.rb +32 -0
  49. data/lib/snapshot/items/projectile.rb +25 -0
  50. data/lib/snapshot/items/spectator_info.rb +23 -0
  51. data/lib/snapshot/items/tune_params.rb +90 -0
  52. data/lib/snapshot/snap_event_base.rb +14 -0
  53. data/lib/snapshot/snap_item_base.rb +86 -0
  54. data/lib/snapshot/unpacker.rb +301 -0
  55. data/lib/string.rb +81 -0
  56. data/lib/teeworlds_client.rb +506 -0
  57. data/lib/teeworlds_network.rb +4 -0
  58. data/lib/teeworlds_server.rb +363 -0
  59. data/lib/version.rb +3 -0
  60. metadata +132 -0
data/lib/network.rb ADDED
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ GAME_VERSION = '0.7.5'
4
+ GAME_NETVERSION_HASH_FORCED = '802f1be60a05665f'
5
+ GAME_NETVERSION = "0.7 #{GAME_NETVERSION_HASH_FORCED}".freeze
6
+ CLIENT_VERSION = 0x0705
7
+
8
+ NETMSG_NULL = 0
9
+ NETMSG_INFO = 1
10
+ NETMSG_MAP_CHANGE = 2 # sent when client should switch map
11
+ NETMSG_MAP_DATA = 3 # map transfer, contains a chunk of the map file
12
+ NETMSG_SERVERINFO = 4
13
+ NETMSG_CON_READY = 5 # connection is ready, client should send start info
14
+ NETMSG_SNAP = 6 # normal snapshot, multiple parts
15
+ NETMSG_SNAPEMPTY = 7 # empty snapshot
16
+ NETMSG_SNAPSINGLE = 8 # ?
17
+ NETMSG_SNAPSMALL = 9
18
+ NETMSG_INPUTTIMING = 10 # reports how off the input was
19
+ NETMSG_RCON_AUTH_ON = 11 # rcon authentication enabled
20
+ NETMSG_RCON_AUTH_OFF = 12 # rcon authentication disabled
21
+ NETMSG_RCON_LINE = 13 # line that should be printed to the remote console
22
+ NETMSG_RCON_CMD_ADD = 14
23
+ NETMSG_RCON_CMD_REM = 15
24
+
25
+ NETMSG_MAPLIST_ENTRY_ADD = 29 # TODO: 0.8: move up
26
+ NETMSG_MAPLIST_ENTRY_REM = 30
27
+
28
+ # sent by client
29
+ NETMSG_READY = 18
30
+ NETMSG_ENTERGAME = 19
31
+ NETMSG_INPUT = 20 # contains the inputdata from the client
32
+ NETMSG_RCON_CMD = 21
33
+ NETMSG_RCON_AUTH = 22
34
+ NETMSG_REQUEST_MAP_DATA = 23
35
+
36
+ NETMSG_INVALID = 0
37
+ NETMSGTYPE_SV_MOTD = 1
38
+ NETMSGTYPE_SV_BROADCAST = 2
39
+ NETMSGTYPE_SV_CHAT = 3
40
+ NETMSGTYPE_SV_TEAM = 4
41
+ NETMSGTYPE_SV_KILLMSG = 5
42
+ NETMSGTYPE_SV_TUNEPARAMS = 6
43
+ NETMSGTYPE_SV_EXTRAPROJECTILE = 7
44
+ NETMSGTYPE_SV_READYTOENTER = 8
45
+ NETMSGTYPE_SV_WEAPONPICKUP = 19
46
+ NETMSGTYPE_SV_EMOTICON = 10
47
+ NETMSGTYPE_SV_VOTECLEAROPTIONS = 11
48
+ NETMSGTYPE_SV_VOTEOPTIONLISTADD = 12
49
+ NETMSGTYPE_SV_VOTEOPTIONADD = 13
50
+ NETMSGTYPE_SV_VOTEOPTIONREMOVE = 14
51
+ NETMSGTYPE_SV_VOTESET = 15
52
+ NETMSGTYPE_SV_VOTESTATUS = 16
53
+ NETMSGTYPE_SV_SERVERSETTINGS = 17
54
+ NETMSGTYPE_SV_CLIENTINFO = 18
55
+ NETMSGTYPE_SV_GAMEINFO = 19
56
+ NETMSGTYPE_SV_CLIENTDROP = 20
57
+ NETMSGTYPE_SV_GAMEMSG = 21
58
+ NETMSGTYPE_DE_CLIENTENTER = 22
59
+ NETMSGTYPE_DE_CLIENTLEAVE = 23
60
+ NETMSGTYPE_CL_SAY = 24
61
+ NETMSGTYPE_CL_SETTEAM = 25
62
+ NETMSGTYPE_CL_SETSPECTATORMODE = 26
63
+ NETMSGTYPE_CL_STARTINFO = 27
64
+ NETMSGTYPE_CL_KILL = 28
65
+ NETMSGTYPE_CL_READYCHANGE = 29
66
+ NETMSGTYPE_CL_EMOTICON = 30
67
+ NETMSGTYPE_CL_VOTE = 31
68
+ NETMSGTYPE_CL_CALLVOTE = 32
69
+ NETMSGTYPE_SV_SKINCHANGE = 33
70
+ NETMSGTYPE_CL_SKINCHANGE = 34
71
+ NETMSGTYPE_SV_RACEFINISH = 35
72
+ NETMSGTYPE_SV_CHECKPOINT = 36
73
+ NETMSGTYPE_SV_COMMANDINFO = 37
74
+ NETMSGTYPE_SV_COMMANDINFOREMOVE = 38
75
+ NETMSGTYPE_CL_COMMAND = 39
76
+ NUM_NETMSGTYPES = 40
77
+
78
+ NET_CTRLMSG_KEEPALIVE = 0
79
+ NET_CTRLMSG_CONNECT = 1
80
+ NET_CTRLMSG_ACCEPT = 2
81
+ NET_CTRLMSG_CLOSE = 4
82
+ NET_CTRLMSG_TOKEN = 5
83
+
84
+ NET_MAX_SEQUENCE = 1 << 10
85
+
86
+ NET_CONNSTATE_OFFLINE = 0
87
+ NET_CONNSTATE_TOKEN = 1
88
+ NET_CONNSTATE_CONNECT = 2
89
+ NET_CONNSTATE_PENDING = 3
90
+ NET_CONNSTATE_ONLINE = 4
91
+ NET_CONNSTATE_ERROR = 5
92
+
93
+ NET_MAX_CHUNKHEADERSIZE = 3
94
+
95
+ NET_PACKETHEADERSIZE = 7
96
+ NET_PACKETHEADERSIZE_CONNLESS = NET_PACKETHEADERSIZE + 2
97
+ NET_MAX_PACKETHEADERSIZE = NET_PACKETHEADERSIZE_CONNLESS
98
+
99
+ NET_MAX_PACKETSIZE = 1400
100
+ NET_MAX_PAYLOAD = NET_MAX_PACKETSIZE - NET_MAX_PACKETHEADERSIZE
101
+
102
+ CHAT_NONE = 0
103
+ CHAT_ALL = 1
104
+ CHAT_TEAM = 2
105
+ CHAT_WHISPER = 3
106
+ NUM_CHATS = 4
107
+
108
+ TARGET_SERVER = -1
109
+
110
+ PACKET_HEADER_SIZE = 7
111
+ VITAL_CHUNK_HEADER_SIZE = 3
112
+ NON_VITAL_CHUNK_HEADER_SIZE = 2
113
+
114
+ MAX_CLIENTS = 64
115
+ MAX_PLAYERS = 16
116
+
117
+ MAP_CHUNK_SIZE = NET_MAX_PAYLOAD - NET_MAX_CHUNKHEADERSIZE - 4 # msg type
118
+
119
+ SERVER_TICK_SPEED = 50
120
+
121
+ # should this be moved to some snap related file?
122
+
123
+ NETOBJ_INVALID = 0
124
+ NETOBJTYPE_PLAYERINPUT = 1
125
+ NETOBJTYPE_PROJECTILE = 2
126
+ NETOBJTYPE_LASER = 3
127
+ NETOBJTYPE_PICKUP = 4
128
+ NETOBJTYPE_FLAG = 5
129
+ NETOBJTYPE_GAMEDATA = 6
130
+ NETOBJTYPE_GAMEDATATEAM = 7
131
+ NETOBJTYPE_GAMEDATAFLAG = 8
132
+ NETOBJTYPE_CHARACTERCORE = 9
133
+ NETOBJTYPE_CHARACTER = 10
134
+ NETOBJTYPE_PLAYERINFO = 11
135
+ NETOBJTYPE_SPECTATORINFO = 12
136
+ NETOBJTYPE_DE_CLIENTINFO = 13
137
+ NETOBJTYPE_DE_GAMEINFO = 14
138
+ NETOBJTYPE_DE_TUNEPARAMS = 15
139
+ NETEVENTTYPE_COMMON = 16
140
+ NETEVENTTYPE_EXPLOSION = 17
141
+ NETEVENTTYPE_SPAWN = 18
142
+ NETEVENTTYPE_HAMMERHIT = 19
143
+ NETEVENTTYPE_DEATH = 20
144
+ NETEVENTTYPE_SOUNDWORLD = 21
145
+ NETEVENTTYPE_DAMAGE = 22
146
+ NETOBJTYPE_PLAYERINFORACE = 23
147
+ NETOBJTYPE_GAMEDATARACE = 24
148
+ NUM_NETOBJTYPE = 25
data/lib/packer.rb ADDED
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'array'
4
+
5
+ SANITIZE = 1
6
+ SANITIZE_CC = 2
7
+ SKIP_START_WHITESPACES = 4
8
+
9
+ class Packer
10
+ # Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign
11
+ def self.pack_int(num)
12
+ # the first byte can fit 6 bits
13
+ # because the first two bits are extended and sign
14
+ # so if you have a number bigger than 63
15
+ # it needs two bytes
16
+ # which are also least significant byte first etc
17
+ # so we do not support that YET
18
+ #
19
+ # the first too big number 64 is represented as those bits
20
+ # 10000000 00000001
21
+ # ^^^ ^ ^^ ^
22
+ # ||\ / | \ /
23
+ # || \ / not \ /
24
+ # || \ extended
25
+ # || \ /
26
+ # || \ /
27
+ # || \ /
28
+ # || \/
29
+ # || /\
30
+ # || / \
31
+ # || / \
32
+ # || 0000001 000000
33
+ # || |
34
+ # || v
35
+ # || 64
36
+ # ||
37
+ # |positive
38
+ # extended
39
+ sign = '0'
40
+ if num.negative?
41
+ sign = '1'
42
+ num += 1
43
+ end
44
+ num = num.abs
45
+ return pack_big_int(sign, num) if num > 63 || num < -63
46
+
47
+ ext = '0'
48
+ bits = ext + sign + num.to_s(2).rjust(6, '0')
49
+ [bits.to_i(2)]
50
+ end
51
+
52
+ def self.pack_big_int(sign, num)
53
+ num_bits = num.to_s(2)
54
+ first = "1#{sign}#{num_bits[-6..]}"
55
+
56
+ num_bits = num_bits[0..-7]
57
+ bytes = []
58
+ num_bits.chars.groups_of(7).each do |seven_bits|
59
+ # mark all as extended
60
+ bytes << "1#{seven_bits.join.rjust(7, '0')}"
61
+ end
62
+ # least significant first
63
+ bytes = bytes.reverse
64
+ # mark last byte as unextended
65
+ bytes[-1][0] = '0'
66
+ ([first] + bytes).map { |b| b.to_i(2) }
67
+ end
68
+
69
+ def self.pack_str(str)
70
+ str.chars.map(&:ord) + [0x00]
71
+ end
72
+ end
73
+
74
+ class Unpacker
75
+ attr_reader :prev, :data, :parsed
76
+
77
+ def initialize(data)
78
+ @data = data
79
+ @prev = [] # all the data already unpacked
80
+ @parsed = []
81
+ # TODO: delete parsed or document it with examples
82
+ # @parsed.push({ type: 'string', value: str, raw:, len:, pos: })
83
+ @red_bytes = 0
84
+ # { type: 'int', value: 1, raw: "\x01", len: 1, pos: 0 }
85
+ if data.instance_of?(String)
86
+ @data = data.unpack('C*')
87
+ elsif data.instance_of?(Array)
88
+ @data = data
89
+ else
90
+ raise 'Error: Unpacker expects array of integers or byte string'
91
+ end
92
+ end
93
+
94
+ def str_sanitize(str)
95
+ letters = str.chars
96
+ letters.map! do |c|
97
+ c.ord < 32 && c != "\r" && c != "\n" && c != "\t" ? ' ' : c
98
+ end
99
+ letters.join
100
+ end
101
+
102
+ def str_sanitize_cc(str)
103
+ letters = str.chars
104
+ letters.map! do |c|
105
+ c.ord < 32 ? ' ' : c
106
+ end
107
+ letters.join
108
+ end
109
+
110
+ def get_string(sanitize = SANITIZE)
111
+ return nil if @data.nil?
112
+
113
+ str = ''
114
+ raw = []
115
+ len = 0
116
+ pos = @red_bytes
117
+ @data.each_with_index do |byte, index|
118
+ @prev.push(byte)
119
+ raw.push(raw)
120
+ @red_bytes += 1
121
+ len += 1
122
+ if byte.zero?
123
+ @data = index == @data.length - 1 ? nil : @data[(index + 1)..]
124
+ str = str_sanitize(str) unless (sanitize & SANITIZE).zero?
125
+ str = str_sanitize_cc(str) unless (sanitize & SANITIZE_CC).zero?
126
+ @parsed.push({ type: 'string', value: str, raw:, len:, pos: })
127
+ return str
128
+ end
129
+ str += byte.chr
130
+ end
131
+ # raise "get_string() failed to find null terminator"
132
+ # return empty string in case of error
133
+ ''
134
+ end
135
+
136
+ def get_int
137
+ return nil if @data.nil?
138
+ return nil if @data.empty?
139
+
140
+ # TODO: make this more performant
141
+ # it should not read in ALL bytes
142
+ # of the WHOLE packed data
143
+ # it should be max 4 bytes
144
+ # because bigger ints are not sent anyways
145
+ bytes = @data.map { |byte| byte.to_s(2).rjust(8, '0') }
146
+ first = bytes[0]
147
+
148
+ sign = first[1]
149
+ bits = []
150
+ parsed = { type: 'int' }
151
+
152
+ # extended
153
+ if first[0] == '1'
154
+ bits << first[2..]
155
+ bytes = bytes[1..]
156
+ consumed = 1
157
+ bytes.each do |eigth_bits|
158
+ bits << eigth_bits[1..]
159
+ consumed += 1
160
+
161
+ break if eigth_bits[0] == '0'
162
+ end
163
+ bits = bits.reverse
164
+ @prev += @data[0...consumed]
165
+ parsed[:raw] = @data[0...consumed]
166
+ parsed[:len] = consumed
167
+ parsed[:pos] = @red_bytes
168
+ @red_bytes += consumed
169
+ @data = @data[consumed..]
170
+ else # single byte
171
+ bits = [first[2..]]
172
+ @prev.push(@data[0])
173
+ parsed[:raw] = [@data[0]]
174
+ parsed[:len] = 1
175
+ parsed[:pos] = @red_bytes
176
+ @red_bytes += 1
177
+ @data = @data[1..]
178
+ end
179
+ num = bits.join.to_i(2)
180
+ parsed[:value] = sign == '1' ? -(num + 1) : num
181
+ @parsed.push(parsed)
182
+ parsed[:value]
183
+ end
184
+
185
+ def get_raw(size = -1)
186
+ len = size == -1 ? @data.size : size
187
+ @prev += @data[...len]
188
+ pos = @red_bytes
189
+ @red_bytes += len
190
+ @parsed.push({ type: 'raw', value: @data[...len], raw: @data[...len], len:, pos: })
191
+ # TODO: error if size exceeds @data.size
192
+ @data.shift(len)
193
+ end
194
+ end
data/lib/packet.rb ADDED
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'models/net_addr'
4
+ require_relative 'models/packet_flags'
5
+
6
+ require 'huffman_tw'
7
+
8
+ # Class holding the parsed packet data
9
+ class Packet
10
+ attr_reader :flags, :payload, :addr
11
+ attr_accessor :client_id, :client
12
+
13
+ def initialize(data, prefix = '')
14
+ # @data and @payload
15
+ # are strings representing the raw bytes
16
+ #
17
+ # @prefix is a String that will be displayed
18
+ # when printing the packet
19
+ # use '>' and '<' for example to indicate
20
+ # network direction (client/server)
21
+ @prefix = prefix
22
+ @addr = NetAddr.new(nil, nil)
23
+ @huffman = Huffman.new
24
+ @client_id = nil
25
+ @client = nil
26
+ @data = data
27
+ flags_byte = @data[0].unpack('B*')
28
+ @flags = PacketFlags.new(flags_byte.first[2..5]).hash
29
+ @payload = @data[PACKET_HEADER_SIZE..]
30
+ return unless flags_compressed
31
+
32
+ @payload = @huffman.decompress(@payload.unpack('C*'))
33
+ @payload = @payload.pack('C*')
34
+ end
35
+
36
+ def annotate_first_row(bytes)
37
+ header = bytes[0..2].join(' ').yellow
38
+ token = bytes[3..6].join(' ').green
39
+ payload = bytes[7..].join(' ')
40
+ puts @prefix + " data: #{[header, token, payload].join(' ')}"
41
+ print "#{@prefix} "
42
+ print 'header'.ljust(3 * 3, ' ').yellow
43
+ print 'token'.ljust(4 * 3, ' ').green
44
+ puts 'data'
45
+ end
46
+
47
+ def to_s
48
+ puts "#{@prefix}Packet"
49
+ puts @prefix + " flags: #{@flags}"
50
+ bytes = str_hex(@data).split
51
+ # TODO: check terminal size?
52
+ max_width = 14
53
+ rows = bytes.groups_of(max_width)
54
+ annotate_first_row(rows.first)
55
+ rows[1..].each do |row|
56
+ print "#{@prefix} "
57
+ puts row.join(' ')
58
+ end
59
+ puts ''
60
+ end
61
+
62
+ def flags_compressed
63
+ @flags[:compressed]
64
+ end
65
+
66
+ def flags_connless
67
+ @flags[:connection] == false
68
+ end
69
+
70
+ def flags_control
71
+ @flags[:control]
72
+ end
73
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_event_base'
4
+
5
+ class NetEvent
6
+ class Damage < SnapEventBase
7
+ attr_accessor :client_id, :angle, :health_ammount, :armor_amount, :self
8
+
9
+ def initialize(hash_or_raw)
10
+ @field_names = %i[
11
+ client_id
12
+ angle
13
+ health_ammount
14
+ armor_amount
15
+ self
16
+ ]
17
+ super
18
+ end
19
+
20
+ def self.match_type?(type)
21
+ type == NETEVENTTYPE_DAMAGE
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_event_base'
4
+
5
+ class NetEvent
6
+ class Death < SnapEventBase
7
+ attr_accessor :client_id
8
+
9
+ def initialize(hash_or_raw)
10
+ @field_names = %i[
11
+ client_id
12
+ ]
13
+ super
14
+ end
15
+
16
+ def self.match_type?(type)
17
+ type == NETEVENTTYPE_DEATH
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_item_base'
4
+
5
+ class NetEvent
6
+ class Explosion < SnapEventBase
7
+ def initialize(hash_or_raw)
8
+ @field_names = []
9
+ super
10
+ end
11
+
12
+ def self.match_type?(type)
13
+ type == NETEVENTTYPE_EXPLOSION
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_item_base'
4
+
5
+ class NetEvent
6
+ class HammerHit < SnapEventBase
7
+ def initialize(hash_or_raw)
8
+ @field_names = []
9
+ super
10
+ end
11
+
12
+ def self.match_type?(type)
13
+ type == NETEVENTTYPE_HAMMERHIT
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_event_base'
4
+
5
+ class NetEvent
6
+ class SoundWorld < SnapEventBase
7
+ attr_accessor :sound_id
8
+
9
+ def initialize(hash_or_raw)
10
+ @field_names = %i[
11
+ sound_id
12
+ ]
13
+ super
14
+ end
15
+
16
+ def self.match_type?(type)
17
+ type == NETEVENTTYPE_SOUNDWORLD
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_item_base'
4
+
5
+ class NetEvent
6
+ class Spawn < SnapEventBase
7
+ def initialize(hash_or_raw)
8
+ @field_names = []
9
+ super
10
+ end
11
+
12
+ def self.match_type?(type)
13
+ type == NETEVENTTYPE_SPAWN
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_item_base'
4
+
5
+ class NetObj
6
+ class Character < SnapItemBase
7
+ attr_accessor :tick, :x, :y, :vel_x, :vel_y, :angle, :direction, :jumped, :hooked_player, :hook_state, :hook_tick,
8
+ :hook_x, :hook_y, :hook_dx, :hook_dy,
9
+ :health, :armor, :ammo_count, :weapon, :emote, :attack_tick, :triggered_events
10
+
11
+ def initialize(hash_or_raw)
12
+ @field_names = %i[
13
+ tick
14
+ x
15
+ y
16
+ vel_x
17
+ vel_y
18
+ angle
19
+ direction
20
+ jumped
21
+ hooked_player
22
+ hook_state
23
+ hook_tick
24
+ hook_x
25
+ hook_y
26
+ hook_dx
27
+ hook_dy
28
+ health
29
+ armor
30
+ ammo_count
31
+ weapon
32
+ emote
33
+ attack_tick
34
+ triggered_events
35
+ ]
36
+ super
37
+ end
38
+
39
+ def self.match_type?(type)
40
+ type == NETOBJTYPE_CHARACTER
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_item_base'
4
+
5
+ class NetObj
6
+ class ClientInfo < SnapItemBase
7
+ attr_accessor :local, :team
8
+
9
+ def initialize(hash_or_raw)
10
+ @field_names = %i[
11
+ local
12
+ team
13
+ ]
14
+ super
15
+ raise 'ClientInfo includes strings that is not supported yet'
16
+ end
17
+
18
+ def self.match_type?(type)
19
+ type == NETOBJTYPE_DE_CLIENTINFO
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_item_base'
4
+
5
+ class NetObj
6
+ class Flag < SnapItemBase
7
+ attr_accessor :x, :y, :team
8
+
9
+ def initialize(hash_or_raw)
10
+ @field_names = %i[
11
+ x
12
+ y
13
+ team
14
+ ]
15
+ super
16
+ end
17
+
18
+ def self.match_type?(type)
19
+ type == NETOBJTYPE_FLAG
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_item_base'
4
+
5
+ class NetObj
6
+ class GameData < SnapItemBase
7
+ attr_accessor :game_start_tick, :game_state_flags, :game_state_end_tick
8
+
9
+ def initialize(hash_or_raw)
10
+ @field_names = %i[
11
+ game_start_tick
12
+ game_state_flags
13
+ game_state_end_tick
14
+ ]
15
+ super
16
+ end
17
+
18
+ def self.match_type?(type)
19
+ type == NETOBJTYPE_GAMEDATA
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_item_base'
4
+
5
+ class NetObj
6
+ class GameDataFlag < SnapItemBase
7
+ attr_accessor :flag_carrier_red, :flag_carrier_blue,
8
+ :flag_drop_tick_red, :flag_drop_tick_blue
9
+
10
+ def initialize(hash_or_raw)
11
+ @field_names = %i[
12
+ flag_carrier_red
13
+ flag_carrier_blue
14
+ flag_drop_tick_red
15
+ flag_drop_tick_blue
16
+ ]
17
+ super
18
+ end
19
+
20
+ def self.match_type?(type)
21
+ type == NETOBJTYPE_GAMEDATAFLAG
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../snap_item_base'
4
+
5
+ class NetObj
6
+ class GameDataTeam < SnapItemBase
7
+ attr_accessor :teamscore_red, :teamscore_blue
8
+
9
+ def initialize(hash_or_raw)
10
+ @field_names = %i[
11
+ teamscore_red
12
+ teamscore_blue
13
+ ]
14
+ super
15
+ end
16
+
17
+ def self.match_type?(type)
18
+ type == NETOBJTYPE_GAMEDATATEAM
19
+ end
20
+ end
21
+ end