Class: ServerCore

Inherits:
Object
  • Object
show all
Defined in:
lib/server/chichilku3_server.rb

Overview

ServerCore: should only contain the networking and no gamelogic

Instance Method Summary collapse

Constructor Details

#initializeServerCore

Returns a new instance of ServerCore.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/server/chichilku3_server.rb', line 21

def initialize
  # single dimension array holding player objects
  @players = []
  # multi dimensional array
  # 0 - client network socket
  # 1 - player id
  @clients = []
  @current_id = 0
  @bans = {}
  @tick = 0
  @last_alive_pck_by_client = Time.now
  @console = Console.new
  @cfg = ServerCfg.new(@console, 'server.json')
  @gamelogic = GameLogic.new(@console)

  @cfg.data['map'] = 'battle' if @cfg.data['map'] == ''
  @game_map = GameMap.new(@console, @cfg, @cfg.data['map'])
  @game_map.prepare_upload
end

Instance Method Details

#add_player(name, version, client, ip) ⇒ Object



89
90
91
92
93
94
95
96
97
# File 'lib/server/chichilku3_server.rb', line 89

def add_player(name, version, client, ip)
  @current_id = next_free_id
  return -1 if @current_id > MAX_CLIENTS || @current_id < 1

  @console.dbg "[NEW PLAYER] IP='#{ip}' ID='#{@current_id}' version='#{version}'"
  @players << Player.new(@current_id, 0, nil, nil, name, version, ip)
  client[PLAYER_ID] = @current_id
  @current_id # implicit return
end

#ban_client(client, seconds, message = 'banned') ⇒ Object



338
339
340
341
342
343
# File 'lib/server/chichilku3_server.rb', line 338

def ban_client(client, seconds, message = 'banned')
  _port, ip = Socket.unpack_sockaddr_in(client.getpeername)
  ban_ip(ip, seconds, message)
  net_write("0l#{NET_ERR_BAN}#{message}"[0..SERVER_PACKAGE_LEN].ljust(SERVER_PACKAGE_LEN, ' '), client)
  client.close
end

#ban_ip(ip, seconds, message) ⇒ Object



345
346
347
348
# File 'lib/server/chichilku3_server.rb', line 345

def ban_ip(ip, seconds, message)
  @bans[ip] = Time.now + seconds
  @console.log "IP=#{ip} banned for #{seconds} seconds (#{message})"
end

#client_by_playerid(player_id) ⇒ Object



294
295
296
297
298
# File 'lib/server/chichilku3_server.rb', line 294

def client_by_playerid(player_id)
  @clients.find do |client|
    client[PLAYER_ID] == player_id
  end
end

#client_tick(cli, dt) ⇒ Object

Handles each client



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/server/chichilku3_server.rb', line 262

def client_tick(cli, dt)
  client_data = save_read(cli[NET_CLIENT], CLIENT_PACKAGE_LEN)
  if client_data == ''
    # diff = Time.now - @last_alive_pck_by_client
    # if (diff > MAX_TIMEOUT)
    #   @console.log "sombody timed out"
    # end
    return
  end

  @last_alive_pck_by_client = Time.now
  _port, ip = Socket.unpack_sockaddr_in(cli[NET_CLIENT].getpeername)
  server_response = handle_client_data(cli, client_data, ip, dt)
  pck_type = server_response[0]
  if pck_type == SERVER_PCK_TYPE[:error]
    disconnect_client(cli, server_response)
  else
    net_write(server_response, cli[NET_CLIENT])
  end
end

#command_package(data, client) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/server/chichilku3_server.rb', line 163

def command_package(data, client)
  id = data[0..1].to_i(16)
  cmd = data[1..-1].strip
  @console.log "[chat] ID=#{id} command='#{cmd}'"
  msg = "server_recived_cmd: #{cmd}"
  msg = msg.ljust(SERVER_PACKAGE_LEN - 2, '0')
  msg = msg[0..SERVER_PACKAGE_LEN - CMD_LEN]
  case cmd
  when 'test'
    # return "0l#{NET_ERR_DISCONNECT}    SAMPLE MESSAGE     "
    msg = "id=#{client[PLAYER_ID]}"
  when 'exit'
    msg = 'ok'
    shutdown
  end
  msg = msg.ljust(SERVER_PACKAGE_LEN - 2, ' ')
  msg = msg[0..SERVER_PACKAGE_LEN - 2]
  # protocol 4 (chat command)
  "4l#{msg}"
end

#create_name_package(data, client) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/server/chichilku3_server.rb', line 56

