chichilku3 14.0.2 → 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/chichilku3 +1 -0
- data/bin/chichilku3-server +1 -0
- data/lib/client/chichilku3.rb +3 -1
- data/lib/client/client.rb +112 -59
- data/lib/client/client_cfg.rb +2 -1
- data/lib/client/gui.rb +219 -197
- data/lib/client/img/stick128/arm64/arm0.png +0 -0
- data/lib/client/img/stick128/arm64/arm1.png +0 -0
- data/lib/client/img/stick128/arm64/arm2.png +0 -0
- data/lib/client/img/stick128/arm64/arm3.png +0 -0
- data/lib/client/img/stick128/noarms/stick0.png +0 -0
- data/lib/client/img/stick128/noarms/stick1.png +0 -0
- data/lib/client/img/stick128/noarms/stick2.png +0 -0
- data/lib/client/img/stick128/noarms/stick3.png +0 -0
- data/lib/client/img/stick128/noarms/stick4.png +0 -0
- data/lib/client/img/stick128/noarms/stick5.png +0 -0
- data/lib/client/keys.rb +29 -0
- data/lib/client/particles.rb +54 -0
- data/lib/client/scoreboard.rb +29 -27
- data/lib/server/chichilku3_server.rb +169 -64
- data/lib/server/gamelogic.rb +107 -51
- data/lib/server/server_cfg.rb +2 -0
- data/lib/share/config.rb +21 -7
- data/lib/share/console.rb +22 -5
- data/lib/share/game_map.rb +279 -0
- data/lib/share/math.rb +14 -0
- data/lib/share/network.rb +47 -42
- data/lib/share/player.rb +168 -105
- data/lib/share/projectile.rb +97 -98
- data/lib/share/string.rb +24 -0
- data/server.json +3 -2
- metadata +44 -20
- data/lib/client/img/battle1024x576.png +0 -0
- data/lib/client/img/grass1024x512.png +0 -0
- data/lib/client/img/stick128/stick_noarms.png +0 -0
- data/lib/client/test.rb +0 -39
- data/lib/client/text.rb +0 -86
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/client/keys.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
KEY_A = 4
|
4
|
+
KEY_B = 5
|
5
|
+
KEY_C = 6
|
6
|
+
KEY_D = 7
|
7
|
+
KEY_E = 8
|
8
|
+
KEY_F = 9
|
9
|
+
KEY_G = 10
|
10
|
+
KEY_H = 11
|
11
|
+
KEY_I = 12
|
12
|
+
KEY_J = 13
|
13
|
+
KEY_K = 14
|
14
|
+
KEY_L = 15
|
15
|
+
KEY_M = 16
|
16
|
+
KEY_N = 17
|
17
|
+
KEY_O = 18
|
18
|
+
KEY_P = 19
|
19
|
+
KEY_Q = 20
|
20
|
+
KEY_R = 21
|
21
|
+
KEY_S = 22
|
22
|
+
KEY_T = 23
|
23
|
+
KEY_U = 24
|
24
|
+
KEY_V = 25
|
25
|
+
KEY_W = 26
|
26
|
+
KEY_RIGHT = 79
|
27
|
+
KEY_LEFT = 80
|
28
|
+
KEY_DOWN = 81
|
29
|
+
KEY_UP = 82
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Renderer for moving textures like grass
|
4
|
+
class Particles
|
5
|
+
def initialize(console)
|
6
|
+
@console = console
|
7
|
+
@grass_img = Gosu::Image.new(img('grass/single_64.png'))
|
8
|
+
end
|
9
|
+
|
10
|
+
def draw(game_map, players)
|
11
|
+
# y = MAP_HEIGHT * TILE_SIZE - TILE_SIZE * 3
|
12
|
+
# y += TILE_SIZE / 2
|
13
|
+
# y += 2
|
14
|
+
# grass(0, TILE_SIZE * 5, y, move_x, move_y)
|
15
|
+
|
16
|
+
game_map.grass_rows.each do |grass_row|
|
17
|
+
grass(grass_row[:x1], grass_row[:x2], grass_row[:y], players)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def img(path)
|
24
|
+
File.join(File.dirname(__FILE__), '../../lib/client/img/', path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def grass(x1, x2, y, players, density = 3)
|
28
|
+
# @console.log(x1.to_s + " " + x2.to_s + " " + y.to_s + " " + move_x.to_s + " " + move_y.to_s)
|
29
|
+
srand(0)
|
30
|
+
x = x1
|
31
|
+
amount = (x2 - x1) / density
|
32
|
+
amount.times do
|
33
|
+
x += density
|
34
|
+
rot = rand(-8..8)
|
35
|
+
move = false
|
36
|
+
players.each do |player|
|
37
|
+
next unless player.x > x - 10 && player.x < x + 10 && player.y > y - 128 && player.y < y + 128
|
38
|
+
|
39
|
+
move = true
|
40
|
+
break
|
41
|
+
end
|
42
|
+
if move
|
43
|
+
if rot.positive?
|
44
|
+
rot += 2
|
45
|
+
else
|
46
|
+
rot -= 2
|
47
|
+
end
|
48
|
+
@grass_img.draw_rot(x + rand(-3..3), y + rand(0..3) + 1, 0, rot)
|
49
|
+
else
|
50
|
+
@grass_img.draw_rot(x + rand(-3..3), y + rand(0..3), 0, rot)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/client/scoreboard.rb
CHANGED
@@ -1,31 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
def draw_scoreboard(win_size_x, win_size_y, players, font, debug)
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
font.draw_text(player.name, dbg + padX + 5, padY + (i * slot_height), 0, text_scale, text_scale)
|
29
|
-
font.draw_text(player.score, dbg + padX + sizeX - score_offset, padY + (i * slot_height), 0, text_scale, text_scale)
|
4
|
+
# TODO: do not compute those every frame
|
5
|
+
pad_x = win_size_x / 3
|
6
|
+
size_x = win_size_x / 3
|
7
|
+
pad_y = win_size_y / 6
|
8
|
+
size_y = win_size_y / 3
|
9
|
+
slot_height = size_y / MAX_CLIENTS
|
10
|
+
text_scale = slot_height / 15
|
11
|
+
# background
|
12
|
+
draw_rect(pad_x, pad_y, size_x, size_y + 3, 0xaa000000)
|
13
|
+
# left border
|
14
|
+
draw_rect(pad_x, pad_y, 3, size_y + 3, 0xaa000000)
|
15
|
+
# right border
|
16
|
+
draw_rect(pad_x + size_x - 3, pad_y, 3, size_y + 3, 0xaa000000)
|
17
|
+
(0..MAX_CLIENTS).each do |i|
|
18
|
+
# row borders
|
19
|
+
draw_rect(pad_x + 3, pad_y + (i * slot_height), size_x - 6, 3, 0xaa000000)
|
20
|
+
end
|
21
|
+
players.each_with_index do |player, i|
|
22
|
+
score_offset = text_scale * 10 * player.score.to_s.length
|
23
|
+
dbg = 0
|
24
|
+
if debug
|
25
|
+
dbg += 25
|
26
|
+
score_offset += 25
|
27
|
+
font.draw_text(player.id, pad_x + 5, pad_y + (i * slot_height), 0, text_scale, text_scale, 0xFF00FF00)
|
30
28
|
end
|
29
|
+
font.draw_text(player.name, dbg + pad_x + 5, pad_y + (i * slot_height), 0, text_scale, text_scale)
|
30
|
+
font.draw_text(player.score, dbg + pad_x + size_x - score_offset, pad_y + (i * slot_height), 0, text_scale,
|
31
|
+
text_scale)
|
32
|
+
end
|
31
33
|
end
|
@@ -1,8 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'socket'
|
2
4
|
|
3
5
|
require_relative '../share/console'
|
4
6
|
require_relative '../share/network'
|
5
7
|
require_relative '../share/player'
|
8
|
+
require_relative '../share/game_map'
|
6
9
|
|
7
10
|
require_relative 'gamelogic'
|
8
11
|
require_relative 'server_cfg'
|
@@ -23,12 +26,16 @@ class ServerCore
|
|
23
26
|
# 1 - player id
|
24
27
|
@clients = []
|
25
28
|
@current_id = 0
|
29
|
+
@bans = {}
|
26
30
|
@tick = 0
|
27
31
|
@last_alive_pck_by_client = Time.now
|
28
32
|
@console = Console.new
|
29
|
-
@cfg = ServerCfg.new(@console,
|
33
|
+
@cfg = ServerCfg.new(@console, 'server.json')
|
30
34
|
@gamelogic = GameLogic.new(@console)
|
31
|
-
|
35
|
+
|
36
|
+
@cfg.data['map'] = 'battle' if @cfg.data['map'] == ''
|
37
|
+
@game_map = GameMap.new(@console, @cfg, @cfg.data['map'])
|
38
|
+
@game_map.prepare_upload
|
32
39
|
end
|
33
40
|
|
34
41
|
def parse_client_version(data)
|
@@ -38,10 +45,10 @@ class ServerCore
|
|
38
45
|
version = data[1..4]
|
39
46
|
player = Player.get_player_by_id(@players, id)
|
40
47
|
if player
|
41
|
-
@console.
|
48
|
+
@console.dbg "[NAME-REQUEST] ID='#{id}' version='#{version}' name='#{player.name}'"
|
42
49
|
player.set_version(version)
|
43
50
|
else
|
44
|
-
@console.
|
51
|
+
@console.err "failed to parse version data=#{data}"
|
45
52
|
end
|
46
53
|
player
|
47
54
|
end
|
@@ -49,7 +56,13 @@ class ServerCore
|
|
49
56
|
def create_name_package(data, client)
|
50
57
|
if !client.nil? && !data.nil?
|
51
58
|
player = Player.get_player_by_id(@players, client[PLAYER_ID])
|
52
|
-
player.
|
59
|
+
if player.nil?
|
60
|
+
port, ip = Socket.unpack_sockaddr_in(client[NET_CLIENT].getpeername)
|
61
|
+
@console.wrn "IP=#{ip}:#{port} tried to get a name pack (without player)"
|
62
|
+
return
|
63
|
+
end
|
64
|
+
player.name = data.strip.gsub(/[^a-zA-Z0-9_]/, '_')
|
65
|
+
@gamelogic.on_player_connect(client, @players)
|
53
66
|
end
|
54
67
|
|
55
68
|
# protocol 3 name prot
|
@@ -58,16 +71,15 @@ class ServerCore
|
|
58
71
|
pck = "3l#{net_pack_int(@players.count)}g"
|
59
72
|
@players.each do |p|
|
60
73
|
pck += p.to_n_pck
|
61
|
-
@console.dbg "pname='#{p.name}'"
|
62
74
|
end
|
63
75
|
pck.ljust(SERVER_PACKAGE_LEN, '0')
|
64
76
|
end
|
65
77
|
|
66
|
-
def
|
78
|
+
def next_free_id
|
67
79
|
# TODO: do this smarter
|
68
|
-
used_ids = @clients.map{ |c| c[1] }
|
80
|
+
used_ids = @clients.map { |c| c[1] }
|
69
81
|
id = 0
|
70
|
-
while id < MAX_CLIENTS
|
82
|
+
while id < MAX_CLIENTS
|
71
83
|
id += 1
|
72
84
|
return id unless used_ids.include? id
|
73
85
|
end
|
@@ -75,10 +87,10 @@ class ServerCore
|
|
75
87
|
end
|
76
88
|
|
77
89
|
def add_player(name, version, client, ip)
|
78
|
-
@current_id =
|
90
|
+
@current_id = next_free_id
|
79
91
|
return -1 if @current_id > MAX_CLIENTS || @current_id < 1
|
80
92
|
|
81
|
-
@console.
|
93
|
+
@console.dbg "[NEW PLAYER] IP='#{ip}' ID='#{@current_id}' version='#{version}'"
|
82
94
|
@players << Player.new(@current_id, 0, nil, nil, name, version, ip)
|
83
95
|
client[PLAYER_ID] = @current_id
|
84
96
|
@current_id # implicit return
|
@@ -100,16 +112,37 @@ class ServerCore
|
|
100
112
|
|
101
113
|
def update_pck(data, dt)
|
102
114
|
id = data[0].to_i(16)
|
103
|
-
@console.dbg "got player with id: #{id}"
|
104
|
-
@players = @gamelogic.handle_client_requests(data[1..-1], id, @players, dt)
|
115
|
+
@console.dbg "[UPDATE] got player with id: #{id}"
|
116
|
+
@players = @gamelogic.handle_client_requests(@game_map, data[1..-1], id, @players, dt)
|
105
117
|
nil # defaults to normal update pck
|
106
118
|
end
|
107
119
|
|
120
|
+
def map_info_pck
|
121
|
+
"5l#{@game_map.checksum}".ljust(SERVER_PACKAGE_LEN, ' ')
|
122
|
+
end
|
123
|
+
|
124
|
+
def map_dl_init_pck
|
125
|
+
size = net_pack_bigint(@game_map.size, 6)
|
126
|
+
name = @game_map.name
|
127
|
+
"6l#{size}#{name}".ljust(SERVER_PACKAGE_LEN, ' ')
|
128
|
+
end
|
129
|
+
|
130
|
+
def map_dl_chunk_pck(player)
|
131
|
+
size = SERVER_PACKAGE_LEN - 2
|
132
|
+
map_chunk = @game_map.get_data(player.map_download, size)
|
133
|
+
player.map_download += size
|
134
|
+
"7l#{map_chunk}"
|
135
|
+
end
|
136
|
+
|
108
137
|
def id_pck(data, client, ip)
|
138
|
+
if num_ip_connected(ip) > @cfg.data['max_clients_per_ip']
|
139
|
+
disconnect_client(client, "0l#{NET_ERR_DISCONNECT}too many clients per ip ")
|
140
|
+
return
|
141
|
+
end
|
109
142
|
player_version = data[0..3]
|
110
|
-
id = add_player(
|
143
|
+
id = add_player('(connecting)', player_version, client, ip)
|
111
144
|
if id == -1
|
112
|
-
@console.log "'#{
|
145
|
+
@console.log "IP='#{ip}' failed to connect (server full)"
|
113
146
|
# protocol 0 (error) code=404 slot not found
|
114
147
|
return "0l#{NET_ERR_FULL} "
|
115
148
|
end
|
@@ -120,22 +153,27 @@ class ServerCore
|
|
120
153
|
@console.log "IP='#{ip}' failed to connect (client too new '#{player_version}' < '#{GAME_VERSION}')"
|
121
154
|
return "0l#{NET_ERR_SERVER_OUTDATED}#{GAME_VERSION}".ljust(SERVER_PACKAGE_LEN, ' ')
|
122
155
|
end
|
123
|
-
@console.
|
124
|
-
@global_pack = "true"
|
156
|
+
@console.dbg "[ID-PACKAGE] ID='#{id}' IP='#{ip}'"
|
125
157
|
# protocol 2 (id)
|
126
|
-
"2l#{net_pack_int(@players.count)}#{net_pack_int(MAX_CLIENTS)}#{id.to_s(16)}X#{GAME_VERSION}".ljust(
|
158
|
+
"2l#{net_pack_int(@players.count)}#{net_pack_int(MAX_CLIENTS)}#{id.to_s(16)}X#{GAME_VERSION}".ljust(
|
159
|
+
SERVER_PACKAGE_LEN, '0'
|
160
|
+
)
|
127
161
|
end
|
128
162
|
|
129
163
|
def command_package(data, client)
|
130
164
|
id = data[0..1].to_i(16)
|
131
|
-
cmd = data[1..-1]
|
165
|
+
cmd = data[1..-1].strip
|
132
166
|
@console.log "[chat] ID=#{id} command='#{cmd}'"
|
133
167
|
msg = "server_recived_cmd: #{cmd}"
|
134
168
|
msg = msg.ljust(SERVER_PACKAGE_LEN - 2, '0')
|
135
169
|
msg = msg[0..SERVER_PACKAGE_LEN - CMD_LEN]
|
136
|
-
|
170
|
+
case cmd
|
171
|
+
when 'test'
|
137
172
|
# return "0l#{NET_ERR_DISCONNECT} SAMPLE MESSAGE "
|
138
173
|
msg = "id=#{client[PLAYER_ID]}"
|
174
|
+
when 'exit'
|
175
|
+
msg = 'ok'
|
176
|
+
shutdown
|
139
177
|
end
|
140
178
|
msg = msg.ljust(SERVER_PACKAGE_LEN - 2, ' ')
|
141
179
|
msg = msg[0..SERVER_PACKAGE_LEN - 2]
|
@@ -144,27 +182,62 @@ class ServerCore
|
|
144
182
|
end
|
145
183
|
|
146
184
|
def handle_protocol(client, protocol, p_status, data, ip, dt)
|
147
|
-
@console.dbg "
|
185
|
+
@console.dbg "[PROTOCOL] protocol=#{protocol} status=#{p_status}"
|
148
186
|
if protocol.zero? # error pck
|
149
|
-
@console.
|
187
|
+
@console.err "Error pck=#{data}"
|
150
188
|
elsif protocol == 1 # id pck
|
151
|
-
|
189
|
+
id_pck(data, client, ip)
|
152
190
|
elsif protocol == 3 # initial request names
|
153
|
-
|
191
|
+
create_name_package(data, client)
|
154
192
|
else
|
193
|
+
if data.nil?
|
194
|
+
@console.err "IP=#{ip} invalid data"
|
195
|
+
return
|
196
|
+
end
|
155
197
|
# all other types require id
|
156
198
|
id = data[0].to_i(16)
|
157
199
|
if id != client[PLAYER_ID]
|
158
|
-
@console.
|
200
|
+
@console.wrn "id=#{client[PLAYER_ID]} tried to spoof id=#{id} ip=#{ip}"
|
201
|
+
@console.wrn data
|
159
202
|
disconnect_client(client, "0l#{NET_ERR_DISCONNECT}invalid player id ")
|
160
203
|
return nil
|
161
204
|
end
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
205
|
+
case protocol
|
206
|
+
when 2 # update pck
|
207
|
+
return update_pck(data, dt) if @game_map.nil?
|
208
|
+
|
209
|
+
player = Player.get_player_by_id(@players, id)
|
210
|
+
if player.map_download == -2
|
211
|
+
player.map_download = -1
|
212
|
+
map_info_pck
|
213
|
+
elsif player.map_download == -1
|
214
|
+
# set state to -3 which stops sending
|
215
|
+
# any further information
|
216
|
+
# wait for the client to respond
|
217
|
+
player.map_download = -3
|
218
|
+
map_dl_init_pck
|
219
|
+
elsif player.map_download < @game_map.size && player.map_download >= 0
|
220
|
+
map_dl_chunk_pck(player)
|
221
|
+
else
|
222
|
+
update_pck(data, dt)
|
223
|
+
end
|
224
|
+
when 4 # command
|
225
|
+
command_package(data, client)
|
226
|
+
when 5 # map info response
|
227
|
+
return update_pck(data, dt) if @game_map.nil?
|
228
|
+
|
229
|
+
player = Player.get_player_by_id(@players, id)
|
230
|
+
if data[1] == '1'
|
231
|
+
player.map_download = 0
|
232
|
+
@console.log 'player started map download'
|
233
|
+
map_dl_chunk_pck(player)
|
234
|
+
else
|
235
|
+
@console.log 'player rejected map download'
|
236
|
+
player.map_download = @game_map.size
|
237
|
+
update_pck(data, dt)
|
238
|
+
end
|
166
239
|
else
|
167
|
-
@console.
|
240
|
+
@console.err "IP=#{ip} unkown protocol=#{protocol} data=#{data}"
|
168
241
|
end
|
169
242
|
end
|
170
243
|
end
|
@@ -174,32 +247,17 @@ class ServerCore
|
|
174
247
|
# the response is a direct respond to an protocol
|
175
248
|
# everything above this could override important responds
|
176
249
|
# like id assignment
|
177
|
-
#
|
250
|
+
# everything that is after this guard case just overrides update pcks
|
178
251
|
return response unless response.nil?
|
179
252
|
|
180
|
-
if (@tick % 100).zero?
|
181
|
-
return create_name_package(nil, nil)
|
182
|
-
end
|
183
|
-
|
184
|
-
# some debug suff for class vars
|
185
|
-
# if (@tick % 50).zero?
|
186
|
-
# puts ""
|
187
|
-
# @console.log "id=#{data[0].to_i} currentid=#{@current_id}"
|
188
|
-
# end
|
189
|
-
|
190
|
-
# if @global_pack.nil?
|
191
|
-
# @global_pack = nil
|
192
|
-
# @console.log "sending an global pck"
|
193
|
-
# return "5l#{players_to_packet}"
|
194
|
-
# end
|
253
|
+
return create_name_package(nil, nil) if (@tick % 100).zero?
|
195
254
|
|
196
255
|
# if error occurs or something unexpected
|
197
256
|
# just send regular update pck
|
198
257
|
# protocol 1 (update)
|
199
|
-
"1l#{players_to_packet}"
|
258
|
+
"1l#{players_to_packet}"
|
200
259
|
end
|
201
260
|
|
202
|
-
# TODO: this func and it dependencies should be new file
|
203
261
|
# Handles each client
|
204
262
|
def client_tick(cli, dt)
|
205
263
|
client_data = save_read(cli[NET_CLIENT], CLIENT_PACKAGE_LEN)
|
@@ -211,9 +269,8 @@ class ServerCore
|
|
211
269
|
return
|
212
270
|
end
|
213
271
|
|
214
|
-
@console.dbg "tick recv: '#{client_data}'"
|
215
272
|
@last_alive_pck_by_client = Time.now
|
216
|
-
|
273
|
+
_port, ip = Socket.unpack_sockaddr_in(cli[NET_CLIENT].getpeername)
|
217
274
|
server_response = handle_client_data(cli, client_data, ip, dt)
|
218
275
|
pck_type = server_response[0]
|
219
276
|
if pck_type == SERVER_PCK_TYPE[:error]
|
@@ -225,8 +282,8 @@ class ServerCore
|
|
225
282
|
|
226
283
|
def disconnect_client(client, server_response = nil)
|
227
284
|
player_id = client[PLAYER_ID]
|
228
|
-
@
|
229
|
-
@console.dbg "client disconnected.#{" (
|
285
|
+
@gamelogic.on_player_disconnect(client, @players)
|
286
|
+
@console.dbg "client disconnected.#{" (#{server_response})" unless server_response.nil?}"
|
230
287
|
net_write(server_response, client[NET_CLIENT]) unless server_response.nil?
|
231
288
|
client[NET_CLIENT].close
|
232
289
|
delete_player(player_id)
|
@@ -240,6 +297,14 @@ class ServerCore
|
|
240
297
|
end
|
241
298
|
end
|
242
299
|
|
300
|
+
def shutdown
|
301
|
+
@clients.each do |client|
|
302
|
+
disconnect_client(client)
|
303
|
+
end
|
304
|
+
@console.log 'server shutdown.'
|
305
|
+
exit
|
306
|
+
end
|
307
|
+
|
243
308
|
def run
|
244
309
|
server = TCPServer.open(@cfg.data['port'])
|
245
310
|
server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) # nagle's algorithm
|
@@ -252,39 +317,79 @@ class ServerCore
|
|
252
317
|
if $next_tick > t
|
253
318
|
sleep $next_tick - t
|
254
319
|
else
|
255
|
-
unless @tick.zero?
|
256
|
-
@console.log "[WARNING] tick took #{t - $next_tick} too long"
|
257
|
-
end
|
320
|
+
@console.wrn "tick took #{t - $next_tick} too long" unless @tick.zero?
|
258
321
|
end
|
259
322
|
$next_tick = Time.now + MAX_TICK_SPEED
|
260
323
|
@tick += 1
|
261
|
-
@players = @gamelogic.tick(@players, diff)
|
324
|
+
@players = @gamelogic.tick(@game_map, @players, diff, @tick)
|
262
325
|
# there is no gurantee the client will tick here
|
263
326
|
# there might be 2 gamelogic ticks and posticks
|
264
327
|
# before the server recieves client data
|
265
328
|
# since it is a nonblocking read and server/client are not in perfect sync
|
266
329
|
@clients.each do |client|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
disconnect_client(client)
|
271
|
-
end
|
330
|
+
client_tick(client, diff)
|
331
|
+
rescue Errno::ECONNRESET, Errno::ENOTCONN, IOError
|
332
|
+
disconnect_client(client)
|
272
333
|
end
|
273
|
-
@players = @gamelogic.posttick(@players, diff)
|
334
|
+
@players = @gamelogic.posttick(@game_map, @players, diff)
|
274
335
|
end
|
275
336
|
end
|
276
337
|
|
338
|
+
def ban_client(client, seconds, message = 'banned')
|
339
|
+
_port, ip = Socket.unpack_sockaddr_in(client.getpeername)
|
340
|
+
ban_ip(ip, seconds, message)
|
341
|
+
net_write("0l#{NET_ERR_BAN}#{message}"[0..SERVER_PACKAGE_LEN].ljust(SERVER_PACKAGE_LEN, ' '), client)
|
342
|
+
client.close
|
343
|
+
end
|
344
|
+
|
345
|
+
def ban_ip(ip, seconds, message)
|
346
|
+
@bans[ip] = Time.now + seconds
|
347
|
+
@console.log "IP=#{ip} banned for #{seconds} seconds (#{message})"
|
348
|
+
end
|
349
|
+
|
350
|
+
def ip_banned?(ip)
|
351
|
+
return false if @bans[ip].nil?
|
352
|
+
|
353
|
+
(@bans[ip] - Time.now).positive?
|
354
|
+
end
|
355
|
+
|
277
356
|
private
|
278
357
|
|
358
|
+
def num_ip_connected(ip)
|
359
|
+
connected = 0
|
360
|
+
@clients.each do |client|
|
361
|
+
_port, conencted_ip = Socket.unpack_sockaddr_in(client[NET_CLIENT].getpeername)
|
362
|
+
connected += 1 if conencted_ip == ip
|
363
|
+
end
|
364
|
+
connected
|
365
|
+
end
|
366
|
+
|
279
367
|
def accept(server)
|
368
|
+
last_connect = Hash.new([Time.now, 0])
|
280
369
|
Socket.accept_loop(server) do |client|
|
370
|
+
_port, ip = Socket.unpack_sockaddr_in(client.getpeername)
|
371
|
+
if ip_banned?(ip)
|
372
|
+
net_write("0l#{NET_ERR_BAN}banned".ljust(SERVER_PACKAGE_LEN, ' '), client)
|
373
|
+
client.close
|
374
|
+
next
|
375
|
+
end
|
376
|
+
diff = Time.now - last_connect[ip][0]
|
377
|
+
if diff < 3
|
378
|
+
last_connect[ip][1] += 1
|
379
|
+
if last_connect[ip][1] > 2
|
380
|
+
last_connect[ip][1] = 0
|
381
|
+
ban_client(client, 10, 'banned for 10 seconds (too many reconnects)')
|
382
|
+
next
|
383
|
+
end
|
384
|
+
end
|
281
385
|
@clients << [client, -1]
|
282
|
-
@console.log "client connected
|
386
|
+
@console.log "client connected IP=#{ip} (total #{@clients.count})"
|
387
|
+
last_connect[ip][0] = Time.now
|
283
388
|
end
|
284
389
|
end
|
285
390
|
|
286
391
|
def net_write(data, cli)
|
287
|
-
@console.dbg("sending: #{data}")
|
392
|
+
# @console.dbg("sending: #{data}")
|
288
393
|
cli.write(data)
|
289
394
|
end
|
290
395
|
end
|