Compare commits
10 commits
c726756b5c
...
10ca8238a8
Author | SHA1 | Date | |
---|---|---|---|
ChillerDragon | 10ca8238a8 | ||
ChillerDragon | af7367be0c | ||
ChillerDragon | deb679d1d0 | ||
ChillerDragon | 1f46adca46 | ||
ChillerDragon | ba7bfc6c07 | ||
ChillerDragon | 7e45bbe038 | ||
ChillerDragon | aa5b755204 | ||
ChillerDragon | f44a370dc0 | ||
ChillerDragon | 420c6deb92 | ||
ChillerDragon | cacb75ac62 |
|
@ -78,7 +78,13 @@ end
|
||||||
|
|
||||||
Signal.trap('INT') do
|
Signal.trap('INT') do
|
||||||
client.disconnect
|
client.disconnect
|
||||||
|
exit
|
||||||
end
|
end
|
||||||
|
|
||||||
# connect and detach thread
|
# connect and detach thread
|
||||||
client.connect(args[:ip], args[:port], detach: false)
|
client.connect(args[:ip], args[:port], detach: true)
|
||||||
|
|
||||||
|
loop do
|
||||||
|
msg = $stdin.gets.chomp
|
||||||
|
client.send_chat(msg)
|
||||||
|
end
|
||||||
|
|
11
lib/chunk.rb
11
lib/chunk.rb
|
@ -13,13 +13,14 @@ require_relative 'bytes'
|
||||||
#
|
#
|
||||||
# https://chillerdragon.github.io/teeworlds-protocol/07/packet_layout.html
|
# https://chillerdragon.github.io/teeworlds-protocol/07/packet_layout.html
|
||||||
class NetChunk
|
class NetChunk
|
||||||
attr_reader :next, :data, :msg, :sys, :flags, :header_raw, :full_raw
|
attr_reader :next, :data, :msg, :sys, :flags, :seq, :header_raw, :full_raw
|
||||||
|
|
||||||
@@sent_vital_chunks = 0
|
@@sent_vital_chunks = 0
|
||||||
|
|
||||||
def initialize(data)
|
def initialize(data)
|
||||||
@next = nil
|
@next = nil
|
||||||
@flags = {}
|
@flags = {}
|
||||||
|
@seq = 0
|
||||||
@size = 0
|
@size = 0
|
||||||
parse_header(data[0..2])
|
parse_header(data[0..2])
|
||||||
header_size = if flags_vital
|
header_size = if flags_vital
|
||||||
|
@ -135,8 +136,12 @@ class NetChunk
|
||||||
size_bytes.map! { |b| b[2..].join }
|
size_bytes.map! { |b| b[2..].join }
|
||||||
@size = size_bytes.join.to_i(2)
|
@size = size_bytes.join.to_i(2)
|
||||||
|
|
||||||
# sequence number
|
if @flags[:vital]
|
||||||
# in da third byte but who needs seq?!
|
data = data[0..2].bytes
|
||||||
|
@seq = (data[1] & (0xC0 << 2)) | data[2]
|
||||||
|
else
|
||||||
|
@seq = 0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
class Config
|
class Config
|
||||||
def initialize(options = {})
|
def initialize(options = {})
|
||||||
filepath = options[:file] || 'autoexec.cfg'
|
filepath = options[:file] || 'autoexec.cfg'
|
||||||
|
@type = options[:type] || :client
|
||||||
init_configs
|
init_configs
|
||||||
load_cfg(filepath)
|
load_cfg(filepath)
|
||||||
end
|
end
|
||||||
|
|
||||||
def init_configs
|
def init_client_configs
|
||||||
@configs = {
|
@configs = {
|
||||||
password: { help: 'Password to the server', default: '' }
|
password: { help: 'Password to the server', default: '' }
|
||||||
}
|
}
|
||||||
|
@ -15,6 +16,23 @@ class Config
|
||||||
echo: { help: 'Echo the text', callback: proc { |arg| puts arg } },
|
echo: { help: 'Echo the text', callback: proc { |arg| puts arg } },
|
||||||
quit: { help: 'Quit', callback: proc { |_| exit } }
|
quit: { help: 'Quit', callback: proc { |_| exit } }
|
||||||
}
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def init_server_configs
|
||||||
|
@configs = {
|
||||||
|
sv_map: { help: 'map', default: 'dm1' }
|
||||||
|
}
|
||||||
|
@commands = {
|
||||||
|
shutdown: { help: 'shutdown server', callback: proc { |_| exit } }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def init_configs
|
||||||
|
if @type == :client
|
||||||
|
init_client_configs
|
||||||
|
else
|
||||||
|
init_server_configs
|
||||||
|
end
|
||||||
@configs.each do |cfg, data|
|
@configs.each do |cfg, data|
|
||||||
self.class.send(:attr_accessor, cfg)
|
self.class.send(:attr_accessor, cfg)
|
||||||
instance_variable_set("@#{cfg}", data[:default])
|
instance_variable_set("@#{cfg}", data[:default])
|
||||||
|
|
20
lib/connection.rb
Normal file
20
lib/connection.rb
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
##
|
||||||
|
# Only used for chunks where the sequence number does not match the expected value
|
||||||
|
# to decide wether to drop known chunks silently or request resend if something got lost
|
||||||
|
#
|
||||||
|
# true - if the sequence number is already known and the chunk should be dropped
|
||||||
|
# false - if the sequence number is off and we need to request a resend of lost chunks
|
||||||
|
#
|
||||||
|
# @return [Boolean]
|
||||||
|
def seq_in_backroom?(seq, ack)
|
||||||
|
bottom = ack - (NET_MAX_SEQUENCE / 2)
|
||||||
|
if bottom.negative?
|
||||||
|
return true if seq <= ack
|
||||||
|
return true if seq >= (bottom + NET_MAX_SEQUENCE)
|
||||||
|
elsif seq <= ack && seq >= bottom
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
false
|
||||||
|
end
|
|
@ -11,12 +11,15 @@ require_relative 'messages/cl_say'
|
||||||
require_relative 'messages/cl_emoticon'
|
require_relative 'messages/cl_emoticon'
|
||||||
require_relative 'messages/cl_info'
|
require_relative 'messages/cl_info'
|
||||||
require_relative 'messages/cl_input'
|
require_relative 'messages/cl_input'
|
||||||
|
require_relative 'messages/client_info'
|
||||||
|
|
||||||
class GameServer
|
class GameServer
|
||||||
attr_accessor :pred_game_tick, :ack_game_tick, :map
|
attr_accessor :pred_game_tick, :ack_game_tick, :map
|
||||||
|
|
||||||
def initialize(server)
|
def initialize(server)
|
||||||
@server = server
|
@server = server
|
||||||
|
@config = server.config
|
||||||
|
@map_path = nil
|
||||||
@ack_game_tick = -1
|
@ack_game_tick = -1
|
||||||
@pred_game_tick = 0
|
@pred_game_tick = 0
|
||||||
@map = Map.new(
|
@map = Map.new(
|
||||||
|
@ -27,6 +30,29 @@ class GameServer
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def load_map
|
||||||
|
puts "loading map '#{@config.sv_map}' ..."
|
||||||
|
map_path = nil
|
||||||
|
if File.exist? "data/#{@config.sv_map}.map"
|
||||||
|
map_path = "data/#{@config.sv_map}.map"
|
||||||
|
elsif File.exist? "data/maps/#{@config.sv_map}.map"
|
||||||
|
map_path = "data/maps/#{@config.sv_map}.map"
|
||||||
|
elsif File.exist? "maps/#{@config.sv_map}.map"
|
||||||
|
map_path = "maps/#{@config.sv_map}.map"
|
||||||
|
elsif File.exist? "#{Dir.home}/.teeworlds/maps/#{@config.sv_map}.map"
|
||||||
|
map_path = "#{Dir.home}/.teeworlds/maps/#{@config.sv_map}.map"
|
||||||
|
end
|
||||||
|
|
||||||
|
if map_path.nil?
|
||||||
|
puts "map not found '#{@config.sv_map}'"
|
||||||
|
# TODO: this should error when the feature is done
|
||||||
|
# exit 1
|
||||||
|
else
|
||||||
|
puts "found at #{map_path}"
|
||||||
|
@map_path = map_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
# call_hook
|
# call_hook
|
||||||
#
|
#
|
||||||
|
@ -97,6 +123,16 @@ class GameServer
|
||||||
puts msg
|
puts msg
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# https://chillerdragon.github.io/teeworlds-protocol/07/game_messages.html#NETMSGTYPE_SV_CLIENTINFO
|
||||||
|
# send infos about currently connected clients to the newly joined client
|
||||||
|
# send info of the newly joined client to all currently connected clients
|
||||||
|
#
|
||||||
|
# @param client [Client] newly joined client
|
||||||
|
def send_client_infos(client)
|
||||||
|
client_info = ClientInfo.new(client_id: client.id, local: 1)
|
||||||
|
@server.send_client_info(client, client_info)
|
||||||
|
end
|
||||||
|
|
||||||
def on_enter_game(_chunk, packet)
|
def on_enter_game(_chunk, packet)
|
||||||
# vanilla server responds to enter game with two packets
|
# vanilla server responds to enter game with two packets
|
||||||
# first:
|
# first:
|
||||||
|
@ -109,6 +145,7 @@ class GameServer
|
||||||
|
|
||||||
packet.client.in_game = true
|
packet.client.in_game = true
|
||||||
@server.send_server_info(packet.client, ServerInfo.new.to_a)
|
@server.send_server_info(packet.client, ServerInfo.new.to_a)
|
||||||
|
send_client_infos(packet.client)
|
||||||
@server.send_game_info(packet.client, GameInfo.new.to_a)
|
@server.send_game_info(packet.client, GameInfo.new.to_a)
|
||||||
|
|
||||||
puts "'#{packet.client.player.name}' joined the game"
|
puts "'#{packet.client.player.name}' joined the game"
|
||||||
|
|
|
@ -55,10 +55,9 @@ class Packer
|
||||||
first = "1#{sign}#{num_bits[-6..]}"
|
first = "1#{sign}#{num_bits[-6..]}"
|
||||||
|
|
||||||
num_bits = num_bits[0..-7]
|
num_bits = num_bits[0..-7]
|
||||||
bytes = []
|
bytes = num_bits.chars.groups_of(7).map do |seven_bits|
|
||||||
num_bits.chars.groups_of(7).each do |seven_bits|
|
|
||||||
# mark all as extended
|
# mark all as extended
|
||||||
bytes << "1#{seven_bits.join.rjust(7, '0')}"
|
"1#{seven_bits.join.rjust(7, '0')}"
|
||||||
end
|
end
|
||||||
# least significant first
|
# least significant first
|
||||||
bytes = bytes.reverse
|
bytes = bytes.reverse
|
||||||
|
|
|
@ -2,37 +2,11 @@
|
||||||
|
|
||||||
require_relative 'snapshot'
|
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
|
class SnapshotBuilder
|
||||||
def initialize
|
def initialize
|
||||||
@data_size = 0
|
@data_size = 0
|
||||||
@num_items = 0
|
@num_items = 0
|
||||||
|
# @type items [Array<SnapItemBase>]
|
||||||
@items = []
|
@items = []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -41,21 +15,16 @@ class SnapshotBuilder
|
||||||
#
|
#
|
||||||
# https://chillerdragon.github.io/teeworlds-protocol/07/snap_items.html
|
# 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 the snap item. For characters that is the ClientID.
|
||||||
# @param id [Integer] id of said item for characters thats the ClientID
|
# Not to be confused with the type
|
||||||
# @param fields [Array] array of uncompressed integers
|
# @param item [SnapItemBase] Snap item instance. Holding type and payload.
|
||||||
# for example [0, 0, 1] for obj_flag
|
def new_item(id, item)
|
||||||
# would set
|
item.id = id
|
||||||
# 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)
|
@items.push(item)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [Snapshot]
|
# @return [Snapshot]
|
||||||
def finish
|
def finish
|
||||||
Snapshot.new
|
Snapshot.new(@items)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetEvent
|
||||||
attr_accessor :client_id, :angle, :health_ammount, :armor_amount, :self
|
attr_accessor :client_id, :angle, :health_ammount, :armor_amount, :self
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETEVENTTYPE_DAMAGE
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
client_id
|
client_id
|
||||||
angle
|
angle
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetEvent
|
||||||
attr_accessor :client_id
|
attr_accessor :client_id
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETEVENTTYPE_DEATH
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
client_id
|
client_id
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,6 +5,7 @@ require_relative '../snap_item_base'
|
||||||
class NetEvent
|
class NetEvent
|
||||||
class Explosion < SnapEventBase
|
class Explosion < SnapEventBase
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETEVENTTYPE_EXPLOSION
|
||||||
@field_names = []
|
@field_names = []
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ require_relative '../snap_item_base'
|
||||||
class NetEvent
|
class NetEvent
|
||||||
class HammerHit < SnapEventBase
|
class HammerHit < SnapEventBase
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETEVENTTYPE_HAMMERHIT
|
||||||
@field_names = []
|
@field_names = []
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetEvent
|
||||||
attr_accessor :sound_id
|
attr_accessor :sound_id
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETEVENTTYPE_SOUNDWORLD
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
sound_id
|
sound_id
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,6 +5,7 @@ require_relative '../snap_item_base'
|
||||||
class NetEvent
|
class NetEvent
|
||||||
class Spawn < SnapEventBase
|
class Spawn < SnapEventBase
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETEVENTTYPE_SPAWN
|
||||||
@field_names = []
|
@field_names = []
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,7 @@ class NetObj
|
||||||
:health, :armor, :ammo_count, :weapon, :emote, :attack_tick, :triggered_events
|
:health, :armor, :ammo_count, :weapon, :emote, :attack_tick, :triggered_events
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_CHARACTER
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
tick
|
tick
|
||||||
x
|
x
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetObj
|
||||||
attr_accessor :local, :team
|
attr_accessor :local, :team
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_DE_CLIENTINFO
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
local
|
local
|
||||||
team
|
team
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetObj
|
||||||
attr_accessor :x, :y, :team
|
attr_accessor :x, :y, :team
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_FLAG
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
x
|
x
|
||||||
y
|
y
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetObj
|
||||||
attr_accessor :game_start_tick, :game_state_flags, :game_state_end_tick
|
attr_accessor :game_start_tick, :game_state_flags, :game_state_end_tick
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_GAMEDATA
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
game_start_tick
|
game_start_tick
|
||||||
game_state_flags
|
game_state_flags
|
||||||
|
|
|
@ -8,6 +8,7 @@ class NetObj
|
||||||
:flag_drop_tick_red, :flag_drop_tick_blue
|
:flag_drop_tick_red, :flag_drop_tick_blue
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_GAMEDATAFLAG
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
flag_carrier_red
|
flag_carrier_red
|
||||||
flag_carrier_blue
|
flag_carrier_blue
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetObj
|
||||||
attr_accessor :teamscore_red, :teamscore_blue
|
attr_accessor :teamscore_red, :teamscore_blue
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_GAMEDATATEAM
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
teamscore_red
|
teamscore_red
|
||||||
teamscore_blue
|
teamscore_blue
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetObj
|
||||||
attr_accessor :x, :y, :from_x, :from_y, :start_tick
|
attr_accessor :x, :y, :from_x, :from_y, :start_tick
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_LASER
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
x
|
x
|
||||||
y
|
y
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetObj
|
||||||
attr_accessor :x, :y, :type
|
attr_accessor :x, :y, :type
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_PICKUP
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
x
|
x
|
||||||
y
|
y
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetObj
|
||||||
attr_accessor :player_flags, :score, :latency
|
attr_accessor :player_flags, :score, :latency
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_PLAYERINFO
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
player_flags
|
player_flags
|
||||||
score
|
score
|
||||||
|
|
|
@ -10,6 +10,7 @@ class NetObj
|
||||||
:next_weapon, :prev_weapon
|
:next_weapon, :prev_weapon
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_PLAYERINPUT
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
direction
|
direction
|
||||||
target_x
|
target_x
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetObj
|
||||||
attr_accessor :x, :y, :vel_x, :vel_y, :type, :start_tick
|
attr_accessor :x, :y, :vel_x, :vel_y, :type, :start_tick
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_PROJECTILE
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
x
|
x
|
||||||
y
|
y
|
||||||
|
|
|
@ -7,6 +7,7 @@ class NetObj
|
||||||
attr_accessor :spec_mode, :spectator_id, :x, :y
|
attr_accessor :spec_mode, :spectator_id, :x, :y
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
|
@type = NETOBJTYPE_SPECTATORINFO
|
||||||
@field_names = %i[
|
@field_names = %i[
|
||||||
spec_mode
|
spec_mode
|
||||||
spectator_id
|
spectator_id
|
||||||
|
|
|
@ -59,7 +59,7 @@ class NetObj
|
||||||
end
|
end
|
||||||
|
|
||||||
def init_hash(attr)
|
def init_hash(attr)
|
||||||
@fields_names.each do |name|
|
@field_names.each do |name|
|
||||||
instance_variable_set("@#{name}", attr[name] || 0)
|
instance_variable_set("@#{name}", attr[name] || 0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative '../packer'
|
require_relative '../packer'
|
||||||
|
require_relative '../network'
|
||||||
|
|
||||||
class SnapItemBase
|
class SnapItemBase
|
||||||
attr_reader :notes, :name, :id
|
attr_reader :notes, :name, :type
|
||||||
|
attr_accessor :id
|
||||||
|
|
||||||
def initialize(hash_or_raw)
|
def initialize(hash_or_raw)
|
||||||
@fields = @field_names.map do |_|
|
@fields = @field_names.map do |_|
|
||||||
|
@ -25,6 +27,10 @@ class SnapItemBase
|
||||||
@fields.none?(&:nil?)
|
@fields.none?(&:nil?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def size
|
||||||
|
@fields.size
|
||||||
|
end
|
||||||
|
|
||||||
def init_unpacker(u)
|
def init_unpacker(u)
|
||||||
@id = u.get_int
|
@id = u.get_int
|
||||||
p = u.parsed.last
|
p = u.parsed.last
|
||||||
|
@ -56,8 +62,12 @@ class SnapItemBase
|
||||||
end
|
end
|
||||||
|
|
||||||
def init_hash(attr)
|
def init_hash(attr)
|
||||||
@fields_names.each do |name|
|
@field_names.each_with_index do |name, i|
|
||||||
instance_variable_set("@#{name}", attr[name] || 0)
|
# direct instance variables work
|
||||||
|
# but using the @fields array is easier to then pack
|
||||||
|
# idk how to iterate just the instance variables that i need on packing
|
||||||
|
# instance_variable_set("@#{name}", attr[name] || 0)
|
||||||
|
@fields[i] = attr[name] || 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -74,6 +84,8 @@ class SnapItemBase
|
||||||
# int array the server sends to the client
|
# int array the server sends to the client
|
||||||
def to_a
|
def to_a
|
||||||
arr = []
|
arr = []
|
||||||
|
arr += Packer.pack_int(@type)
|
||||||
|
arr += Packer.pack_int(@id)
|
||||||
@fields.each do |value|
|
@fields.each do |value|
|
||||||
arr += Packer.pack_int(value)
|
arr += Packer.pack_int(value)
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Snapshot
|
||||||
def initialize(items)
|
def initialize(items)
|
||||||
# @type game_tick [Integer]
|
# @type game_tick [Integer]
|
||||||
@game_tick = 0
|
@game_tick = 0
|
||||||
# @type items [Array<SnapItemBase>]
|
# @type items [Array<SnapItemBase>, Array<SnapItem>]
|
||||||
@items = items
|
@items = items
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -216,6 +216,7 @@ class SnapshotUnpacker
|
||||||
obj = NetEvent::HammerHit.new(u)
|
obj = NetEvent::HammerHit.new(u)
|
||||||
elsif @verbose
|
elsif @verbose
|
||||||
puts "no match #{item_type}"
|
puts "no match #{item_type}"
|
||||||
|
exit(1)
|
||||||
end
|
end
|
||||||
obj = unpack_ddnet_item(u, notes) if !obj && item_type.zero?
|
obj = unpack_ddnet_item(u, notes) if !obj && item_type.zero?
|
||||||
if obj
|
if obj
|
||||||
|
|
|
@ -14,6 +14,7 @@ require_relative 'packer'
|
||||||
require_relative 'models/player'
|
require_relative 'models/player'
|
||||||
require_relative 'game_client'
|
require_relative 'game_client'
|
||||||
require_relative 'config'
|
require_relative 'config'
|
||||||
|
require_relative 'connection'
|
||||||
|
|
||||||
class TeeworldsClient
|
class TeeworldsClient
|
||||||
attr_reader :state, :hooks, :game_client, :verbose_snap
|
attr_reader :state, :hooks, :game_client, :verbose_snap
|
||||||
|
@ -445,9 +446,20 @@ class TeeworldsClient
|
||||||
end
|
end
|
||||||
chunks = BigChungusTheChunkGetter.get_chunks(data)
|
chunks = BigChungusTheChunkGetter.get_chunks(data)
|
||||||
chunks.each do |chunk|
|
chunks.each do |chunk|
|
||||||
if chunk.flags_vital && !chunk.flags_resend && chunk.msg != NETMSG_NULL
|
if chunk.flags_vital
|
||||||
|
if chunk.seq == (@netbase.ack + 1) % NET_MAX_SEQUENCE
|
||||||
|
# in sequence
|
||||||
@netbase.ack = (@netbase.ack + 1) % NET_MAX_SEQUENCE
|
@netbase.ack = (@netbase.ack + 1) % NET_MAX_SEQUENCE
|
||||||
puts "got ack: #{@netbase.ack}" if @verbose
|
else
|
||||||
|
puts 'warning: got chunk out of sequence! ' \
|
||||||
|
"seq=#{chunk.seq} expected_seq=#{(@netbase.ack + 1) % NET_MAX_SEQUENCE}"
|
||||||
|
if seq_in_backroom?(chunk.seq, @netbase.ack)
|
||||||
|
puts ' dropping known chunk ...'
|
||||||
|
next
|
||||||
|
end
|
||||||
|
# TODO: request resend
|
||||||
|
puts ' REQUESTING RESEND NOT IMPLEMENTED'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
process_chunk(chunk)
|
process_chunk(chunk)
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,9 +11,11 @@ require_relative 'chunk'
|
||||||
require_relative 'net_base'
|
require_relative 'net_base'
|
||||||
require_relative 'models/net_addr'
|
require_relative 'models/net_addr'
|
||||||
require_relative 'packer'
|
require_relative 'packer'
|
||||||
|
require_relative 'config'
|
||||||
require_relative 'game_server'
|
require_relative 'game_server'
|
||||||
require_relative 'models/token'
|
require_relative 'models/token'
|
||||||
require_relative 'messages/sv_emoticon'
|
require_relative 'messages/sv_emoticon'
|
||||||
|
require_relative 'snapshot/builder'
|
||||||
|
|
||||||
class Client
|
class Client
|
||||||
attr_accessor :id, :addr, :vital_sent, :last_recv_time, :token, :player, :in_game, :authed
|
attr_accessor :id, :addr, :vital_sent, :last_recv_time, :token, :player, :in_game, :authed
|
||||||
|
@ -64,14 +66,17 @@ class Client
|
||||||
end
|
end
|
||||||
|
|
||||||
class TeeworldsServer
|
class TeeworldsServer
|
||||||
attr_accessor :clients
|
attr_accessor :clients, :config
|
||||||
attr_reader :hooks, :shutdown_reason, :current_game_tick
|
attr_reader :hooks, :shutdown_reason, :current_game_tick
|
||||||
|
|
||||||
def initialize(options = {})
|
def initialize(options = {})
|
||||||
@verbose = options[:verbose] || false
|
@verbose = options[:verbose] || false
|
||||||
@ip = '127.0.0.1'
|
@ip = '127.0.0.1'
|
||||||
@port = 8303
|
@port = 8303
|
||||||
|
@config = Config.new(file: options[:config], type: :server)
|
||||||
@game_server = GameServer.new(self)
|
@game_server = GameServer.new(self)
|
||||||
|
@game_server.load_map
|
||||||
|
# @type clients [Hash<Integer, Client>]
|
||||||
@clients = {}
|
@clients = {}
|
||||||
@current_game_tick = 0
|
@current_game_tick = 0
|
||||||
@last_snap_time = Time.now
|
@last_snap_time = Time.now
|
||||||
|
@ -369,6 +374,19 @@ class TeeworldsServer
|
||||||
@netbase.send_packet(msg, chunks: 1, client:)
|
@netbase.send_packet(msg, chunks: 1, client:)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# https://chillerdragon.github.io/teeworlds-protocol/07/game_messages.html#NETMSGTYPE_SV_CLIENTINFO
|
||||||
|
#
|
||||||
|
# @param client [Client] recipient of the message
|
||||||
|
# @param client_info [ClientInfo] client info net message
|
||||||
|
def send_client_info(client, client_info)
|
||||||
|
data = client_info.to_a
|
||||||
|
msg = NetChunk.create_header(vital: true, size: 1 + data.size, client:) +
|
||||||
|
[pack_msg_id(NETMSGTYPE_SV_CLIENTINFO, system: false)] +
|
||||||
|
data
|
||||||
|
@netbase.send_packet(msg, chunks: 1, client:)
|
||||||
|
end
|
||||||
|
|
||||||
def send_server_info(client, server_info)
|
def send_server_info(client, server_info)
|
||||||
msg = NetChunk.create_header(vital: true, size: 1 + server_info.size, client:) +
|
msg = NetChunk.create_header(vital: true, size: 1 + server_info.size, client:) +
|
||||||
[pack_msg_id(NETMSG_SERVERINFO, system: true)] +
|
[pack_msg_id(NETMSG_SERVERINFO, system: true)] +
|
||||||
|
@ -470,11 +488,57 @@ class TeeworldsServer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require_relative 'snapshot/items/game_data'
|
||||||
|
require_relative 'snapshot/items/game_data_team'
|
||||||
|
require_relative 'snapshot/items/game_data_flag'
|
||||||
|
require_relative 'snapshot/items/player_info'
|
||||||
|
require_relative 'snapshot/items/character'
|
||||||
|
require_relative 'snapshot/items/flag'
|
||||||
|
|
||||||
def do_snap_single
|
def do_snap_single
|
||||||
builder = SnapshotBuilder.new
|
builder = SnapshotBuilder.new
|
||||||
|
builder.new_item(0, NetObj::Flag.new(
|
||||||
|
x: 1200, y: 304, team: 0
|
||||||
|
))
|
||||||
|
builder.new_item(1, NetObj::Flag.new(
|
||||||
|
x: 1296, y: 304, team: 1
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::GameData.new(
|
||||||
|
game_start_tick: 0,
|
||||||
|
game_state_flags: 1,
|
||||||
|
game_state_end_tick: 500
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::GameDataTeam.new(
|
||||||
|
teamscore_red: 0,
|
||||||
|
teamscore_blue: 0
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::GameDataFlag.new(
|
||||||
|
flag_carrier_red: -2,
|
||||||
|
flag_carrier_blue: -2,
|
||||||
|
flag_drop_tick_red: 0,
|
||||||
|
flag_drop_tick_blue: 0
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::PlayerInfo.new(
|
||||||
|
player_flags: 8,
|
||||||
|
score: 0,
|
||||||
|
latency: 0
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::Character.new(
|
||||||
|
x: 784, y: 305,
|
||||||
|
vel_x: 0, vel_y: 0,
|
||||||
|
angle: 0, direction: 0, jumped: 0,
|
||||||
|
hooked_player: -1, hook_state: 0,
|
||||||
|
hook_tick: 0, hook_x: 784, hook_y: 304,
|
||||||
|
hook_dx: 784, hook_dy: 0,
|
||||||
|
health: 10, armor: 0, ammo_count: 10,
|
||||||
|
weapon: 1, emote: 0,
|
||||||
|
attack_tick: 0, triggered_events: 0
|
||||||
|
))
|
||||||
snap = builder.finish
|
snap = builder.finish
|
||||||
items = snap.to_a
|
items = snap.to_a
|
||||||
|
|
||||||
|
delta_tick = -1
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
# Game tick Int
|
# Game tick Int
|
||||||
data += Packer.pack_int(@current_game_tick)
|
data += Packer.pack_int(@current_game_tick)
|
||||||
|
@ -483,15 +547,28 @@ class TeeworldsServer
|
||||||
# Crc Int
|
# Crc Int
|
||||||
data += Packer.pack_int(snap.crc)
|
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.
|
# 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)
|
header = []
|
||||||
|
header += [0x00] # removed items
|
||||||
|
header += Packer.pack_int(snap.items.count) # num item deltas
|
||||||
|
header += [0x00] # _zero
|
||||||
|
part_size = items.size + header.size
|
||||||
|
data += Packer.pack_int(part_size)
|
||||||
# Data
|
# Data
|
||||||
|
data += header
|
||||||
data += items
|
data += items
|
||||||
|
msg = NetChunk.create_header(vital: false, size: data.size + 1) +
|
||||||
|
[pack_msg_id(NETMSG_SNAPSINGLE, system: true)] +
|
||||||
|
data
|
||||||
|
@clients.each_value do |client|
|
||||||
|
next unless client.in_game?
|
||||||
|
|
||||||
p data
|
@netbase.send_packet(msg, chunks: 1, client:)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_snapshot
|
def do_snapshot
|
||||||
do_snap_empty
|
do_snap_empty
|
||||||
|
# do_snap_single
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_player_by_id(id)
|
def get_player_by_id(id)
|
||||||
|
|
|
@ -1,39 +1,199 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# require_relative '../lib/snapshot/builder.rb'
|
require_relative '../lib/snapshot/builder'
|
||||||
#
|
require_relative '../lib/snapshot/items/character'
|
||||||
# describe 'SnapshotBuilder', :snapshot do
|
require_relative '../lib/snapshot/items/pickup'
|
||||||
# context 'finish' do
|
require_relative '../lib/snapshot/items/flag'
|
||||||
# it 'Should create correct snap' do
|
require_relative '../lib/snapshot/items/game_data'
|
||||||
# builder = SnapshotBuilder.new
|
require_relative '../lib/snapshot/items/game_data_team'
|
||||||
# snap = builder.finish
|
require_relative '../lib/snapshot/items/game_data_flag'
|
||||||
# expected_payload = [
|
require_relative '../lib/snapshot/items/player_info'
|
||||||
# 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)
|
describe 'SnapshotBuilder', :snapshot do
|
||||||
# id=8 game_tick=1908 delta_tick=38
|
context '#finish should create snap payload' do
|
||||||
# num_parts=1 part=0 crc=16846 part_size=28
|
it 'Should build a snap with one character' do
|
||||||
#
|
builder = SnapshotBuilder.new
|
||||||
# header:
|
char = NetObj::Character.new(
|
||||||
# 11 b4 1d 26 ...& int 17 >> 1 = 8 int 1908 int 38
|
tick: 41, x: 0, y: 13,
|
||||||
# 8e 87 02 1c .... int 16846 int 28
|
vel_x: 0, vel_y: 3507, angle: 0,
|
||||||
#
|
direction: 0, jumped: -1, hooked_player: 0, hook_state: 0,
|
||||||
# payload:
|
hook_tick: 0, hook_x: 0, hook_y: 0, hook_dx: 0,
|
||||||
# 00 01 00 0a .... removed_items=0 num_item_deltas=1 _zero=0 type=10 NetObj::Character
|
hook_dy: 0, health: 0, armor: 0, ammo_count: 0,
|
||||||
# 00 29 00 0d .).. id=0 tick=41 x=0 y=13
|
weapon: 0, emote: 0, attack_tick: 0, triggered_events: 0
|
||||||
# 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
|
builder.new_item(0, char)
|
||||||
# 00 00 00 00 .... hook_tick=0 hook_x=0 hook_y=0 hook_dx=0
|
snap = builder.finish
|
||||||
# 00 00 00 00 .... hook_dy=0 health=0 armor=0 ammo_count=0
|
expected_payload = [
|
||||||
# 00 00 00 00 .... weapon=0 emote=0 attack_tick=0 triggered_events=0
|
0x0a, # 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)
|
||||||
|
# >>> 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
|
||||||
|
end
|
||||||
|
it 'Should build a snap with multiple items' do
|
||||||
|
builder = SnapshotBuilder.new
|
||||||
|
builder.new_item(0, NetObj::Pickup.new(
|
||||||
|
x: 1424, y: 272, type: 0
|
||||||
|
))
|
||||||
|
builder.new_item(1, NetObj::Pickup.new(
|
||||||
|
x: 1488, y: 272, type: 2
|
||||||
|
))
|
||||||
|
builder.new_item(2, NetObj::Pickup.new(
|
||||||
|
x: 1552, y: 272, type: 3
|
||||||
|
))
|
||||||
|
builder.new_item(3, NetObj::Pickup.new(
|
||||||
|
x: 1616, y: 272, type: 4
|
||||||
|
))
|
||||||
|
builder.new_item(7, NetObj::Pickup.new(
|
||||||
|
x: 1392, y: 272, type: 1
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::Flag.new(
|
||||||
|
x: 1200, y: 304, team: 0
|
||||||
|
))
|
||||||
|
builder.new_item(1, NetObj::Flag.new(
|
||||||
|
x: 1296, y: 304, team: 1
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::GameData.new(
|
||||||
|
game_start_tick: 1286,
|
||||||
|
game_state_flags: 0, game_state_end_tick: 0
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::GameDataTeam.new(
|
||||||
|
teamscore_red: 0, teamscore_blue: 0
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::GameDataFlag.new(
|
||||||
|
flag_carrier_red: -2, flag_carrier_blue: -2,
|
||||||
|
flag_drop_tick_red: 0, flag_drop_tick_blue: 0
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::Character.new(
|
||||||
|
tick: 1314, x: 784, y: 306,
|
||||||
|
vel_x: 0, vel_y: 256, angle: -707,
|
||||||
|
direction: 0, jumped: 0, hooked_player: -1, hook_state: 0,
|
||||||
|
hook_tick: 0, hook_x: 784, hook_y: 305, hook_dx: 0,
|
||||||
|
hook_dy: 0, health: 0, armor: 0, ammo_count: 0,
|
||||||
|
weapon: 1, emote: 0, attack_tick: 0, triggered_events: 0
|
||||||
|
))
|
||||||
|
builder.new_item(1, NetObj::Character.new(
|
||||||
|
tick: 1313, x: 848, y: 305,
|
||||||
|
vel_x: 0, vel_y: 128, angle: 0,
|
||||||
|
direction: 0, jumped: 0, hooked_player: -1, hook_state: 0,
|
||||||
|
hook_tick: 0, hook_x: 848, hook_y: 304, hook_dx: 0,
|
||||||
|
hook_dy: 0, health: 10, armor: 0, ammo_count: 10,
|
||||||
|
weapon: 1, emote: 0, attack_tick: 0, triggered_events: 0
|
||||||
|
))
|
||||||
|
builder.new_item(0, NetObj::PlayerInfo.new(
|
||||||
|
player_flags: 8, score: 0, latency: 0
|
||||||
|
))
|
||||||
|
builder.new_item(1, NetObj::PlayerInfo.new(
|
||||||
|
player_flags: 8, score: 0, latency: 0
|
||||||
|
))
|
||||||
|
snap = builder.finish
|
||||||
|
expected_payload = [
|
||||||
|
0x04, # type=4 NetObj::Pickup
|
||||||
|
0x00, 0x90, 0x16, 0x90, # id=0 x=1424 y=272
|
||||||
|
0x04, 0x00, 0x04, 0x01, # y=272 type=0 id=1 type=4 NetObj::Pickup
|
||||||
|
0x90, 0x17, 0x90, 0x04, # x=1488 y=272
|
||||||
|
0x02, 0x04, 0x02, 0x90, # type=2 id=2 x=1552 type=4 NetObj::Pickup
|
||||||
|
0x18, 0x90, 0x04, 0x03, # x=1552 y=272 type=3
|
||||||
|
0x04, 0x03, 0x90, 0x19, # id=3 x=1616 type=4 NetObj::Pickup
|
||||||
|
0x90, 0x04, 0x04, 0x04, # y=272 type=4 type=4 NetObj::Pickup
|
||||||
|
0x07, 0xb0, 0x15, 0x90, # id=7 x=1392 y=272
|
||||||
|
0x04, 0x01, 0x05, 0x00, # y=272 type=1 id=0 type=5 NetObj::Flag
|
||||||
|
0xb0, 0x12, 0xb0, 0x04, # x=1200 y=304
|
||||||
|
0x00, 0x05, 0x01, 0x90, # team=0 id=1 x=1296 type=5 NetObj::Flag
|
||||||
|
0x14, 0xb0, 0x04, 0x01, # x=1296 y=304 team=1
|
||||||
|
0x06, 0x00, 0x86, 0x14, # id=0 game_start_tick=1286 type=6 NetObj::GameData
|
||||||
|
0x00, 0x00, 0x07, 0x00, # game_state_flags=0 game_state_end_tick=0 id=0 type=7 NetObj::GameDataTeam
|
||||||
|
0x00, 0x00, 0x08, 0x00, # teamscore_red=0 teamscore_blue=0 id=0 type=8 NetObj::GameDataFlag
|
||||||
|
0x41, 0x41, 0x00, 0x00, # flag_carrier_red=-2 flag_carrier_blue=-2 flag_drop_tick_red=0 flag_drop_tick_blue=0
|
||||||
|
0x0a, 0x00, 0xa2, 0x14, # id=0 tick=1314 type=10 NetObj::Character
|
||||||
|
0x90, 0x0c, 0xb2, 0x04, # x=784 y=306
|
||||||
|
0x00, 0x80, 0x04, 0xc2, # vel_x=0 vel_y=256 angle=-707
|
||||||
|
0x0b, 0x00, 0x00, 0x40, # angle=-707 direction=0 jumped=0 hooked_player=-1
|
||||||
|
0x00, 0x00, 0x90, 0x0c, # hook_state=0 hook_tick=0 hook_x=784
|
||||||
|
0xb1, 0x04, 0x00, 0x00, # hook_y=305 hook_dx=0 hook_dy=0
|
||||||
|
0x00, 0x00, 0x00, 0x01, # health=0 armor=0 ammo_count=0 weapon=1
|
||||||
|
0x00, 0x00, 0x00, 0x0a, # emote=0 attack_tick=0 triggered_events=0 type=10 NetObj::Character
|
||||||
|
0x01, 0xa1, 0x14, 0x90, # id=1 tick=1313 x=848
|
||||||
|
0x0d, 0xb1, 0x04, 0x00, # x=848 y=305 vel_x=0
|
||||||
|
0x80, 0x02, 0x00, 0x00, # vel_y=128 angle=0 direction=0
|
||||||
|
0x00, 0x40, 0x00, 0x00, # jumped=0 hooked_player=-1 hook_state=0 hook_tick=0
|
||||||
|
0x90, 0x0d, 0xb0, 0x04, # hook_x=848 hook_y=304
|
||||||
|
0x00, 0x00, 0x0a, 0x00, # hook_dx=0 hook_dy=0 health=10 armor=0
|
||||||
|
0x0a, 0x01, 0x00, 0x00, # ammo_count=10 weapon=1 emote=0 attack_tick=0
|
||||||
|
0x00, 0x0b, 0x00, 0x08, # triggered_events=0 id=0 player_flags=8 type=11 NetObj::PlayerInfo
|
||||||
|
0x00, 0x00, 0x0b, 0x01, # score=0 latency=0 id=1 type=11 NetObj::PlayerInfo
|
||||||
|
0x08, 0x00, 0x00 # player_flags=8 score=0 latency=0
|
||||||
|
]
|
||||||
|
# snap.to_a.each_with_index do |s, i|
|
||||||
|
# p "[#{i}] got=#{s} want=#{expected_payload[i]}"
|
||||||
|
# expect(s).to eq(expected_payload[i])
|
||||||
|
# end
|
||||||
|
expect(snap.to_a).to eq(expected_payload)
|
||||||
|
# >>> snap NETMSG_SNAPSINGLE (8)
|
||||||
|
# id=8 game_tick=1420 delta_tick=1421
|
||||||
|
# num_parts=1 part=0 crc=20053 part_size=139
|
||||||
|
#
|
||||||
|
# header:
|
||||||
|
# 11 8c 16 8d .... int 17 >> 1 = 8 int 1420 int 1421
|
||||||
|
# 16 95 b9 02 .... int 1421 int 20053
|
||||||
|
# 8b 02 .. int 139
|
||||||
|
#
|
||||||
|
# payload:
|
||||||
|
# 00 0e 00 04 .... removed_items=0 num_item_deltas=14 _zero=0 type=4 NetObj::Pickup
|
||||||
|
# 00 90 16 90 .... id=0 x=1424 y=272
|
||||||
|
# 04 00 04 01 .... y=272 type=0 id=1 type=4 NetObj::Pickup
|
||||||
|
# 90 17 90 04 .... x=1488 y=272
|
||||||
|
# 02 04 02 90 .... type=2 id=2 x=1552 type=4 NetObj::Pickup
|
||||||
|
# 18 90 04 03 .... x=1552 y=272 type=3
|
||||||
|
# 04 03 90 19 .... id=3 x=1616 type=4 NetObj::Pickup
|
||||||
|
# 90 04 04 04 .... y=272 type=4 type=4 NetObj::Pickup
|
||||||
|
# 07 b0 15 90 .... id=7 x=1392 y=272
|
||||||
|
# 04 01 05 00 .... y=272 type=1 id=0 type=5 NetObj::Flag
|
||||||
|
# b0 12 b0 04 .... x=1200 y=304
|
||||||
|
# 00 05 01 90 .... team=0 id=1 x=1296 type=5 NetObj::Flag
|
||||||
|
# 14 b0 04 01 .... x=1296 y=304 team=1
|
||||||
|
# 06 00 86 14 .... id=0 game_start_tick=1286 type=6 NetObj::GameData
|
||||||
|
# 00 00 07 00 .... game_state_flags=0 game_state_end_tick=0 id=0 type=7 NetObj::GameDataTeam
|
||||||
|
# 00 00 08 00 .... teamscore_red=0 teamscore_blue=0 id=0 type=8 NetObj::GameDataFlag
|
||||||
|
# 41 41 00 00 AA.. flag_carrier_red=-2 flag_carrier_blue=-2 flag_drop_tick_red=0 flag_drop_tick_blue=0
|
||||||
|
# 0a 00 a2 14 .... id=0 tick=1314 type=10 NetObj::Character
|
||||||
|
# 90 0c b2 04 .... x=784 y=306
|
||||||
|
# 00 80 04 c2 .... vel_x=0 vel_y=256 angle=-707
|
||||||
|
# 0b 00 00 40 ...@ angle=-707 direction=0 jumped=0 hooked_player=-1
|
||||||
|
# 00 00 90 0c .... hook_state=0 hook_tick=0 hook_x=784
|
||||||
|
# b1 04 00 00 .... hook_y=305 hook_dx=0 hook_dy=0
|
||||||
|
# 00 00 00 01 .... health=0 armor=0 ammo_count=0 weapon=1
|
||||||
|
# 00 00 00 0a .... emote=0 attack_tick=0 triggered_events=0 type=10 NetObj::Character
|
||||||
|
# 01 a1 14 90 .... id=1 tick=1313 x=848
|
||||||
|
# 0d b1 04 00 .... x=848 y=305 vel_x=0
|
||||||
|
# 80 02 00 00 .... vel_y=128 angle=0 direction=0
|
||||||
|
# 00 40 00 00 .@.. jumped=0 hooked_player=-1 hook_state=0 hook_tick=0
|
||||||
|
# 90 0d b0 04 .... hook_x=848 hook_y=304
|
||||||
|
# 00 00 0a 00 .... hook_dx=0 hook_dy=0 health=10 armor=0
|
||||||
|
# 0a 01 00 00 .... ammo_count=10 weapon=1 emote=0 attack_tick=0
|
||||||
|
# 00 0b 00 08 .... triggered_events=0 id=0 player_flags=8 type=11 NetObj::PlayerInfo
|
||||||
|
# 00 00 0b 01 .... score=0 latency=0 id=1 type=11 NetObj::PlayerInfo
|
||||||
|
# 08 00 00 ... player_flags=8 score=0 latency=0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in a new issue