class Client

Networking part of the game clientside

Attributes

id[R]
server_version[R]
state[R]

Public Class Methods

new(console, cfg) click to toggle source
# File lib/client/client.rb, line 15
def initialize(console, cfg)
  @id = nil
  @tick = 0
  @state = STATE_MENU
  @cfg = cfg

  @console = console
  @console.log "LOAD #{@s}"

  @s = nil # network socket (set in connect() method)

  @recording_ticks = []
  @recording_ticks_len = 0
  @recording_file = "autorec.txt"
  @is_recording = false

  @server_version = nil

  # state variables
  @req_playerlist = Time.now - 8

  # return values
  @players = []
  @flags = { skip: false, state: @state, gamestate: 'g',id: nil }
end

Public Instance Methods

connect(ip, port) click to toggle source
# File lib/client/client.rb, line 49
def connect(ip, port)
  reset
  @s = TCPSocket.open(ip, port)
  @s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) # nagle's algorithm-
  @state = STATE_CONNECTING
  start_recording() if @cfg.data['autorecord']
end
disconnect() click to toggle source
# File lib/client/client.rb, line 57
def disconnect()
  return if @state == STATE_MENU

  @console.log "disconnecting from server."
  @s.close
  @s = nil
  reset
end
load_recording(recording_file) click to toggle source
# File lib/client/client.rb, line 66
def load_recording(recording_file)
  recording_file = "#{@cfg.chichilku3_dir}recordings/#{recording_file}"
  @recording_ticks = []
  @tick = 0
  update_state(STATE_REC_PLAYBACK)
  File.readlines(recording_file).each do |data|
    @recording_ticks << data[0..-2] # cut off newline
  end
  # TODO: check if this .length lookup eats performance in ruby
  @recording_ticks_len = @recording_ticks.length
  @console.log "loaded recording containing #{@recording_ticks.size} ticks"
end
recording_playback_tick() click to toggle source
# File lib/client/client.rb, line 93
def recording_playback_tick()
  if @recording_ticks_len <= @tick
    @console.log "finished playing back recording"
    update_state(STATE_MENU)
    return [[], @flags, nil]
  end
  data = @recording_ticks[@tick]
  if data.length != SERVER_PACKAGE_LEN
    @console.err "failed to parse recording data=#{data.length} server=#{SERVER_PACKAGE_LEN}"
    return nil
  end

  @tick += 1
  @flags[:skip] = false

  # save protocol and cut it off
  msg = handle_protocol(data[0].to_i, data[1], data[2..-1])
  [@players, @flags, msg, [@tick, @recording_ticks_len]]
end
recording_record_tick(data) click to toggle source
# File lib/client/client.rb, line 86
def recording_record_tick(data)
  return unless @is_recording

  recording_file = "#{@cfg.chichilku3_dir}recordings/#{@recording_file}"
  IO.write(recording_file, data + "\n", mode: 'a')
end
reset() click to toggle source
# File lib/client/client.rb, line 41
def reset()
  @id = nil
  @tick = 0
  @state = STATE_MENU
  @players = []
  @flags = { skip: false, state: @state, gamestate: 'g',id: nil }
end
start_recording() click to toggle source
# File lib/client/client.rb, line 79
def start_recording()
  @recording_file = "autorec.txt"
  rec_file = "#{@cfg.chichilku3_dir}recordings/#{@recording_file}"
  @is_recording = true
  File.delete(rec_file) if File.exists? rec_file
end
tick(client_data, protocol, tick) click to toggle source
# File lib/client/client.rb, line 113
def tick(client_data, protocol, tick)
  return nil if @state == STATE_MENU

  # sleep(1)
  @tick = tick
  @flags[:skip] = false

  # send data to the server
  send_data(client_data, protocol)

  # get data from the server + implicit return
  data = fetch_server_data
  return nil if data.nil?

  recording_record_tick(data)

  # save protocol and cut it off
  msg = handle_protocol(data[0].to_i, data[1], data[2..-1])
  [@players, @flags, msg]
end

Private Instance Methods

