chichilku3 14.0.3 → 15.0.1

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/bin/chichilku3 +1 -0
  3. data/bin/chichilku3-server +1 -0
  4. data/lib/client/chichilku3.rb +3 -1
  5. data/lib/client/client.rb +112 -59
  6. data/lib/client/client_cfg.rb +2 -1
  7. data/lib/client/gui.rb +219 -197
  8. data/lib/client/img/stick128/arm64/arm0.png +0 -0
  9. data/lib/client/img/stick128/arm64/arm1.png +0 -0
  10. data/lib/client/img/stick128/arm64/arm2.png +0 -0
  11. data/lib/client/img/stick128/arm64/arm3.png +0 -0
  12. data/lib/client/img/stick128/noarms/stick0.png +0 -0
  13. data/lib/client/img/stick128/noarms/stick1.png +0 -0
  14. data/lib/client/img/stick128/noarms/stick2.png +0 -0
  15. data/lib/client/img/stick128/noarms/stick3.png +0 -0
  16. data/lib/client/img/stick128/noarms/stick4.png +0 -0
  17. data/lib/client/img/stick128/noarms/stick5.png +0 -0
  18. data/lib/client/keys.rb +29 -0
  19. data/lib/client/particles.rb +54 -0
  20. data/lib/client/scoreboard.rb +29 -27
  21. data/lib/{client → external/gosu}/text.rb +29 -38
  22. data/lib/external/rubyzip/recursive.rb +58 -0
  23. data/lib/server/chichilku3_server.rb +169 -64
  24. data/lib/server/gamelogic.rb +107 -51
  25. data/lib/server/server_cfg.rb +2 -0
  26. data/lib/share/config.rb +21 -7
  27. data/lib/share/console.rb +22 -5
  28. data/lib/share/game_map.rb +279 -0
  29. data/lib/share/math.rb +14 -0
  30. data/lib/share/network.rb +47 -42
  31. data/lib/share/player.rb +168 -105
  32. data/lib/share/projectile.rb +97 -98
  33. data/lib/share/string.rb +24 -0
  34. data/server.json +3 -2
  35. metadata +49 -23
  36. data/lib/client/img/battle1024x576.png +0 -0
  37. data/lib/client/img/grass1024x512.png +0 -0
  38. data/lib/client/img/stick128/stick_noarms.png +0 -0
  39. data/lib/client/test.rb +0 -39
data/lib/share/config.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'os'
3
5
  require 'fileutils'
@@ -7,7 +9,7 @@ class Config
7
9
  attr_reader :data, :chichilku3_dir
8
10
 
9
11
  def initialize(console, file)
10
- @chichilku3_dir = ""
12
+ @chichilku3_dir = ''
11
13
  if OS.linux?
12
14
  @chichilku3_dir = "#{ENV['HOME']}/.chichilku/chichilku3/"
13
15
  elsif OS.mac?
@@ -15,13 +17,24 @@ class Config
15
17
  # elsif OS.windows?
16
18
  # @chichilku3_dir = "%APPDATA%\\chichilku\\chichilku3\\"
17
19
  else
18
- puts "os not supported."
20
+ puts 'os not supported.'
19
21
  exit
20
22
  end
21
- puts "path: " + @chichilku3_dir
23
+ puts "path: #{@chichilku3_dir}"
22
24
  FileUtils.mkdir_p @chichilku3_dir
23
25
  FileUtils.mkdir_p "#{@chichilku3_dir}recordings"
26
+ FileUtils.mkdir_p "#{@chichilku3_dir}maps_b64"
27
+ FileUtils.mkdir_p "#{@chichilku3_dir}downloadedmaps"
28
+ FileUtils.mkdir_p "#{@chichilku3_dir}tmp"
29
+ unless File.directory? "#{@chichilku3_dir}maps"
30
+ if File.directory? 'maps'
31
+ FileUtils.cp_r 'maps', "#{@chichilku3_dir}maps"
32
+ else
33
+ FileUtils.mkdir_p "#{@chichilku3_dir}maps"
34
+ end
35
+ end
24
36
  create_default_cfg(file, "#{@chichilku3_dir}/#{file}")
