Class: ServerCore
- Inherits:
-
Object
- Object
- ServerCore
- Defined in:
- lib/server/chichilku3_server.rb
Overview
ServerCore: should only contain the networking and no gamelogic
Instance Method Summary collapse
- #add_player(name, version, client, ip) ⇒ Object
- #ban_client(client, seconds, message = 'banned') ⇒ Object
- #ban_ip(ip, seconds, message) ⇒ Object
- #client_by_playerid(player_id) ⇒ Object
-
#client_tick(cli, dt) ⇒ Object
Handles each client.
- #command_package(data, client) ⇒ Object
- #create_name_package(data, client) ⇒ Object
- #delete_player(id) ⇒ Object
- #disconnect_client(client, server_response = nil) ⇒ Object
- #handle_client_data(client, data, ip, dt) ⇒ Object
- #handle_protocol(client, protocol, p_status, data, ip, dt) ⇒ Object
- #id_pck(data, client, ip) ⇒ Object
-
#initialize ⇒ ServerCore
constructor
A new instance of ServerCore.
- #ip_banned?(ip) ⇒ Boolean
- #map_dl_chunk_pck(player) ⇒ Object
- #map_dl_init_pck ⇒ Object
- #map_info_pck ⇒ Object
- #next_free_id ⇒ Object
- #parse_client_version(data) ⇒ Object
- #players_to_packet ⇒ Object
- #run ⇒ Object
- #shutdown ⇒ Object
- #update_pck(data, dt) ⇒ Object
Constructor Details
#initialize ⇒ ServerCore
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, = 'banned') _port, ip = Socket.unpack_sockaddr_in(client.getpeername) ban_ip(ip, seconds, ) net_write("0l#{NET_ERR_BAN}#{}"[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, ) @bans[ip] = Time.now + seconds @console.log "IP=#{ip} banned for #{seconds} seconds (#{})" 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
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_pck ⇒ Object
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_pck ⇒ Object
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_id ⇒ Object
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_packet ⇒ Object
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 |
#run ⇒ Object
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 |
#shutdown ⇒ Object
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 |