fetch_server_data() click to toggle source
# File lib/client/client.rb, line 237
def fetch_server_data
  server_data = save_read(@s, 3)
  return nil if server_data == ''

  if server_data[0] == "1"
    len = net_unpack_int(server_data[2]) * PLAYER_PACKAGE_LEN
    server_data += save_read(@s, len+1)
  else
    server_data += save_read(@s, SERVER_PACKAGE_LEN-3)
  end
  return nil if server_data == ''

  @console.dbg "recived data: #{server_data}"
  server_data
end
get_server_version(data) click to toggle source
# File lib/client/client.rb, line 232
def get_server_version(data)
  @server_version = data[4..7]
  @console.log "server version='#{@server_version}'"
end
grab_id(data) click to toggle source
# File lib/client/client.rb, line 216
def grab_id(data)
  @console.log 'Trying to read id...'
  @playercount = net_unpack_int(data[0..1])
  id = data[2].to_i(16)
  set_id(id)
  update_state(STATE_INGAME) unless @state == STATE_REC_PLAYBACK
end
handle_protocol(protocol, p_status, data) click to toggle source
# File lib/client/client.rb, line 141
def handle_protocol(protocol, p_status, data)
  @console.dbg "HANDLE PROTOCOL=#{protocol} status=#{p_status}"
  if protocol == 0 # error packet
    code = data[0..2]
    error_msg = data[3..-1]
    if code == NET_ERR_FULL
      @console.log "server is full."
    elsif code == NET_ERR_DISCONNECT
      @console.log "disconnected by server."
    elsif code == NET_ERR_KICK
      @console.log "kicked by server."
    elsif code == NET_ERR_BAN
      @console.log "banned by server."
    elsif code == NET_ERR_SERVER_OUTDATED
      @console.log "failed to connect to server: server is outdated."
      @console.log error_msg
    elsif code == NET_ERR_CLIENT_OUTDATED
      @console.log "failed to connect to server: your client is outdated."
    else
      @console.log "ERROR unkown error code code=#{code} data=#{data}"
      return
    end
    @state = STATE_ERROR
    return [0, code, error_msg]
  elsif protocol == 1 # update package
    server_package_to_player_array(data)
  elsif protocol == 2 # id packet
    if @id.nil?
      id_packet(data)
    else
      @console.log "WARNING got unexpected id packet=#{data}"
    end
  elsif protocol == 3 # name packet
    protocol_names(data)
  elsif protocol == 4 # command respond
    @console.log "server respond: #{data}"
    return [1, data]
  else
    @console.log "ERROR unkown protocol=#{protocol} data=#{data}"
  end
  nil
end
id_packet(data) click to toggle source
# File lib/client/client.rb, line 224
def id_packet(data)
  # protocol 2
  # the id protocol contains fresh client id
  # and server version
  grab_id(data)
  get_server_version(data)
end
net_write(data) click to toggle source
# File lib/client/client.rb, line 253
def net_write(data)
  if data.length != CLIENT_PACKAGE_LEN
    @console.log "ERROR wrong pack len: #{data.length}/#{CLIENT_PACKAGE_LEN} pck: #{data}"
    exit
  end
  @s.write(data)
  @console.dbg "sent: #{data}"
end
player_strs_to_objects(player_strs) click to toggle source
# File lib/client/client.rb, line 323
def player_strs_to_objects(player_strs)
  players = []
  player_strs.each do |player_str|
    id = player_str[0].to_i(16)
    score = net_unpack_int(player_str[1])
    net_state = player_str[2]
    projR = player_str[3]
    projX = net_unpack_bigint(player_str[4..5])
    projY = net_unpack_bigint(player_str[6..7])
    aimX = net_unpack_bigint(player_str[8..9])
    aimY = net_unpack_bigint(player_str[10..11])
    x = net_unpack_bigint(player_str[12..13])
    y = net_unpack_bigint(player_str[14..15])
    # puts "'#{player_str}' id: #{id} x: #{x} '#{player_str[12..13]}' y: #{y} '#{player_str[14..15]}'"
    # players << Player.new(id, x, y) unless id.zero?

    @console.dbg "-- updt player id=#{id} score=#{score}--"
    p_index = Player.get_player_index_by_id(@players, id)
    @console.dbg "@players index=#{p_index}"
    @console.dbg "players: \n #{@players}"
    next if p_index.nil?

    @console.dbg "got player: #{@players[p_index]}"
    new_player = Player.update_player(@players, id, x, y, score, aimX, aimY)
    new_player.projectile.r = projR
    new_player.projectile.x = projX
    new_player.projectile.y = projY
    new_player.net_to_state(net_state)
    @players[Player.get_player_index_by_id(@players, id)] = new_player
  end
  # debug
  players.each { |p| @console.dbg "player=#{p.id} pos=#{p.x}/#{p.y}" }
  players