37
+ @source_file = file
25
38
  @file = @chichilku3_dir + file
26
39
  @console = console
27
40
  @data = load
@@ -31,7 +44,7 @@ class Config
31
44
  return if File.file?(to)
32
45
 
33
46
  tmp = JSON.parse(File.read(from))
34
- File.open(to,"w") do |f|
47
+ File.open(to, 'w') do |f|
35
48
  f.write(tmp.to_json)
36
49
  end
37
50
  end
@@ -41,13 +54,14 @@ class Config
41
54
  end
42
55
 
43
56
  def load
57
+ defaults = JSON.parse(File.read(@source_file))
44
58
  data = JSON.parse(File.read(@file))
45
- data = sanitize_data(data)
46
- data
59
+ data = defaults.merge(data)
60
+ sanitize_data(data)
47
61
  end
48
62
 
49
63
  def save
50
- File.open(@file, "w") do |f|
64
+ File.open(@file, 'w') do |f|
51
65
  f.write(JSON.pretty_generate(data))
52
66
  end
53
67
  end
data/lib/share/console.rb CHANGED
@@ -1,22 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'string'
1
4
  DEBUG = false
5
+ DEBUG_PHYSICS = true
2
6
 
3
7
  # Console used by Client and Server
4
8
  class Console
5
9
  def log(message)
6
- t = Time.now
7
- puts format("[%02d:%02d:%02d][log] %s", t.hour, t.min, t.sec, message)
10
+ log_type('log', message)
8
11
  end
9
12
 
10
13
  def err(message)
11
- t = Time.now
12
- puts format("[%02d:%02d:%02d][error] %s", t.hour, t.min, t.sec, message)
14
+ log_type('error'.red, message)
15
+ end
16
+
17
+ def wrn(message)
18
+ log_type('warning'.yellow, message)
13
19
  end
14
20
 
15
21
  def dbg(message)
16
22
  return unless DEBUG
17
23
 
24
+ log_type('debug'.pink, message)
25
+ end
26
+
27
+ private
28
+
29
+ def log_type(type, message)
18
30
  t = Time.now
19
- puts format("[%02d:%02d:%02d][debug] %s", t.hour, t.min, t.sec, message)
31
+ puts format('[%<hour>02d:%<min>02d:%<sec>02d][%<type>s] %<msg>s',
32
+ hour: t.hour,
33
+ min: t.min,
34
+ sec: t.sec,
35
+ type: type,
36
+ msg: message)
20
37
  end
21
38
  end
