class ClientConnection

The client creates one of these. It is then used for all communication with the server.

Constants

ACTION_DELAY

We tell the server to execute all actions this many ticks in the future, to give the message time to propagate around the fleet

Attributes

engine[RW]
player_name[R]

Public Class Methods

new(host, port, game, player_name, key_size, timeout=2000) click to toggle source
# File lib/game_2d/client_connection.rb, line 23
def initialize(host, port, game, player_name, key_size, timeout=2000)
  # remote host address, remote host port, channels, download bandwidth, upload bandwidth
  @socket = _create_connection(host, port, 2, 0, 0)
  @host, @port, @game, @player_name, @key_size, @timeout =
   host,  port,  game,  player_name,  key_size,  timeout

  @socket.on_connection(method(:on_connect))
  @socket.on_disconnection(method(:on_close))
  @socket.on_packet_receive(method(:on_packet))

  @dh = @password_hash = nil
end

Public Instance Methods

_create_connection(*args) click to toggle source
# File lib/game_2d/client_connection.rb, line 50
def _create_connection(*args)
  ENet::Connection.new(*args)
end
debug_packet(direction, hash) click to toggle source
# File lib/game_2d/client_connection.rb, line 176
def debug_packet(direction, hash)
  return unless $debug_traffic
  at_tick = hash[:at_tick] || 'NO TICK'
  keys = hash.keys - [:at_tick]
  puts "#{direction} #{keys.join(', ')} <#{at_tick}>"
end
disconnect() click to toggle source
# File lib/game_2d/client_connection.rb, line 188
def disconnect; @socket.disconnect(200) if online?; end
login(server_public_key) click to toggle source
# File lib/game_2d/client_connection.rb, line 63
def login(server_public_key)
  self.key = @dh.compute_key(OpenSSL::BN.new server_public_key)
  data, iv = encrypt(@password_hash)
  @password_hash = nil
  send_record(
    :password_hash => strict_encode64(data),
    :iv => strict_encode64(iv)
  )
end
on_close() click to toggle source
# File lib/game_2d/client_connection.rb, line 73
def on_close
  puts "Client disconnected by server"
  @game.shutdown
end
on_connect() click to toggle source
# File lib/game_2d/client_connection.rb, line 54
def on_connect
  @game.display_message "Connected, logging in"
  send_record( { :handshake => {
    :player_name => @player_name,
    :dh_public_key => @dh.public_key.to_pem,
    :client_public_key => @dh.pub_key.to_s
  } }, true) # send handshake reliably
end
on_packet(data, channel) click to toggle source
# File lib/game_2d/client_connection.rb, line 78
def on_packet(data, channel)
  hash = JSON.parse(data).fix_keys
  debug_packet('Received', hash)

  if pong = hash.delete(:pong)
    stop = Time.now.to_f
    puts "Ping took #{stop - pong[:start]} seconds"
  end

  if server_public_key = hash.delete(:server_public_key)
    login(server_public_key)
    return
  end

  # Leave :at_tick intact; add_delta will reuse it
  fail "No at_tick in #{hash.inspect}" unless at_tick = hash[:at_tick]

  if world = hash.delete(:world)
    @game.clear_message
    @engine.establish_world(world, at_tick)
  end

  you_are = hash.delete :you_are
  registry, highest_id = hash.delete(:registry), hash.delete(:highest_id)

  delta_keys = [
    :add_players, :add_npcs, :delete_entities, :update_entities, :update_score, :move
  ]
  @engine.add_delta(hash) if delta_keys.any? {|k| hash.has_key? k}

  if you_are
    # The 'world' response includes deltas for add_players and add_npcs
    # Need to process those first, as one of the players is us
    @engine.apply_deltas(at_tick) if world

    @game.player_id = you_are
  end

  @engine.sync_registry(registry, highest_id, at_tick) if registry
end
online?() click to toggle source
# File lib/game_2d/client_connection.rb, line 187
def online?; @socket.online?; end
send_actions_at() click to toggle source
# File lib/game_2d/client_connection.rb, line 119
def send_actions_at
  @engine.tick + ACTION_DELAY
end
send_create_npc(npc) click to toggle source
# File lib/game_2d/client_connection.rb, line 132
def send_create_npc(npc)
  return unless online?
  # :on_* hooks are for our own use; we don't send them
  remote_npc = npc.reject {|k,v| k.to_s.start_with? 'on_'}
  send_record :at_tick => send_actions_at, :add_npcs => [ remote_npc ]
  @engine.add_delta :at_tick => send_actions_at, :add_npcs => [ npc ]
end
send_delete_entity(entity) click to toggle source
# File lib/game_2d/client_connection.rb, line 147
def send_delete_entity(entity)
  return unless online?
  delta = { :delete_entities => [entity.registry_id], :at_tick => send_actions_at }
  send_record delta
  @engine.add_delta delta
end
send_move(player_id, move, args={}) click to toggle source
# File lib/game_2d/client_connection.rb, line 123
def send_move(player_id, move, args={})
  return unless move && online?
  args[:move] = move.to_s
  delta = { :at_tick => send_actions_at, :move => args }
  send_record delta
  delta[:player_id] = player_id
  @engine.add_delta delta
end
send_ping() click to toggle source
# File lib/game_2d/client_connection.rb, line 165
def send_ping
  send_record :ping => { :start => Time.now.to_f }
end
send_record(data, reliable=false) click to toggle source
# File lib/game_2d/client_connection.rb, line 169
def send_record(data, reliable=false)
  return unless online?
  debug_packet('Sending', data)
  @socket.send_packet(data.to_json, reliable, 0)
  @socket.flush
end
send_save() click to toggle source
# File lib/game_2d/client_connection.rb, line 161
def send_save
  send_record :save => true
end
send_snap_to_grid(entity) click to toggle source
# File lib/game_2d/client_connection.rb, line 154
def send_snap_to_grid(entity)
  return unless online? && entity
  delta = { :at_tick => send_actions_at, :snap_to_grid => entity.registry_id }
  send_record delta
  @engine.add_delta delta
end
send_update_entity(entity) click to toggle source
# File lib/game_2d/client_connection.rb, line 140
def send_update_entity(entity)
  return unless online?
  delta = { :update_entities => [entity], :at_tick => send_actions_at }
  send_record delta
  @engine.add_delta delta
end
start(password_hash) click to toggle source
# File lib/game_2d/client_connection.rb, line 36
def start(password_hash)
  @password_hash = password_hash
  Thread.new do
    @game.display_message! "Establishing encryption (#{@key_size}-bit)..."
    @dh = OpenSSL::PKey::DH.new(@key_size)

    # Connect to server and kick off handshaking
    # We will create our player object only after we've been accepted by the server
    # and told our starting position
    @game.display_message! "Connecting to #{@host}:#{@port} as #{@player_name}"
    @socket.connect(@timeout)
  end
end
update() click to toggle source
# File lib/game_2d/client_connection.rb, line 183
def update
  @socket.update(0) # non-blocking
end