Class: ClientConnection

Inherits:
Object
  • Object
show all
Includes:
Base64, Encryption
Defined in:
lib/game_2d/client_connection.rb

Overview

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

Constant Summary collapse

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

6

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Encryption

#decrypt, #encrypt, #key=, #make_cipher, #make_password_hash

Constructor Details

#initialize(host, port, game, player_name, key_size, timeout = 2000) ⇒ ClientConnection

1/10 of a second



23
24
25
26
27
28
29
30
31
32
33
34
# 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

Instance Attribute Details

#engineObject

Returns the value of attribute engine.



16
17
18
# File 'lib/game_2d/client_connection.rb', line 16

def engine
  @engine
end

#player_nameObject (readonly)

Returns the value of attribute player_name.



15
16
17
# File 'lib/game_2d/client_connection.rb', line 15

def player_name
  @player_name
end

Instance Method Details

#_create_connection(*args) ⇒ Object



50
51
52
# File 'lib/game_2d/client_connection.rb', line 50

def _create_connection(*args)
  ENet::Connection.new(*args)
end

#debug_packet(direction, hash) ⇒ Object



176
177
178
179
180
181
# 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

#disconnectObject



188
# File 'lib/game_2d/client_connection.rb', line 188

def disconnect; @socket.disconnect(200) if online?; end

#login(server_public_key) ⇒ Object



63
64
65
66
67
68
69
70
71
# File 'lib/game_2d/client_connection.rb', line 63

def (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_closeObject



73
74
75
76
# File 'lib/game_2d/client_connection.rb', line 73

def on_close
  puts "Client disconnected by server"
  @game.shutdown
end

#on_connectObject



54
55
56
57
58
59
60
61
# 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) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 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)
    (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?Boolean

Returns:

  • (Boolean)


187
# File 'lib/game_2d/client_connection.rb', line 187

def online?; @socket.online?; end

#send_actions_atObject



119
120
121
# File 'lib/game_2d/client_connection.rb', line 119

def send_actions_at
  @engine.tick + ACTION_DELAY
end

#send_create_npc(npc) ⇒ Object



132
133
134
135
136
137
138
# 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) ⇒ Object



147
148
149
150
151
152
# 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 = {}) ⇒ Object



123
124
125
126
127
128
129
130
# 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_pingObject



165
166
167
# 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) ⇒ Object



169
170
171
172
173
174
# 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_saveObject



161
162
163
# File 'lib/game_2d/client_connection.rb', line 161

def send_save
  send_record :save => true
end

#send_snap_to_grid(entity) ⇒ Object



154
155
156
157
158
159
# 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) ⇒ Object



140
141
142
143
144
145
# 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) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
# 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

#updateObject



183
184
185
# File 'lib/game_2d/client_connection.rb', line 183

def update
  @socket.update(0) # non-blocking
end