22
39
 
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'digest/sha1'
5
+ require 'zip'
6
+ require 'fileutils'
7
+
8
+ require_relative '../external/rubyzip/recursive'
9
+
10
+ MAX_UNZIP_SIZE = 1024**2 # 1MiB
11
+ MAP_VERSION = 1
12
+ MAP_FILES = [
13
+ 'background.png',
14
+ 'gametiles.txt',
15
+ 'metadata.json'
16
+ ].freeze
17
+
18
+ # GameMap class handles the game_map file format
19
+ class GameMap
20
+ attr_reader :gametiles, :grass_rows, :ready, :metadata
21
+
22
+ def initialize(console, cfg, mapname, callback = nil, checksum = nil)
23
+ @console = console
24
+ @cfg = cfg
25
+ @mapname = mapname
26
+ @b64_size = -1
27
+ @b64_data = ''
28
+ @sha1sum = checksum
29
+ @gametiles = []
30
+ # grass_rows
31
+ # array of hashes containing connected grass tiles
32
+ # a row of grass from x 0 to x 10 at the height 2 would look like this
33
+ # {x1: 0, x2: 10, y: 2}
34
+ @grass_rows = []
35
+ @ready = false
36
+ @metadata = nil
37
+
38
+ # client
39
+ @callback = callback
40
+ @progress = 0
41
+ @tmpfile = nil
42
+ end
43
+
44
+ def checksum
45
+ @sha1sum
46
+ end
47
+
48
+ def name
49
+ @mapname
50
+ end
51
+
52
+ # TODO: fix this rubocop
53
+ # rubocop:disable Naming/AccessorMethodName
54
+ def set_name(name)
55
+ raise unless @mapname.nil?
56
+
57
+ @mapname = name
58
+ end
59
+
60
+ def size
61
+ @b64_size
62
+ end
63
+
64
+ def set_size(size)
65
+ raise unless @b64_size == -1
66
+
67
+ @b64_size = size
68
+ end
69
+ # rubocop:enable Naming/AccessorMethodName
70
+
71
+ def load_gametiles(map_dir)
72
+ gamefile = "#{map_dir}/gametiles.txt"
73
+ unless File.exist? gamefile
74
+ @console.err "could not load gametiles '#{gamefile}'"
75
+ exit 1
76
+ end
77
+
78
+ is_skip = true
79
+ @gametiles = []
80
+ @grass_rows = []
81
+ File.readlines(gamefile).each_with_index do |data, i|
82
+ gamerow = data[0..-2] # cut off newline
83
+ is_skip = !is_skip if gamerow =~ /\+-+\+/
84
+ gamerow = gamerow.match(/\|(.*)\|/)
85
+ next if gamerow.nil?
86
+
87
+ gamerow = gamerow[1]
88
+ next if is_skip
89
+
90
+ if gamerow.length != MAP_WIDTH
91
+ @console.err "invalid gametiles row=#{i} size=#{gamerow.length}/#{MAP_WIDTH}"
92
+ exit 1
93
+ end
94
+ @gametiles << gamerow
95
+ end
96
+ if @gametiles.length != MAP_HEIGHT
97
+ @console.err "invalid gametiles rows=#{@gametiles.length}/#{MAP_HEIGHT}"
98
+ exit 1
99
+ end
100
+ y = 0
101
+ grass = {}
102
+ @gametiles.each do |gamerow|
103
+ x = 0
104
+ gamerow.chars.each do |tile|
105
+ if tile == 'i'
106
+ grass[:x2] = x * TILE_SIZE + TILE_SIZE
107
+ grass[:y] = y * TILE_SIZE + TILE_SIZE / 2 + 2
108
+ grass[:x1] = x * TILE_SIZE if grass[:x1].nil?
109
+ else
110
+ @grass_rows.push(grass) unless grass == {}
111
+ grass = {}
112
+ end
113
+ x += 1
114
+ end
115
+ y += 1
116
+ end
117
+ nil
118
+ end
119
+
120
+ def load_metadata(map_dir)
121
+ metafile = "#{map_dir}/metadata.json"
122
+ unless File.exist? metafile
123
+ @console.err "could not load gametiles '#{metafile}'"
124
+ exit 1
125
+ end
126
+
127
+ @metadata = JSON.parse(File.read(metafile))
128
+ if @metadata['chichilku3-map-version'] != MAP_VERSION
129
+ @console.err "Failed to load map '#{@metadata['name']}':"
130
+ @console.err " Expected map version '#{MAP_VERSION}' but got '#{@metadata['chichilku3-map-version']}'"
131
+ exit 1
132
+ end
133
+ @console.log "loaded map '#{@metadata['name']}' (#{@metadata['version']}) by #{@metadata['authors'].join(',')}"
134
+ end
135
+
136
+ def load_data(map_dir)
137
+ load_gametiles(map_dir)
138
+ load_metadata(map_dir)
139
+ @ready = true
140
+ end
141
+
142
+ def death?(x, y)
143
+ { x: x, y: y } if @gametiles[y][x] == 'X'
144
+ end
145
+
146
+ def collision?(x, y)
147
+ { x: x, y: y } if @gametiles[y][x] == 'O'
148
+ end
149
+
150
+ def grass?(x, y)
151
+ { x: x, y: y } if @gametiles[y][x] == 'i'
152
+ end
153
+
154
+ # SERVER
155
+
156
+ def prepare_upload
157
+ return if @mapname == '' || @mapname.nil?
158
+
159
+ map_dir = "#{@cfg.chichilku3_dir}maps/#{@mapname}"
160
+ unless File.directory? map_dir
161
+ @console.err "failed to load map '#{@mapname}' (directory not found)"
162
+ exit 1
163
+ end
164
+ unless File.exist? "#{map_dir}/background.png"
165
+ @console.err "failed to load map '#{@mapname}' (no background.png)"
166
+ exit 1
167
+ end
168
+ unless File.exist? "#{map_dir}/gametiles.txt"
169
+ @console.err "failed to load map '#{@mapname}' (no gametiles.txt)"
170
+ exit 1
171
+ end
172
+ load_data(map_dir)
173
+ zip
174
+ encode
175
+ end
176
+
177
+ def zip
178
+ map_dir = "#{@cfg.chichilku3_dir}maps/#{@mapname}"
179
+ map_zip = "#{@cfg.chichilku3_dir}maps/#{@mapname}.zip"
180
+ File.delete map_zip if File.exist? map_zip
181
+
182
+ @console.log "archiving map '#{map_zip}' ..."
183
+ Zip::File.open(map_zip, Zip::File::CREATE) do |zipfile|
184
+ MAP_FILES.each do |filename|
185
+ filepath = File.join(map_dir, filename)
186
+ unless File.exist? filepath
187
+ @console.err "failed to zip map '#{@mapname}' missing file:"
188
+ @console.err filepath
189
+ exit 1
190
+ end
191
+ zipfile.add(filename, filepath)
192
+ end
193
+ end
194
+ end
195
+
196
+ def encode
197
+ rawfile = "#{@cfg.chichilku3_dir}maps/#{@mapname}.zip"
198
+ @console.log "encoding map archive '#{@mapname}' ..."
199
+ File.open(rawfile, 'rb') do |map_png|
200
+ raw_content = map_png.read
201
+ @sha1sum = Digest::SHA1.hexdigest raw_content
202
+ encodefile = "#{@cfg.chichilku3_dir}maps_b64/#{@mapname}_#{checksum}.zip"
203
+ File.open(encodefile, 'wb') do |map_encoded|
204
+ @b64_data = Base64.encode64(raw_content).delete! "\n"
205
+ @b64_size = @b64_data.size
206
+ map_encoded.write(@b64_data)
207
+ end
208
+ end
209
+ @console.log "finished encoding size=#{@b64_size} checksum=#{checksum}"
210
+ end
211
+
212
+ def get_data(offset, size)
213
+ return nil if @mapname == '' || @mapname.nil?
214
+
215
+ if offset + size > @b64_size
216
+ @b64_data[offset..-1].ljust(size, ' ')
217
+ else
218
+ @b64_data[offset...offset + size]
219
+ end
220
+ end
221
+
222
+ # CLIENT
223
+
224
+ def dl_path
225
+ "#{@cfg.chichilku3_dir}downloadedmaps/#{@mapname}_#{checksum}"
226
+ end
227
+
228
+ def prepare_download
229
+ @tmpfile = "#{@cfg.chichilku3_dir}tmp/#{@mapname}"
230
+ File.delete @tmpfile if File.exist? @tmpfile
231
+ end
232
+
233
+ def download(data)
234
+ data.strip!
235
+ @progress += data.size
236
+ @console.dbg "downloading #{@progress} / #{@b64_size} ..."
237
+ IO.write(@tmpfile, data, mode: 'a')
238
+ if @progress >= @b64_size
239
+ @console.log 'finished download'
240
+ @callback.call(load)
241
+ end
242
+ @progress
243
+ end
244
+
245
+ def found?
246
+ File.directory? dl_path
247
+ end
248
+
249
+ def unzip
250
+ map_archive = "#{dl_path}.zip"
251
+ map_dir = dl_path
252
+ FileUtils.mkdir_p map_dir
253
+ Dir.chdir map_dir do
254
+ Zip::File.open(map_archive) do |zip_file|
255
+ zip_file.each do |entry|
256
+ @console.log "extracting '#{entry.name}' ...'"
257
+ raise 'File too large when extracted' if entry.size > MAX_UNZIP_SIZE
258
+
259
+ entry.extract
260
+ end
261
+ end
262
+ end
263
+ File.delete map_archive if File.exist? map_archive
264
+ map_dir
265
+ end
266
+
267
+ def load
268
+ outfile = "#{dl_path}.zip"
269
+ @console.log 'converting downloaded map ...'
270
+ File.open(@tmpfile, 'rb') do |map_encoded|
271
+ File.open(outfile, 'wb') do |map_png|
272
+ map_png.write(
273
+ Base64.decode64(map_encoded.read)
274
+ )
275
+ end
276
+ end
277
+ unzip
278
+ end
279
+ end
data/lib/share/math.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ SIDE_LEFT = -1
4
+ SIDE_RIGHT = 1
5
+
6
+ ##
7
+ # Checks wether a given point is closer to the left or right step of a given interval
8
+ #
9
+ # @return -1 for left and 1 for right
10
+ # @param interval [Integer] interval size.
11
+ # @param point [Integer] point to check.
12
+ def closest_interval_side(interval, point)
13
+ (point % interval) * 2 < interval ? SIDE_LEFT : SIDE_RIGHT
14
+ end
data/lib/share/network.rb CHANGED
@@ -1,22 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'socket'
2
4
  # check doc_network.rb for documentation
