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.
- checksums.yaml +7 -0
- data/lib/array.rb +28 -0
- data/lib/bytes.rb +139 -0
- data/lib/chunk.rb +177 -0
- data/lib/config.rb +44 -0
- data/lib/context.rb +29 -0
- data/lib/game_client.rb +196 -0
- data/lib/game_server.rb +122 -0
- data/lib/messages/cl_emoticon.rb +63 -0
- data/lib/messages/cl_say.rb +52 -0
- data/lib/messages/client_info.rb +141 -0
- data/lib/messages/game_info.rb +25 -0
- data/lib/messages/input_timing.rb +48 -0
- data/lib/messages/maplist_entry_add.rb +44 -0
- data/lib/messages/maplist_entry_rem.rb +44 -0
- data/lib/messages/rcon_cmd_add.rb +52 -0
- data/lib/messages/rcon_cmd_rem.rb +44 -0
- data/lib/messages/rcon_line.rb +44 -0
- data/lib/messages/server_info.rb +64 -0
- data/lib/messages/server_settings.rb +23 -0
- data/lib/messages/start_info.rb +129 -0
- data/lib/messages/sv_client_drop.rb +57 -0
- data/lib/models/chat_message.rb +39 -0
- data/lib/models/map.rb +57 -0
- data/lib/models/net_addr.rb +18 -0
- data/lib/models/packet_flags.rb +42 -0
- data/lib/models/player.rb +56 -0
- data/lib/models/token.rb +20 -0
- data/lib/net_base.rb +106 -0
- data/lib/network.rb +148 -0
- data/lib/packer.rb +194 -0
- data/lib/packet.rb +73 -0
- data/lib/snapshot/events/damage.rb +24 -0
- data/lib/snapshot/events/death.rb +20 -0
- data/lib/snapshot/events/explosion.rb +16 -0
- data/lib/snapshot/events/hammer_hit.rb +16 -0
- data/lib/snapshot/events/sound_world.rb +20 -0
- data/lib/snapshot/events/spawn.rb +16 -0
- data/lib/snapshot/items/character.rb +43 -0
- data/lib/snapshot/items/client_info.rb +22 -0
- data/lib/snapshot/items/flag.rb +22 -0
- data/lib/snapshot/items/game_data.rb +22 -0
- data/lib/snapshot/items/game_data_flag.rb +24 -0
- data/lib/snapshot/items/game_data_team.rb +21 -0
- data/lib/snapshot/items/laser.rb +24 -0
- data/lib/snapshot/items/pickup.rb +22 -0
- data/lib/snapshot/items/player_info.rb +22 -0
- data/lib/snapshot/items/player_input.rb +32 -0
- data/lib/snapshot/items/projectile.rb +25 -0
- data/lib/snapshot/items/spectator_info.rb +23 -0
- data/lib/snapshot/items/tune_params.rb +90 -0
- data/lib/snapshot/snap_event_base.rb +14 -0
- data/lib/snapshot/snap_item_base.rb +86 -0
- data/lib/snapshot/unpacker.rb +301 -0
- data/lib/string.rb +81 -0
- data/lib/teeworlds_client.rb +506 -0
- data/lib/teeworlds_network.rb +4 -0
- data/lib/teeworlds_server.rb +363 -0
- data/lib/version.rb +3 -0
- metadata +132 -0
@@ -0,0 +1,363 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
require_relative 'string'
|
6
|
+
require_relative 'array'
|
7
|
+
require_relative 'bytes'
|
8
|
+
require_relative 'network'
|
9
|
+
require_relative 'packet'
|
10
|
+
require_relative 'chunk'
|
11
|
+
require_relative 'net_base'
|
12
|
+
require_relative 'models/net_addr'
|
13
|
+
require_relative 'packer'
|
14
|
+
require_relative 'game_server'
|
15
|
+
require_relative 'models/token'
|
16
|
+
|
17
|
+
class Client
|
18
|
+
attr_accessor :id, :addr, :vital_sent, :last_recv_time, :token, :player, :in_game
|
19
|
+
attr_reader :ack
|
20
|
+
|
21
|
+
def initialize(attr = {})
|
22
|
+
@id = attr[:id]
|
23
|
+
@addr = attr[:addr]
|
24
|
+
@vital_sent = 0
|
25
|
+
@ack = 0
|
26
|
+
@in_game = false
|
27
|
+
@last_recv_time = Time.now
|
28
|
+
@player = Player.new(
|
29
|
+
id: @id,
|
30
|
+
local: 0,
|
31
|
+
team: 0,
|
32
|
+
name: '(connecting)',
|
33
|
+
clan: '',
|
34
|
+
country: -1
|
35
|
+
)
|
36
|
+
@token = attr[:token]
|
37
|
+
SecurityToken.validate(@token)
|
38
|
+
end
|
39
|
+
|
40
|
+
def in_game?
|
41
|
+
@in_game
|
42
|
+
end
|
43
|
+
|
44
|
+
def bump_ack
|
45
|
+
@ack = (@ack + 1) % NET_MAX_SEQUENCE
|
46
|
+
end
|
47
|
+
|
48
|
+
# TODO: use or remove
|
49
|
+
# not sure if its cool
|
50
|
+
# one can make vital_sent read only
|
51
|
+
# and then the seq reader increments it
|
52
|
+
# so everytime a header is created
|
53
|
+
# the chunk builder just calls
|
54
|
+
# seq = client.seq
|
55
|
+
def seq
|
56
|
+
@vital_sent + 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class TeeworldsServer
|
61
|
+
attr_accessor :clients
|
62
|
+
|
63
|
+
def initialize(options = {})
|
64
|
+
@verbose = options[:verbose] || false
|
65
|
+
@ip = '127.0.0.1'
|
66
|
+
@port = 8303
|
67
|
+
@game_server = GameServer.new(self)
|
68
|
+
@clients = {}
|
69
|
+
@current_game_tick = 0
|
70
|
+
@last_snap_time = Time.now
|
71
|
+
end
|
72
|
+
|
73
|
+
def run(ip, port)
|
74
|
+
@server_token = (1..4).to_a.map { |_| rand(0..255) }
|
75
|
+
@server_token = @server_token.map { |b| b.to_s(16).rjust(2, '0') }.join
|
76
|
+
puts "server token #{@server_token}"
|
77
|
+
@netbase = NetBase.new(verbose: @verbose)
|
78
|
+
NetChunk.reset
|
79
|
+
@ip = ip
|
80
|
+
@port = port
|
81
|
+
puts "listening on #{@ip}:#{@port} .."
|
82
|
+
@s = UDPSocket.new
|
83
|
+
@s.bind(@ip, @port)
|
84
|
+
@netbase.bind(@s)
|
85
|
+
loop do
|
86
|
+
tick
|
87
|
+
# TODO: proper tick speed sleep
|
88
|
+
# replace by blocking network read
|
89
|
+
# m_NetServer
|
90
|
+
# .Wait(
|
91
|
+
# clamp(
|
92
|
+
# int((
|
93
|
+
# TickStartTime(
|
94
|
+
# m_CurrentGameTick+1)-time_get()
|
95
|
+
# )*1000/time_freq()),
|
96
|
+
# 1,
|
97
|
+
# 1000/SERVER_TICK_SPEED/2));
|
98
|
+
sleep 0.001
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def on_message(chunk, packet)
|
103
|
+
puts "got game chunk: #{chunk}"
|
104
|
+
case chunk.msg
|
105
|
+
when NETMSGTYPE_CL_STARTINFO then @game_server.on_start_info(chunk, packet)
|
106
|
+
when NETMSGTYPE_CL_SAY then @game_server.on_say(chunk, packet)
|
107
|
+
when NETMSGTYPE_CL_EMOTICON then @game_server.on_emoticon(chunk, packet)
|
108
|
+
else
|
109
|
+
puts "Unsupported game msg: #{chunk.msg}"
|
110
|
+
exit(1)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def process_chunk(chunk, packet)
|
115
|
+
unless chunk.sys
|
116
|
+
on_message(chunk, packet)
|
117
|
+
return
|
118
|
+
end
|
119
|
+
puts "proccess chunk with msg: #{chunk.msg}" if @verbose
|
120
|
+
case chunk.msg
|
121
|
+
when NETMSG_INFO
|
122
|
+
@game_server.on_info(chunk, packet)
|
123
|
+
when NETMSG_READY
|
124
|
+
@game_server.on_ready(chunk, packet)
|
125
|
+
when NETMSG_ENTERGAME
|
126
|
+
@game_server.on_enter_game(chunk, packet)
|
127
|
+
when NETMSG_INPUT
|
128
|
+
@game_server.on_input(chunk, packet)
|
129
|
+
when NETMSG_RCON_CMD
|
130
|
+
@game_server.on_rcon_cmd(chunk, packet)
|
131
|
+
else
|
132
|
+
puts "Unsupported system msg: #{chunk.msg}"
|
133
|
+
exit(1)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def on_client_packet(packet)
|
138
|
+
client = packet.client
|
139
|
+
if client.nil?
|
140
|
+
# TODO: turn this into a silent return
|
141
|
+
# otherwise bad actors can easily trigger this
|
142
|
+
# with handcrafted packets
|
143
|
+
puts 'Error: got client packet from unknown client'
|
144
|
+
exit 1
|
145
|
+
end
|
146
|
+
chunks = BigChungusTheChunkGetter.get_chunks(packet.payload)
|
147
|
+
chunks.each do |chunk|
|
148
|
+
if chunk.flags_vital && !chunk.flags_resend
|
149
|
+
packet.client.bump_ack
|
150
|
+
puts "got ack: #{packet.client.ack}" if @verbose
|
151
|
+
end
|
152
|
+
process_chunk(chunk, packet)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def on_ctrl_message(packet)
|
157
|
+
u = Unpacker.new(packet.payload)
|
158
|
+
msg = u.get_int
|
159
|
+
puts "got ctrl msg: #{msg}"
|
160
|
+
case msg
|
161
|
+
when NET_CTRLMSG_TOKEN then on_ctrl_token(packet)
|
162
|
+
when NET_CTRLMSG_CONNECT then on_ctrl_connect(packet)
|
163
|
+
when NET_CTRLMSG_KEEPALIVE then on_ctrl_keep_alive(packet)
|
164
|
+
when NET_CTRLMSG_CLOSE then on_ctrl_close(packet)
|
165
|
+
else
|
166
|
+
puts "Uknown control message #{msg}"
|
167
|
+
exit(1)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def send_ctrl_close(client, reason)
|
172
|
+
# when clients disconnect
|
173
|
+
# during the connection process
|
174
|
+
# we do not care
|
175
|
+
return if client.nil?
|
176
|
+
|
177
|
+
msg = [NET_CTRLMSG_CLOSE]
|
178
|
+
msg += Packer.pack_str(reason) unless reason.nil?
|
179
|
+
@netbase.set_peer_token(client.token)
|
180
|
+
@netbase.send_packet(msg, chunks: 0, control: true, client:)
|
181
|
+
# @netbase.set_peer_token(@server_token)
|
182
|
+
end
|
183
|
+
|
184
|
+
def send_ctrl_with_token(addr, token)
|
185
|
+
msg = [NET_CTRLMSG_TOKEN] + str_bytes(@server_token)
|
186
|
+
@netbase.set_peer_token(token)
|
187
|
+
@netbase.send_packet(msg, chunks: 0, control: true, addr:)
|
188
|
+
# @netbase.set_peer_token(@server_token)
|
189
|
+
end
|
190
|
+
|
191
|
+
def send_map(client)
|
192
|
+
data = []
|
193
|
+
data += Packer.pack_str(@game_server.map.name)
|
194
|
+
data += @game_server.map.crc_arr # poor mans pack_raw()
|
195
|
+
data += Packer.pack_int(@game_server.map.size)
|
196
|
+
data += Packer.pack_int(8) # chunk num?
|
197
|
+
data += Packer.pack_int(MAP_CHUNK_SIZE)
|
198
|
+
data += @game_server.map.sha256_arr # poor mans pack_raw()
|
199
|
+
msg = NetChunk.create_header(vital: true, size: data.size + 1, client:) +
|
200
|
+
[pack_msg_id(NETMSG_MAP_CHANGE, system: true)] +
|
201
|
+
data
|
202
|
+
@netbase.send_packet(msg, chunks: 1, client:)
|
203
|
+
end
|
204
|
+
|
205
|
+
def send_ready(client)
|
206
|
+
msg = NetChunk.create_header(vital: true, size: 1, client:) +
|
207
|
+
[pack_msg_id(NETMSG_CON_READY, system: true)]
|
208
|
+
@netbase.send_packet(msg, chunks: 1, client:)
|
209
|
+
end
|
210
|
+
|
211
|
+
def send_ready_to_enter(client)
|
212
|
+
msg = NetChunk.create_header(vital: true, size: 1, client:) +
|
213
|
+
[pack_msg_id(NETMSGTYPE_SV_READYTOENTER, system: false)]
|
214
|
+
@netbase.send_packet(msg, chunks: 1, client:)
|
215
|
+
end
|
216
|
+
|
217
|
+
def send_server_settings(client, server_settings)
|
218
|
+
msg = NetChunk.create_header(vital: true, size: 1 + server_settings.size, client:) +
|
219
|
+
[pack_msg_id(NETMSGTYPE_SV_SERVERSETTINGS, system: false)] +
|
220
|
+
server_settings
|
221
|
+
@netbase.send_packet(msg, chunks: 1, client:)
|
222
|
+
end
|
223
|
+
|
224
|
+
def send_server_info(client, server_info)
|
225
|
+
msg = NetChunk.create_header(vital: true, size: 1 + server_info.size, client:) +
|
226
|
+
[pack_msg_id(NETMSG_SERVERINFO, system: true)] +
|
227
|
+
server_info
|
228
|
+
@netbase.send_packet(msg, chunks: 1, client:)
|
229
|
+
end
|
230
|
+
|
231
|
+
def send_game_info(client, data)
|
232
|
+
msg = NetChunk.create_header(vital: true, size: 1 + data.size, client:) +
|
233
|
+
[pack_msg_id(NETMSGTYPE_SV_GAMEINFO, system: false)] +
|
234
|
+
data
|
235
|
+
@netbase.send_packet(msg, chunks: 1, client:)
|
236
|
+
end
|
237
|
+
|
238
|
+
def on_ctrl_token(packet)
|
239
|
+
u = Unpacker.new(packet.payload[1..])
|
240
|
+
token = u.get_raw(4)
|
241
|
+
token_str = token.map { |b| b.to_s(16).rjust(2, '0') }.join
|
242
|
+
puts "got token #{token_str}"
|
243
|
+
send_ctrl_with_token(packet.addr, token_str)
|
244
|
+
end
|
245
|
+
|
246
|
+
def on_ctrl_keep_alive(packet)
|
247
|
+
puts "Got keep alive from #{packet.addr}" if @verbose
|
248
|
+
end
|
249
|
+
|
250
|
+
def on_ctrl_close(packet)
|
251
|
+
reason = nil
|
252
|
+
if packet.payload[2]
|
253
|
+
u = Unpacker.new(packet.payload[1..])
|
254
|
+
reason = u.get_string
|
255
|
+
end
|
256
|
+
drop_client(packet.client, reason)
|
257
|
+
end
|
258
|
+
|
259
|
+
def drop_client(client, reason = nil)
|
260
|
+
send_ctrl_close(client, reason)
|
261
|
+
return if client.nil?
|
262
|
+
|
263
|
+
@game_server.on_client_drop(client, reason)
|
264
|
+
@clients.delete(client.id)
|
265
|
+
end
|
266
|
+
|
267
|
+
def on_ctrl_connect(packet)
|
268
|
+
id = get_next_client_id
|
269
|
+
if id == -1
|
270
|
+
puts 'server full drop packet. TODO: tell the client'
|
271
|
+
return
|
272
|
+
end
|
273
|
+
token = bytes_to_str(packet.payload[1..4])
|
274
|
+
puts "got connection, sending accept (client token: #{token})"
|
275
|
+
client = Client.new(id:, addr: packet.addr, token:)
|
276
|
+
@clients[id] = client
|
277
|
+
@netbase.send_packet([NET_CTRLMSG_ACCEPT], chunks: 0, control: true, client:)
|
278
|
+
end
|
279
|
+
|
280
|
+
def on_packet(packet)
|
281
|
+
# process connless packets data
|
282
|
+
if packet.flags_control
|
283
|
+
on_ctrl_message(packet)
|
284
|
+
else # process non-connless packets
|
285
|
+
on_client_packet(packet)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def get_next_client_id
|
290
|
+
(0..MAX_CLIENTS).each do |i|
|
291
|
+
next if @clients[i]
|
292
|
+
|
293
|
+
return i
|
294
|
+
end
|
295
|
+
-1
|
296
|
+
end
|
297
|
+
|
298
|
+
def tick_start_time(tick)
|
299
|
+
# TODO: implement this C++ code
|
300
|
+
# m_GameStartTime + (time_freq()*Tick)/SERVER_TICK_SPEED;
|
301
|
+
end
|
302
|
+
|
303
|
+
def do_snapshot
|
304
|
+
delta_tick = -1
|
305
|
+
# DeltaTick = m_aClients[i].m_LastAckedSnapshot;
|
306
|
+
data = []
|
307
|
+
data += Packer.pack_int(@current_game_tick)
|
308
|
+
data += Packer.pack_int(@current_game_tick - delta_tick)
|
309
|
+
msg_snap_empty = NetChunk.create_header(vital: false, size: data.size + 1) +
|
310
|
+
[pack_msg_id(NETMSG_SNAPEMPTY, system: true)] +
|
311
|
+
data
|
312
|
+
# data = []
|
313
|
+
# data += Packer.pack_int(@current_game_tick)
|
314
|
+
# data += Packer.pack_int(@current_game_tick - delta_tick)
|
315
|
+
# msg_snap_single = NetChunk.create_header(vital: false, size: data.size + 1) +
|
316
|
+
# [pack_msg_id(NETMSG_SNAPSINGLE, system: true)] +
|
317
|
+
# data
|
318
|
+
@clients.each do |_id, client|
|
319
|
+
next unless client.in_game?
|
320
|
+
|
321
|
+
@netbase.send_packet(msg_snap_empty, chunks: 1, client:)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def get_player_by_id(id)
|
326
|
+
@clients[id]&.player
|
327
|
+
end
|
328
|
+
|
329
|
+
def tick
|
330
|
+
unless @clients.empty?
|
331
|
+
now = Time.now
|
332
|
+
diff = now - @last_snap_time
|
333
|
+
# TODO: replace snaps every second by something more correct
|
334
|
+
if diff > 1
|
335
|
+
@current_game_tick += 1
|
336
|
+
do_snapshot
|
337
|
+
end
|
338
|
+
@game_server.on_tick
|
339
|
+
end
|
340
|
+
|
341
|
+
begin
|
342
|
+
data, sender_inet_addr = @s.recvfrom_nonblock(1400)
|
343
|
+
rescue IO::EAGAINWaitReadable
|
344
|
+
data = nil
|
345
|
+
sender_inet_addr = nil
|
346
|
+
end
|
347
|
+
return unless data
|
348
|
+
|
349
|
+
packet = Packet.new(data, '<')
|
350
|
+
packet.addr.ip = sender_inet_addr[2] # or 3 idk bot 127.0.0.1 in my local test case
|
351
|
+
packet.addr.port = sender_inet_addr[1]
|
352
|
+
@clients.each do |id, client|
|
353
|
+
next unless packet.addr.eq(client.addr)
|
354
|
+
|
355
|
+
client.last_recv_time = Time.now
|
356
|
+
packet.client_id = id
|
357
|
+
packet.client = client
|
358
|
+
end
|
359
|
+
|
360
|
+
puts packet.to_s if @verbose
|
361
|
+
on_packet(packet)
|
362
|
+
end
|
363
|
+
end
|
data/lib/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: teeworlds_network
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ChillerDragon
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: huffman_tw
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.9.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.9.0
|
41
|
+
description: |2
|
42
|
+
A library wrapping the network protocol of the game teeworlds.
|
43
|
+
Only supporting the version 0.7 of the teeworlds protocol.
|
44
|
+
email: ChillerDragon@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- lib/array.rb
|
50
|
+
- lib/bytes.rb
|
51
|
+
- lib/chunk.rb
|
52
|
+
- lib/config.rb
|
53
|
+
- lib/context.rb
|
54
|
+
- lib/game_client.rb
|
55
|
+
- lib/game_server.rb
|
56
|
+
- lib/messages/cl_emoticon.rb
|
57
|
+
- lib/messages/cl_say.rb
|
58
|
+
- lib/messages/client_info.rb
|
59
|
+
- lib/messages/game_info.rb
|
60
|
+
- lib/messages/input_timing.rb
|
61
|
+
- lib/messages/maplist_entry_add.rb
|
62
|
+
- lib/messages/maplist_entry_rem.rb
|
63
|
+
- lib/messages/rcon_cmd_add.rb
|
64
|
+
- lib/messages/rcon_cmd_rem.rb
|
65
|
+
- lib/messages/rcon_line.rb
|
66
|
+
- lib/messages/server_info.rb
|
67
|
+
- lib/messages/server_settings.rb
|
68
|
+
- lib/messages/start_info.rb
|
69
|
+
- lib/messages/sv_client_drop.rb
|
70
|
+
- lib/models/chat_message.rb
|
71
|
+
- lib/models/map.rb
|
72
|
+
- lib/models/net_addr.rb
|
73
|
+
- lib/models/packet_flags.rb
|
74
|
+
- lib/models/player.rb
|
75
|
+
- lib/models/token.rb
|
76
|
+
- lib/net_base.rb
|
77
|
+
- lib/network.rb
|
78
|
+
- lib/packer.rb
|
79
|
+
- lib/packet.rb
|
80
|
+
- lib/snapshot/events/damage.rb
|
81
|
+
- lib/snapshot/events/death.rb
|
82
|
+
- lib/snapshot/events/explosion.rb
|
83
|
+
- lib/snapshot/events/hammer_hit.rb
|
84
|
+
- lib/snapshot/events/sound_world.rb
|
85
|
+
- lib/snapshot/events/spawn.rb
|
86
|
+
- lib/snapshot/items/character.rb
|
87
|
+
- lib/snapshot/items/client_info.rb
|
88
|
+
- lib/snapshot/items/flag.rb
|
89
|
+
- lib/snapshot/items/game_data.rb
|
90
|
+
- lib/snapshot/items/game_data_flag.rb
|
91
|
+
- lib/snapshot/items/game_data_team.rb
|
92
|
+
- lib/snapshot/items/laser.rb
|
93
|
+
- lib/snapshot/items/pickup.rb
|
94
|
+
- lib/snapshot/items/player_info.rb
|
95
|
+
- lib/snapshot/items/player_input.rb
|
96
|
+
- lib/snapshot/items/projectile.rb
|
97
|
+
- lib/snapshot/items/spectator_info.rb
|
98
|
+
- lib/snapshot/items/tune_params.rb
|
99
|
+
- lib/snapshot/snap_event_base.rb
|
100
|
+
- lib/snapshot/snap_item_base.rb
|
101
|
+
- lib/snapshot/unpacker.rb
|
102
|
+
- lib/string.rb
|
103
|
+
- lib/teeworlds_client.rb
|
104
|
+
- lib/teeworlds_network.rb
|
105
|
+
- lib/teeworlds_server.rb
|
106
|
+
- lib/version.rb
|
107
|
+
homepage: https://github.com/ChillerDragon/teeworlds_network
|
108
|
+
licenses:
|
109
|
+
- Unlicense
|
110
|
+
metadata:
|
111
|
+
rubygems_mfa_required: 'true'
|
112
|
+
documentation_uri: https://github.com/ChillerDragon/teeworlds_network/tree/master/docs
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: 3.1.2
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubygems_version: 3.3.7
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: teeworlds 0.7 network protocol (client and server)
|
132
|
+
test_files: []
|