def create_name_package(data, client)
  if !client.nil? && !data.nil?
    player = Player.get_player_by_id(@players, client[PLAYER_ID])
    if player.nil?
      port, ip = Socket.unpack_sockaddr_in(client[NET_CLIENT].getpeername)
      @console.wrn "IP=#{ip}:#{port} tried to get a name pack (without player)"
      return
    end
    player.name = data.strip.gsub(/[^a-zA-Z0-9_]/, '_')
    @gamelogic.on_player_connect(client, @players)
  end

  # protocol 3 name prot
  #                                   gamestate
  #                                       |
  pck = "3l#{net_pack_int(@players.count)}g"
  @players.each do |p|
    pck += p.to_n_pck
  end
  pck.ljust(SERVER_PACKAGE_LEN, '0')
end

#delete_player(id) ⇒ Object



99
100
101
# File 'lib/server/chichilku3_server.rb', line 99

def delete_player(id)
  @players.delete(Player.get_player_by_id(@players, id))
end

#disconnect_client(client, server_response = nil) ⇒ Object



283
284
285
286
287
288
289
290
291
292
# File 'lib/server/chichilku3_server.rb', line 283

def disconnect_client(client, server_response = nil)
  player_id = client[PLAYER_ID]
  @gamelogic.on_player_disconnect(client, @players)
  @console.dbg "client disconnected.#{" (#{server_response})" unless server_response.nil?}"
  net_write(server_response, client[NET_CLIENT]) unless server_response.nil?
  client[NET_CLIENT].close
  delete_player(player_id)
  @clients.delete(client)
  @current_id -= 1
end

#handle_client_data(client, data, ip, dt) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/server/chichilku3_server.rb', line 245

def handle_client_data(client, data, ip, dt)
  response = handle_protocol(client, data[0].to_i, data[1], data[2..-1], ip, dt)
  # the response is a direct respond to an protocol
  # everything above this could override important responds
  # like id assignment
  # everything that is after this guard case just overrides update pcks
  return response unless response.nil?

  return create_name_package(nil, nil) if (@tick % 100).zero?

  # if error occurs or something unexpected
  # just send regular update pck
  # protocol 1 (update)
  "1l#{players_to_packet}"
end

#handle_protocol(client, protocol, p_status, data, ip, dt) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/server/chichilku3_server.rb', line 184

def handle_protocol(client, protocol, p_status, data, ip, dt)
  @console.dbg "[PROTOCOL] protocol=#{protocol} status=#{p_status}"
  if protocol.zero? # error pck
    @console.err "Error pck=#{data}"
  elsif protocol == 1 # id pck
    id_pck(data, client, ip)
  elsif protocol == 3 # initial request names
    create_name_package(data, client)
  else
    if data.nil?
      @console.err "IP=#{ip} invalid data"
      return
    end
    # all other types require id
    id = data[0].to_i(16)
    if id != client[PLAYER_ID]
      @console.wrn "id=#{client[PLAYER_ID]} tried to spoof id=#{id} ip=#{ip}"
      @console.wrn data
      disconnect_client(client, "0l#{NET_ERR_DISCONNECT}invalid player id                              ")
      return nil
    end
    case protocol
    when 2 # update pck
      return update_pck(data, dt) if @game_map.nil?

      player = Player.get_player_by_id(@players, id)
      if player.map_download == -2
        player.map_download = -1
        map_info_pck
      elsif player.map_download == -1
        # set state to -3 which stops sending
        # any further information
        # wait for the client to respond
        player.map_download = -3
        map_dl_init_pck
      elsif player.map_download < @game_map.size && player.map_download >= 0
        map_dl_chunk_pck(player)
      else
        update_pck(data, dt)
      end
    when 4 # command
      command_package(data, client)
    when 5 # map info response
      return update_pck(data, dt) if @game_map.nil?

      player = Player.get_player_by_id(@players, id)
      if data[1] == '1'
        player.map_download = 0
        @console.log 'player started map download'
        map_dl_chunk_pck(player)
      else
        @console.log 'player rejected map download'
        player.map_download = @game_map.size
        update_pck(data, dt)
      end
    else
      @console.err "IP=#{ip} unkown protocol=#{protocol} data=#{data}"
    end
  end
end

#id_pck(data, client, ip) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/server/chichilku3_server.rb', line 137

def id_pck(data, client, ip)
  if num_ip_connected(ip) > @cfg.data['max_clients_per_ip']
    disconnect_client(client, "0l#{NET_ERR_DISCONNECT}too many clients per ip                        ")
    return
  end
  player_version = data[0..3]
  id = add_player('(connecting)', player_version, client, ip)
  if id == -1
    @console.log "IP='#{ip}' failed to connect (server full)"
    # protocol 0 (error) code=404 slot not found
    return "0l#{NET_ERR_FULL}                       "
  end
  if player_version.to_i < GAME_VERSION.to_i
    @console.log "IP='#{ip}' failed to connect (client too old '#{player_version}' < '#{GAME_VERSION}')"
    return "0l#{NET_ERR_CLIENT_OUTDATED}#{GAME_VERSION}".ljust(SERVER_PACKAGE_LEN, ' ')
  elsif player_version.to_i > GAME_VERSION.to_i
    @console.log "IP='#{ip}' failed to connect (client too new '#{player_version}' < '#{GAME_VERSION}')"
    return "0l#{NET_ERR_SERVER_OUTDATED}#{GAME_VERSION}".ljust(SERVER_PACKAGE_LEN, ' ')
  end
  @console.dbg "[ID-PACKAGE] ID='#{id}' IP='#{ip}'"
  # protocol 2 (id)
  "2l#{net_pack_int(@players.count)}#{net_pack_int(MAX_CLIENTS)}#{id.to_s(16)}X#{GAME_VERSION}".ljust(
    SERVER_PACKAGE_LEN, '0'
  )
end

#ip_banned?(ip) ⇒ Boolean

Returns:

  • (Boolean)


350
351
352
353
354
# File 'lib/server/chichilku3_server.rb', line 350

def ip_banned?(ip)
  return false if @bans[ip].nil?

  (@bans[ip] - Time.now).positive?
end

#map_dl_chunk_pck(player) ⇒ Object



130
131
132
133
134
135
# File 'lib/server/chichilku3_server.rb', line 130

def map_dl_chunk_pck(player)
  size = SERVER_PACKAGE_LEN - 2
  map_chunk = @game_map.get_data(player.map_download, size)
  player.map_download += size
  "7l#{map_chunk}"
end

#map_dl_init_pckObject



124
125
126
127
128
# File 'lib/server/chichilku3_server.rb', line 124

def map_dl_init_pck
  size = net_pack_bigint(@game_map.size, 6)
  name = @game_map.name
  "6l#{size}#{name}".ljust(SERVER_PACKAGE_LEN, ' ')
end

#map_info_pckObject



120
121
122
# File 'lib/server/chichilku3_server.rb', line 120

def map_info_pck
  "5l#{@game_map.checksum}".ljust(SERVER_PACKAGE_LEN, ' ')
end

#next_free_idObject



78
79
80
81
82
83
84
85
86
87
# File 'lib/server/chichilku3_server.rb', line 78

def next_free_id
  # TODO: do this smarter
  used_ids = @clients.map { |c| c[1] }
  id = 0
  while id < MAX_CLIENTS
    id += 1
    return id unless used_ids.include? id
  end
  -1
end

#parse_client_version(data) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/server/chichilku3_server.rb', line 41

def parse_client_version(data)
  return if data.nil?

  id = data[0].to_i(16)
  version = data[1..4]
  player = Player.get_player_by_id(@players, id)
  if player
    @console.dbg "[NAME-REQUEST] ID='#{id}' version='#{version}' name='#{player.name}'"
    player.set_version(version)
  else
    @console.err "failed to parse version data=#{data}"
  end
  player
end

#players_to_packetObject



103
104
105
106
107
108
109
110
111
# File 'lib/server/chichilku3_server.rb', line 103

def players_to_packet
  # player count
  packet = net_pack_int(@players.empty? ? 0 : @players.count)
  packet += 'g' # gamestate
  @players.each do |player|
    packet += player.to_s
  end
  packet
end

#runObject



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/server/chichilku3_server.rb', line 308

def run
  server = TCPServer.open(@cfg.data['port'])
  server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) # nagle's algorithm
  Thread.new do
    accept(server)
  end
  loop do
    diff = 0 # TODO: unused lmao traced it through the half source
    t = Time.now
    if $next_tick > t
      sleep $next_tick - t
    else
      @console.wrn "tick took #{t - $next_tick} too long" unless @tick.zero?
    end
    $next_tick = Time.now + MAX_TICK_SPEED
    @tick += 1
    @players = @gamelogic.tick(@game_map, @players, diff, @tick)
    # there is no gurantee the client will tick here
    # there might be 2 gamelogic ticks and posticks
    # before the server recieves client data
    # since it is a nonblocking read and server/client are not in perfect sync
    @clients.each do |client|
      client_tick(client, diff)
    rescue Errno::ECONNRESET, Errno::ENOTCONN, IOError
      disconnect_client(client)
    end
    @players = @gamelogic.posttick(@game_map, @players, diff)
  end
end

#shutdownObject



300
301
302
303
304
305
306
# File 'lib/server/chichilku3_server.rb', line 300

def shutdown
  @clients.each do |client|
    disconnect_client(client)
  end
  @console.log 'server shutdown.'
  exit
end

#update_pck(data, dt) ⇒ Object



113
114
115
116
117
118
# File 'lib/server/chichilku3_server.rb', line 113

def update_pck(data, dt)
  id = data[0].to_i(16)
  @console.dbg "[UPDATE] got player with id: #{id}"
  @players = @gamelogic.handle_client_requests(@game_map, data[1..-1], id, @players, dt)
  nil # defaults to normal update pck
end