3
5
 
4
6
  # update GAME_VERSION on network protocol changes
5
- GAME_VERSION = '0014'
7
+ GAME_VERSION = '0015'
6
8
 
7
9
  # game
8
10
 
9
11
  TILE_SIZE = 64
10
- WINDOW_SIZE_X = TILE_SIZE * 16
11
- WINDOW_SIZE_Y = TILE_SIZE * 9
12
+ PLAYER_SIZE = TILE_SIZE - 2
13
+ MAP_WIDTH = 16
14
+ MAP_HEIGHT = 9
15
+ WINDOW_SIZE_X = TILE_SIZE * MAP_WIDTH
16
+ WINDOW_SIZE_Y = TILE_SIZE * MAP_HEIGHT
12
17
  FULLHD_X = 1920
13
- UI_SCALE = WINDOW_SIZE_X.to_f / FULLHD_X.to_f
18
+ UI_SCALE = WINDOW_SIZE_X.to_f / FULLHD_X
14
19
  SPEED = TILE_SIZE
15
20
 
16
21
  # networking
17
22
 
18
23
  CMD_LEN = 7
19
24
  NAME_LEN = 9
25
+ MAX_MAPNAME_LEN = 43
20
26
  MAX_CLIENTS = 12
21
27
  PLAYER_PACKAGE_LEN = 16