end
protocol_names(data) click to toggle source

playername package And its dependencies:

# File lib/client/client.rb, line 268
def protocol_names(data)
  #     3 0          00 00000 00 00000 00 00000 000
  playercount = net_unpack_int(data[0])
  @flags[:gamestate] = data[1]
  data = data[2..-1]
  p_strs = protocol_names_to_player_strs(playercount, data)
  protocol_names_strs_to_objs(p_strs)
end
protocol_names_strs_to_objs(player_strs) click to toggle source
# File lib/client/client.rb, line 286
def protocol_names_strs_to_objs(player_strs)
  players = []
  player_strs.each do |player_str|
    id = player_str[0].to_i(16)
    score = net_unpack_int(player_str[1])
    name = player_str[2..-1]
    players << Player.new(id, 0, 0, score, name) unless id.zero?
  end
  # debug
  players.each { |p| @console.dbg "player=#{p.id} score=#{p.score} name='#{p.name}'" }
  @flags[:skip] = true # dont redner players at position zer0
  @players = players
end
protocol_names_to_player_strs(used_slots, data) click to toggle source
# File lib/client/client.rb, line 277
def protocol_names_to_player_strs(used_slots, data)
  players = []
  used_slots.times do |index|
    size = NAME_LEN + 2 # id|score|name
    players[index] = data[index * size..index * size + size-1]
  end
  players
end
send_data(data, protocol) click to toggle source
# File lib/client/client.rb, line 184
def send_data(data, protocol)
  if @id.nil?
    # request id has priority
    # resend after 100 ticks if no respond
    net_write("1l#{GAME_VERSION}XXXXX") if (@tick % 200).zero?
    return
  end

  # if no playerlist yet -> request one
  if @players == [] && @req_playerlist < Time.now - 4
    name = @cfg.data['username'].ljust(NAME_LEN, ' ')
    net_write("3l#{name}")
    @console.log('requesting a playerlist')
    @req_playerlist = Time.now
    return
  end

  data = "#{protocol}l#{@id.to_s(16)}#{data.join('')}"
  net_write(data)
end
server_package_to_player_array(data) click to toggle source

server_package_to_player_array And its dependencies:

# File lib/client/client.rb, line 302
def server_package_to_player_array(data)
  # /(?<count>\d{2})(?<player>(?<id>\d{2})(?<x>\d{3})(?<y>\d{3}))/
  # @console.log "data: #{data}"
  used_slots = net_unpack_int(data[0]) # save occupado slots
  # gamestate = data[1].to_i # save gamestate
  @flags[:gamestate] = data[1]
  # @console.log "gamestate: " + @flags[:gamestate]
  data = data[2..-1] # cut slots and gamestate off
  players = server_package_to_player_strs(used_slots, data)
  # @console.log "players: \n#{players}"
  player_strs_to_objects(players)
end
server_package_to_player_strs(used_slots, data) click to toggle source
# File lib/client/client.rb, line 315
def server_package_to_player_strs(used_slots, data)
  players = []
  used_slots.times do |index|
    players[index] = data[index * PLAYER_PACKAGE_LEN..index * PLAYER_PACKAGE_LEN + PLAYER_PACKAGE_LEN-1]
  end
  players
end
set_id(id) click to toggle source
# File lib/client/client.rb, line 205
def set_id(id)
  if id > MAX_CLIENTS || id < 1
    @console.log "Errornous id=#{id}"
    return false
  end
  @id = id
  @console.log "Set ID=#{@id}"
  @flags[:id] = @id
  true
end
update_state(state) click to toggle source
# File lib/client/client.rb, line 136
def update_state(state)
  @flags[:state] = state
  @state = state
end