22
28
  CLIENT_PACKAGE_LEN = 11 # used by server
@@ -24,40 +30,41 @@ SERVER_PACKAGE_LEN = MAX_CLIENTS * PLAYER_PACKAGE_LEN + 4 # used by client
24
30
 
25
31
  MAX_TIMEOUT = 5
26
32
  MAX_TICK_SPEED = 0.01 # the lower the faster client and server tick
33
+ # MAX_TICK_SPEED = 0.07
27
34
  # MAX_TICK_SPEED = 0.005
28
35
 
29
- NET_ERR_FULL = "404"
30
- NET_ERR_DISCONNECT = "001"
31
- NET_ERR_KICK = "002"
32
- NET_ERR_BAN = "003"
33
- NET_ERR_SERVER_OUTDATED = "004"
34
- NET_ERR_CLIENT_OUTDATED = "005"
36
+ NET_ERR_FULL = '404'
37
+ NET_ERR_DISCONNECT = '001'
38
+ NET_ERR_KICK = '002'
39
+ NET_ERR_BAN = '003'
40
+ NET_ERR_SERVER_OUTDATED = '004'
41
+ NET_ERR_CLIENT_OUTDATED = '005'
35
42
 
36
43
  NET_ERR = {
37
- "404" => "SERVER FULL",
38
- "001" => "DISCONNECTED",
39
- "002" => "KICKED",
40
- "003" => "BANNED",
41
- "004" => "SERVER OUTDATED",
42
- "005" => "CLIENT OUTDATED"
43
- }
44
+ '404' => 'SERVER FULL',
45
+ '001' => 'DISCONNECTED',
46
+ '002' => 'KICKED',
47
+ '003' => 'BANNED',
48
+ '004' => 'SERVER OUTDATED',
49
+ '005' => 'CLIENT OUTDATED'
50
+ }.freeze
44
51
 
45
52
  CLIENT_PCK_TYPE = {
46
- :error => "0",
47
- :join => "1",
48
- :move => "2",
49
- :info => "3",
50
- :cmd => "4"
51
- }
53
+ error: '0',
54
+ join: '1',
55
+ move: '2',
56
+ info: '3',
57
+ cmd: '4'
58
+ }.freeze
52
59
 
53
60
  SERVER_PCK_TYPE = {
54
- :error => "0",
55
- :update => "1",
61
+ error: '0',
62
+ update: '1',
56
63
  # TODO: find a good name here
57
- :info => "3",
58
- :cmd => "4",
59
- :event => "5"
60
- }
64
+ info: '3',
65
+ cmd: '4',
66
+ event: '5'
67
+ }.freeze
61
68
 
62
69
  NET_INT_OFFSET = 33
63
70
  NET_INT_BASE = 93
@@ -70,13 +77,13 @@ NET_MIN_INT = 0
70
77
  # the base of the network is NET_INT_BASE
71
78
  # so the number 93 is the last single character number represented as '~'
72
79
  #
73
- # @param [Integer, #chr] int decimal based number
80
+ # @param [Integer, #chr] int decimal based number
74
81
  # @return [String] the int converted to base NET_INT_BASE
75
82
 
76
83
  def net_pack_int(int)
77
84
  net_error "#{__method__}: '#{int}' is too low allowed range #{NET_MIN_INT}-#{NET_MAX_INT}" if int < NET_MIN_INT
78
85
  net_error "#{__method__}: '#{int}' is too high allowed range #{NET_MIN_INT}-#{NET_MAX_INT}" if int > NET_MAX_INT
79
- int = int + NET_INT_OFFSET
86
+ int += NET_INT_OFFSET
80
87
  int.chr
81
88
  end
82
89
 
@@ -101,17 +108,18 @@ end
101
108
  # @return [String] the int converted to base NET_INT_BASE
102
109
 
103
110
  def net_pack_bigint(int, size)
104
- sum = ""
111
+ sum = ''
105
112
  div = size - 1
106
113
  (size - 1).times do
107
- buf = int / ((NET_MAX_INT+1) ** div)
114
+ buf = int / ((NET_MAX_INT + 1)**div)
108
115
  sum += net_pack_int(buf)
109
- int = int % ((NET_MAX_INT+1) ** div)
116
+ int = int % ((NET_MAX_INT + 1)**div)
117
+ div -= 1
110
118
  end
111
119
  sum += net_pack_int(int)
112
120
  # TODO: check reminder and so on
113
121
  # throw and error when int is too big for size
114
- int = int / NET_MAX_INT
122
+ int /= NET_MAX_INT
115
123
  sum
116
124
  end
117
125
 
@@ -127,21 +135,18 @@ def net_unpack_bigint(net_int)
127
135
  if i.zero?
128
136
  sum = net_unpack_int(c)
129
137
  else
130
- sum += net_unpack_int(c) * i * (NET_MAX_INT+1)
138
+ sum += net_unpack_int(c) * (NET_MAX_INT + 1)**i
131
139
  end
132
140
  end
133
141
  sum
134
142
  end
135
143
 
136
144
  def save_read(socket, size)
137
- begin
138
- return socket.read_nonblock(size)
139
- rescue IO::WaitReadable
140
- return ''
141
- end
145
+ socket.read_nonblock(size)
146
+ rescue IO::WaitReadable
147
+ ''
142
148
  end
143
149
 
144
150
  def net_error(err)
145
151
  raise "NetError: #{err}"
146
- exit 1
147
152
  end