termfront 0.1.4 → 0.1.6
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/CHANGELOG.md +43 -0
- data/exe/termfront +2 -0
- data/lib/termfront/config.rb +3 -1
- data/lib/termfront/game.rb +76 -50
- data/lib/termfront/network/client.rb +1 -1
- data/lib/termfront/network/server.rb +32 -6
- data/lib/termfront/renderer.rb +404 -245
- data/lib/termfront/terminal_output.rb +31 -21
- data/lib/termfront/title_screen.rb +51 -23
- data/lib/termfront/version.rb +1 -1
- metadata +1 -1
data/lib/termfront/renderer.rb
CHANGED
|
@@ -2,12 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
module Termfront
|
|
4
4
|
class Renderer
|
|
5
|
+
RADAR_CRAWLER = "\e[91m*\e[0m"
|
|
6
|
+
RADAR_EXECUTOR = "\e[95m*\e[0m"
|
|
7
|
+
RADAR_ALLY = "\e[96m+\e[0m"
|
|
8
|
+
RADAR_TERMINAL = "\e[96mT\e[0m"
|
|
9
|
+
RADAR_PLAYER = "\e[92m^\e[0m"
|
|
10
|
+
RADAR_WALL = "\e[90m#\e[0m"
|
|
11
|
+
|
|
12
|
+
FG_256 = Array.new(256) { |i| "\e[38;5;#{i}m".freeze }.freeze
|
|
13
|
+
BG_256 = Array.new(256) { |i| "\e[48;5;#{i}m".freeze }.freeze
|
|
14
|
+
|
|
5
15
|
def initialize(stdout)
|
|
6
16
|
@stdout = stdout
|
|
17
|
+
@buf_view_w = 0
|
|
18
|
+
@buf_virt_h = 0
|
|
19
|
+
@radar_grid_template = build_radar_grid_template
|
|
20
|
+
@hrule_cache = Hash.new { |h, c| h[c] = ("\xE2\x94\x80" * c)[0, c * 3].freeze }
|
|
21
|
+
@radar_drop_glyphs = {}
|
|
22
|
+
@radar_enemy_cells = {}
|
|
23
|
+
@radar_drop_cells = {}
|
|
24
|
+
@radar_terminal_cells = {}
|
|
25
|
+
@radar_ally_cells = {}
|
|
26
|
+
@fg_truecolor_cache = {}
|
|
27
|
+
@bg_truecolor_cache = {}
|
|
28
|
+
@enemy_sprites = []
|
|
29
|
+
@proj_sprites = []
|
|
30
|
+
@drop_sprites = []
|
|
31
|
+
@ally_sprites = []
|
|
32
|
+
@radar_line_buf = +""
|
|
33
|
+
@size_cache = nil
|
|
34
|
+
@size_cache_at = -Float::INFINITY
|
|
35
|
+
@cached_hud_shield_key = nil
|
|
36
|
+
@cached_hud_shield_line = nil
|
|
37
|
+
@cached_hud_ammo_key = nil
|
|
38
|
+
@cached_hud_ammo_line = nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def invalidate_size_cache!
|
|
42
|
+
@size_cache = nil
|
|
7
43
|
end
|
|
8
44
|
|
|
9
45
|
def render(player:, map:, enemies:, projectiles:, drops:, terminals: [], status_line: nil, allies: [])
|
|
10
|
-
rows, cols =
|
|
46
|
+
rows, cols = current_size
|
|
11
47
|
rows = [rows, 6].max
|
|
12
48
|
cols = [cols, 20].max
|
|
13
49
|
|
|
@@ -16,38 +52,35 @@ module Termfront
|
|
|
16
52
|
view_w = cols
|
|
17
53
|
virt_h = view_h * 2
|
|
18
54
|
|
|
55
|
+
prepare_frame_buffers(view_w, virt_h)
|
|
56
|
+
|
|
19
57
|
dx = Math.cos(player.angle)
|
|
20
58
|
dy = Math.sin(player.angle)
|
|
21
59
|
plane_x = -dy * Math.tan(Config::FOV / 2.0)
|
|
22
60
|
plane_y = dx * Math.tan(Config::FOV / 2.0)
|
|
23
61
|
|
|
24
|
-
dists = Array.new(view_w)
|
|
25
|
-
sides = Array.new(view_w)
|
|
26
62
|
view_w.times do |c|
|
|
27
63
|
cam = 2.0 * c / view_w - 1.0
|
|
28
|
-
dists[c], sides[c] = cast_ray(map, player.x, player.y, dx + plane_x * cam, dy + plane_y * cam)
|
|
64
|
+
@dists[c], @sides[c] = cast_ray(map, player.x, player.y, dx + plane_x * cam, dy + plane_y * cam)
|
|
29
65
|
end
|
|
30
66
|
|
|
31
67
|
vmid = virt_h / 2.0
|
|
32
|
-
wtop = Array.new(view_w)
|
|
33
|
-
wbot = Array.new(view_w)
|
|
34
|
-
wcol = Array.new(view_w)
|
|
35
68
|
view_w.times do |c|
|
|
36
|
-
d = dists[c]
|
|
69
|
+
d = @dists[c]
|
|
37
70
|
lh = d > 0.01 ? (virt_h / d).to_i : virt_h
|
|
38
|
-
wtop[c] = [(vmid - lh / 2.0).to_i, 0].max
|
|
39
|
-
wbot[c] = [(vmid + lh / 2.0).to_i, virt_h].min
|
|
40
|
-
wcol[c] = Sprite.wall_brightness(d, sides[c])
|
|
71
|
+
@wtop[c] = [(vmid - lh / 2.0).to_i, 0].max
|
|
72
|
+
@wbot[c] = [(vmid + lh / 2.0).to_i, virt_h].min
|
|
73
|
+
@wcol[c] = Sprite.wall_brightness(d, @sides[c])
|
|
41
74
|
end
|
|
42
|
-
|
|
43
|
-
overlay_enemies_3d(pixels, view_h, view_w, dists, player, enemies, projectiles, drops)
|
|
44
|
-
overlay_allies_3d(pixels, view_h, view_w, dists, player, allies)
|
|
45
|
-
overlay_damage_flash(pixels, view_h, view_w, player)
|
|
75
|
+
build_view_pixels(virt_h, view_w, @wtop, @wbot, @wcol)
|
|
76
|
+
overlay_enemies_3d(@pixels, view_h, view_w, @dists, player, enemies, projectiles, drops)
|
|
77
|
+
overlay_allies_3d(@pixels, view_h, view_w, @dists, player, allies)
|
|
78
|
+
overlay_damage_flash(@pixels, view_h, view_w, player)
|
|
46
79
|
|
|
47
80
|
buf = TerminalOutput.begin_frame(home: true)
|
|
48
81
|
|
|
49
82
|
render_hud(buf, cols, player, drops, terminals, status_line)
|
|
50
|
-
render_view(buf, view_h, view_w, pixels)
|
|
83
|
+
render_view(buf, view_h, view_w, @pixels)
|
|
51
84
|
buf << "\e[#{3 + view_h};1H"
|
|
52
85
|
render_radar(buf, cols, radar_h, player, enemies, drops, terminals, allies)
|
|
53
86
|
render_crosshair(buf, view_h, view_w, cols, player)
|
|
@@ -132,7 +165,68 @@ module Termfront
|
|
|
132
165
|
|
|
133
166
|
private
|
|
134
167
|
|
|
168
|
+
def current_size
|
|
169
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
170
|
+
if @size_cache.nil? || now - @size_cache_at >= 0.25
|
|
171
|
+
@size_cache = @stdout.winsize
|
|
172
|
+
@size_cache_at = now
|
|
173
|
+
end
|
|
174
|
+
@size_cache
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def build_radar_grid_template
|
|
178
|
+
r = Config::RADAR_RADIUS
|
|
179
|
+
diam = r * 2 + 1
|
|
180
|
+
Array.new(diam) do |ry|
|
|
181
|
+
Array.new(diam) do |rx|
|
|
182
|
+
if ry == r && rx == r
|
|
183
|
+
"^"
|
|
184
|
+
else
|
|
185
|
+
dx = rx - r
|
|
186
|
+
dy = ry - r
|
|
187
|
+
d2 = dx * dx + dy * dy
|
|
188
|
+
if d2 <= r * r
|
|
189
|
+
"."
|
|
190
|
+
elsif d2 <= (r + 1) * (r + 1)
|
|
191
|
+
"#"
|
|
192
|
+
else
|
|
193
|
+
" "
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end.freeze
|
|
197
|
+
end.freeze
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def radar_drop_glyph(drop)
|
|
201
|
+
@radar_drop_glyphs[drop.type] ||= begin
|
|
202
|
+
dc = drop.type.to_s.start_with?("shock") ? "\e[96m" : "\e[93m"
|
|
203
|
+
dl = Weapon::Base.registry[drop.type].new.name[0]
|
|
204
|
+
"#{dc}#{dl}\e[0m".freeze
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def prepare_frame_buffers(view_w, virt_h)
|
|
209
|
+
if @buf_view_w != view_w || @buf_virt_h != virt_h
|
|
210
|
+
@buf_view_w = view_w
|
|
211
|
+
@buf_virt_h = virt_h
|
|
212
|
+
@dists = Array.new(view_w)
|
|
213
|
+
@sides = Array.new(view_w)
|
|
214
|
+
@wtop = Array.new(view_w)
|
|
215
|
+
@wbot = Array.new(view_w)
|
|
216
|
+
@wcol = Array.new(view_w)
|
|
217
|
+
@pixels = Array.new(virt_h) { Array.new(view_w) }
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
135
221
|
def render_hud(buf, cols, player, drops, terminals, status_line)
|
|
222
|
+
buf << hud_shield_line(cols, player, status_line) << "\r\n"
|
|
223
|
+
buf << hud_ammo_line(cols, player, drops, terminals) << "\r\n"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def hud_shield_line(cols, player, status_line)
|
|
227
|
+
key = [player.shield.to_i, status_line, cols]
|
|
228
|
+
return @cached_hud_shield_line if @cached_hud_shield_key == key
|
|
229
|
+
|
|
136
230
|
bar_w = [cols - 20, 10].max
|
|
137
231
|
pct = player.shield / Config::SHIELD_MAX.to_f
|
|
138
232
|
filled = (pct * bar_w).to_i
|
|
@@ -148,11 +242,23 @@ module Termfront
|
|
|
148
242
|
shield_str = "SHIELD #{color}#{"█" * filled}#{"░" * empty}\e[0m #{pct_s}"
|
|
149
243
|
shield_str = "#{shield_str}\e[90m#{status_line}\e[0m" if status_line
|
|
150
244
|
pad = [(cols - bar_w - 15) / 2, 0].max
|
|
151
|
-
|
|
245
|
+
line = TerminalOutput.fit_ansi("#{" " * pad}#{shield_str}", cols)
|
|
152
246
|
|
|
247
|
+
@cached_hud_shield_key = key
|
|
248
|
+
@cached_hud_shield_line = line
|
|
249
|
+
line
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def hud_ammo_line(cols, player, drops, terminals)
|
|
153
253
|
weapon = player.current_weapon
|
|
154
|
-
|
|
254
|
+
can_pickup = drops.any? { |d| d.in_range?(player.x, player.y) }
|
|
255
|
+
can_use_terminal = terminals.any? do |terminal|
|
|
256
|
+
(terminal[:x] - player.x)**2 + (terminal[:y] - player.y)**2 < Config::TERMINAL_USE_RADIUS**2
|
|
257
|
+
end
|
|
258
|
+
key = [weapon.type_id, weapon.ammo, can_pickup, can_use_terminal, cols]
|
|
259
|
+
return @cached_hud_ammo_line if @cached_hud_ammo_key == key
|
|
155
260
|
|
|
261
|
+
wcolor = weapon.type_id.to_s.start_with?("shock") ? "\e[96m" : "\e[97m"
|
|
156
262
|
if weapon.max_ammo
|
|
157
263
|
ammo_bar_w = 12
|
|
158
264
|
ammo_pct = weapon.ammo.to_f / weapon.max_ammo
|
|
@@ -163,10 +269,6 @@ module Termfront
|
|
|
163
269
|
ammo_str = "#{wcolor}#{weapon.name}\e[0m [\xe2\x88\x9e]"
|
|
164
270
|
end
|
|
165
271
|
|
|
166
|
-
can_pickup = drops.any? { |d| d.in_range?(player.x, player.y) }
|
|
167
|
-
can_use_terminal = terminals.any? do |terminal|
|
|
168
|
-
(terminal[:x] - player.x)**2 + (terminal[:y] - player.y)**2 < Config::TERMINAL_USE_RADIUS**2
|
|
169
|
-
end
|
|
170
272
|
interact_str = if can_use_terminal
|
|
171
273
|
"\e[1;96m[E]Use Terminal\e[0m"
|
|
172
274
|
elsif can_pickup
|
|
@@ -175,28 +277,48 @@ module Termfront
|
|
|
175
277
|
"E:interact"
|
|
176
278
|
end
|
|
177
279
|
|
|
178
|
-
line = "#{ammo_str} T:swap #{interact_str} Space:fire"
|
|
179
|
-
|
|
280
|
+
line = TerminalOutput.fit_ansi("#{ammo_str} T:swap #{interact_str} Space:fire", cols)
|
|
281
|
+
|
|
282
|
+
@cached_hud_ammo_key = key
|
|
283
|
+
@cached_hud_ammo_line = line
|
|
284
|
+
line
|
|
180
285
|
end
|
|
181
286
|
|
|
182
287
|
def build_view_pixels(virt_h, view_w, wtop, wbot, wcol)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
288
|
+
ceil_c = Config::CEIL_C
|
|
289
|
+
floor_c = Config::FLOOR_C
|
|
290
|
+
pixels = @pixels
|
|
291
|
+
|
|
292
|
+
c = 0
|
|
293
|
+
while c < view_w
|
|
294
|
+
wt = wtop[c]
|
|
295
|
+
wb = wbot[c]
|
|
296
|
+
wc = wcol[c]
|
|
297
|
+
|
|
298
|
+
vr = 0
|
|
299
|
+
while vr < wt && vr < virt_h
|
|
300
|
+
pixels[vr][c] = ceil_c
|
|
301
|
+
vr += 1
|
|
194
302
|
end
|
|
303
|
+
while vr < wb && vr < virt_h
|
|
304
|
+
pixels[vr][c] = wc
|
|
305
|
+
vr += 1
|
|
306
|
+
end
|
|
307
|
+
while vr < virt_h
|
|
308
|
+
pixels[vr][c] = floor_c
|
|
309
|
+
vr += 1
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
c += 1
|
|
195
313
|
end
|
|
196
|
-
pixels
|
|
197
314
|
end
|
|
198
315
|
|
|
199
316
|
def render_view(buf, view_h, view_w, pixels)
|
|
317
|
+
fg_256 = FG_256
|
|
318
|
+
bg_256 = BG_256
|
|
319
|
+
fg_cache = @fg_truecolor_cache
|
|
320
|
+
bg_cache = @bg_truecolor_cache
|
|
321
|
+
|
|
200
322
|
view_h.times do |r|
|
|
201
323
|
vp0 = r * 2
|
|
202
324
|
vp1 = r * 2 + 1
|
|
@@ -210,16 +332,16 @@ module Termfront
|
|
|
210
332
|
bc = bot_row[c]
|
|
211
333
|
|
|
212
334
|
if tc == bc
|
|
213
|
-
if
|
|
335
|
+
if tc.is_a?(Integer)
|
|
214
336
|
if tc != pbg
|
|
215
|
-
buf <<
|
|
337
|
+
buf << bg_256[tc]
|
|
216
338
|
pbg = tc
|
|
217
339
|
pfg = nil
|
|
218
340
|
end
|
|
219
341
|
buf << " "
|
|
220
342
|
else
|
|
221
343
|
if tc != pfg || pbg
|
|
222
|
-
buf <<
|
|
344
|
+
buf << (fg_cache[tc] ||= "\e[38;2;#{tc}m".freeze)
|
|
223
345
|
pfg = tc
|
|
224
346
|
pbg = nil
|
|
225
347
|
end
|
|
@@ -227,7 +349,9 @@ module Termfront
|
|
|
227
349
|
end
|
|
228
350
|
else
|
|
229
351
|
if tc != pfg || bc != pbg
|
|
230
|
-
|
|
352
|
+
fg = tc.is_a?(Integer) ? fg_256[tc] : (fg_cache[tc] ||= "\e[38;2;#{tc}m".freeze)
|
|
353
|
+
bg = bc.is_a?(Integer) ? bg_256[bc] : (bg_cache[bc] ||= "\e[48;2;#{bc}m".freeze)
|
|
354
|
+
buf << fg << bg
|
|
231
355
|
pfg = tc
|
|
232
356
|
pbg = bc
|
|
233
357
|
end
|
|
@@ -239,104 +363,101 @@ module Termfront
|
|
|
239
363
|
end
|
|
240
364
|
|
|
241
365
|
def render_radar(buf, cols, radar_h, player, enemies, drops, terminals, allies = [])
|
|
242
|
-
buf <<
|
|
366
|
+
buf << @hrule_cache[cols] << "\r\n"
|
|
243
367
|
|
|
244
368
|
r = Config::RADAR_RADIUS
|
|
245
369
|
diam = r * 2 + 1
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
dx = rx - r
|
|
251
|
-
dy = ry - r
|
|
252
|
-
d2 = dx * dx + dy * dy
|
|
253
|
-
if d2 <= r * r
|
|
254
|
-
grid[ry][rx] = "."
|
|
255
|
-
elsif d2 <= (r + 1) * (r + 1)
|
|
256
|
-
grid[ry][rx] = "#"
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
grid[r][r] = "^"
|
|
370
|
+
range = Config::RADAR_RANGE
|
|
371
|
+
range_sq = Config::RADAR_RANGE_SQ
|
|
372
|
+
r_sq = r * r
|
|
373
|
+
grid = @radar_grid_template
|
|
261
374
|
|
|
262
375
|
cos_a = Math.cos(-player.angle + Math::PI / 2)
|
|
263
376
|
sin_a = Math.sin(-player.angle + Math::PI / 2)
|
|
264
|
-
|
|
377
|
+
px = player.x
|
|
378
|
+
py = player.y
|
|
379
|
+
|
|
380
|
+
enemy_cells = @radar_enemy_cells
|
|
381
|
+
drop_cells = @radar_drop_cells
|
|
382
|
+
terminal_cells = @radar_terminal_cells
|
|
383
|
+
ally_cells = @radar_ally_cells
|
|
384
|
+
enemy_cells.clear
|
|
385
|
+
drop_cells.clear
|
|
386
|
+
terminal_cells.clear
|
|
387
|
+
ally_cells.clear
|
|
388
|
+
|
|
265
389
|
enemies.each do |e|
|
|
266
390
|
next unless e.alive
|
|
267
391
|
|
|
268
|
-
ex = e.x -
|
|
269
|
-
ey = e.y -
|
|
270
|
-
|
|
271
|
-
next if dist > Config::RADAR_RANGE
|
|
392
|
+
ex = e.x - px
|
|
393
|
+
ey = e.y - py
|
|
394
|
+
next if ex * ex + ey * ey > range_sq
|
|
272
395
|
|
|
273
396
|
rx = -(ex * cos_a - ey * sin_a)
|
|
274
397
|
ry = -(ex * sin_a + ey * cos_a)
|
|
275
|
-
sx = r + (rx /
|
|
276
|
-
sy = r + (ry /
|
|
277
|
-
next
|
|
398
|
+
sx = r + (rx / range * r).round
|
|
399
|
+
sy = r + (ry / range * r).round
|
|
400
|
+
next if sx < 0 || sx >= diam || sy < 0 || sy >= diam
|
|
278
401
|
|
|
279
|
-
|
|
280
|
-
|
|
402
|
+
dxr = sx - r
|
|
403
|
+
dyr = sy - r
|
|
404
|
+
next if dxr * dxr + dyr * dyr > r_sq
|
|
281
405
|
|
|
282
|
-
enemy_cells[
|
|
406
|
+
enemy_cells[sy * diam + sx] = e.sprite_id
|
|
283
407
|
end
|
|
284
408
|
|
|
285
|
-
drop_cells = {}
|
|
286
409
|
drops.each do |d|
|
|
287
|
-
ex = d.x -
|
|
288
|
-
ey = d.y -
|
|
289
|
-
|
|
290
|
-
next if dist > Config::RADAR_RANGE
|
|
410
|
+
ex = d.x - px
|
|
411
|
+
ey = d.y - py
|
|
412
|
+
next if ex * ex + ey * ey > range_sq
|
|
291
413
|
|
|
292
414
|
rx = -(ex * cos_a - ey * sin_a)
|
|
293
415
|
ry = -(ex * sin_a + ey * cos_a)
|
|
294
|
-
sx = r + (rx /
|
|
295
|
-
sy = r + (ry /
|
|
296
|
-
next
|
|
416
|
+
sx = r + (rx / range * r).round
|
|
417
|
+
sy = r + (ry / range * r).round
|
|
418
|
+
next if sx < 0 || sx >= diam || sy < 0 || sy >= diam
|
|
297
419
|
|
|
298
|
-
|
|
299
|
-
|
|
420
|
+
dxr = sx - r
|
|
421
|
+
dyr = sy - r
|
|
422
|
+
next if dxr * dxr + dyr * dyr > r_sq
|
|
300
423
|
|
|
301
|
-
drop_cells[
|
|
424
|
+
drop_cells[sy * diam + sx] = d
|
|
302
425
|
end
|
|
303
426
|
|
|
304
|
-
terminal_cells = {}
|
|
305
427
|
terminals.each do |terminal|
|
|
306
|
-
ex = terminal[:x] -
|
|
307
|
-
ey = terminal[:y] -
|
|
308
|
-
|
|
309
|
-
next if dist > Config::RADAR_RANGE
|
|
428
|
+
ex = terminal[:x] - px
|
|
429
|
+
ey = terminal[:y] - py
|
|
430
|
+
next if ex * ex + ey * ey > range_sq
|
|
310
431
|
|
|
311
432
|
rx = -(ex * cos_a - ey * sin_a)
|
|
312
433
|
ry = -(ex * sin_a + ey * cos_a)
|
|
313
|
-
sx = r + (rx /
|
|
314
|
-
sy = r + (ry /
|
|
315
|
-
next
|
|
434
|
+
sx = r + (rx / range * r).round
|
|
435
|
+
sy = r + (ry / range * r).round
|
|
436
|
+
next if sx < 0 || sx >= diam || sy < 0 || sy >= diam
|
|
316
437
|
|
|
317
|
-
|
|
318
|
-
|
|
438
|
+
dxr = sx - r
|
|
439
|
+
dyr = sy - r
|
|
440
|
+
next if dxr * dxr + dyr * dyr > r_sq
|
|
319
441
|
|
|
320
|
-
terminal_cells[
|
|
442
|
+
terminal_cells[sy * diam + sx] = terminal
|
|
321
443
|
end
|
|
322
444
|
|
|
323
|
-
ally_cells = {}
|
|
324
445
|
allies.each do |ally|
|
|
325
|
-
ex = ally.x -
|
|
326
|
-
ey = ally.y -
|
|
327
|
-
|
|
328
|
-
next if dist > Config::RADAR_RANGE
|
|
446
|
+
ex = ally.x - px
|
|
447
|
+
ey = ally.y - py
|
|
448
|
+
next if ex * ex + ey * ey > range_sq
|
|
329
449
|
|
|
330
450
|
rx = -(ex * cos_a - ey * sin_a)
|
|
331
451
|
ry = -(ex * sin_a + ey * cos_a)
|
|
332
|
-
sx = r + (rx /
|
|
333
|
-
sy = r + (ry /
|
|
334
|
-
next
|
|
452
|
+
sx = r + (rx / range * r).round
|
|
453
|
+
sy = r + (ry / range * r).round
|
|
454
|
+
next if sx < 0 || sx >= diam || sy < 0 || sy >= diam
|
|
335
455
|
|
|
336
|
-
|
|
337
|
-
|
|
456
|
+
dxr = sx - r
|
|
457
|
+
dyr = sy - r
|
|
458
|
+
next if dxr * dxr + dyr * dyr > r_sq
|
|
338
459
|
|
|
339
|
-
ally_cells[
|
|
460
|
+
ally_cells[sy * diam + sx] = true
|
|
340
461
|
end
|
|
341
462
|
|
|
342
463
|
alive_count = enemies.count(&:alive)
|
|
@@ -344,220 +465,258 @@ module Termfront
|
|
|
344
465
|
info_lines = [
|
|
345
466
|
"Enemies: #{alive_count}/#{total_count}",
|
|
346
467
|
"Heading: #{format("%.0f", (player.angle % (Math::PI * 2)) * 180 / Math::PI)}\xC2\xB0",
|
|
347
|
-
"Pos: (#{"%.1f" %
|
|
468
|
+
"Pos: (#{"%.1f" % px}, #{"%.1f" % py}) T:terminal"
|
|
348
469
|
]
|
|
349
470
|
|
|
350
|
-
|
|
351
|
-
|
|
471
|
+
row = 0
|
|
472
|
+
while row < radar_h
|
|
473
|
+
line = @radar_line_buf.clear
|
|
352
474
|
if row < diam
|
|
353
475
|
line << " "
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
line <<
|
|
360
|
-
elsif
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
line <<
|
|
364
|
-
elsif terminal_cells[
|
|
365
|
-
line <<
|
|
476
|
+
cx = 0
|
|
477
|
+
base = row * diam
|
|
478
|
+
while cx < diam
|
|
479
|
+
key = base + cx
|
|
480
|
+
if (etype = enemy_cells[key])
|
|
481
|
+
line << (etype == :executor ? RADAR_EXECUTOR : RADAR_CRAWLER)
|
|
482
|
+
elsif ally_cells[key]
|
|
483
|
+
line << RADAR_ALLY
|
|
484
|
+
elsif (drop = drop_cells[key])
|
|
485
|
+
line << radar_drop_glyph(drop)
|
|
486
|
+
elsif terminal_cells[key]
|
|
487
|
+
line << RADAR_TERMINAL
|
|
366
488
|
elsif row == r && cx == r
|
|
367
|
-
line <<
|
|
489
|
+
line << RADAR_PLAYER
|
|
368
490
|
elsif grid[row][cx] == "#"
|
|
369
|
-
line <<
|
|
491
|
+
line << RADAR_WALL
|
|
370
492
|
else
|
|
371
493
|
line << grid[row][cx]
|
|
372
494
|
end
|
|
495
|
+
cx += 1
|
|
373
496
|
end
|
|
374
497
|
line << (row < info_lines.size ? " #{info_lines[row]}" : "")
|
|
375
498
|
end
|
|
376
499
|
buf << TerminalOutput.fit_ansi(line, cols)
|
|
377
500
|
buf << "\r\n" if row < radar_h - 1
|
|
501
|
+
row += 1
|
|
378
502
|
end
|
|
379
503
|
end
|
|
380
504
|
|
|
381
505
|
def overlay_enemies_3d(pixels, view_h, view_w, dists, player, enemies, projectiles, drops)
|
|
382
506
|
dx = Math.cos(player.angle)
|
|
383
507
|
dy = Math.sin(player.angle)
|
|
384
|
-
|
|
385
|
-
|
|
508
|
+
tan_half_fov = Math.tan(Config::FOV / 2.0)
|
|
509
|
+
px = -dy * tan_half_fov
|
|
510
|
+
py = dx * tan_half_fov
|
|
386
511
|
virt_h = view_h * 2
|
|
512
|
+
half_virt_h = virt_h / 2
|
|
513
|
+
half_view_w = view_w / 2.0
|
|
514
|
+
view_w_last = view_w - 1
|
|
387
515
|
inv = 1.0 / (px * dy - py * dx)
|
|
516
|
+
player_x = player.x
|
|
517
|
+
player_y = player.y
|
|
388
518
|
|
|
389
|
-
|
|
519
|
+
@enemy_sprites.clear
|
|
390
520
|
enemies.each do |e|
|
|
391
521
|
next unless e.alive
|
|
392
522
|
|
|
393
|
-
ex = e.x -
|
|
394
|
-
ey = e.y -
|
|
523
|
+
ex = e.x - player_x
|
|
524
|
+
ey = e.y - player_y
|
|
395
525
|
tx = inv * (dy * ex - dx * ey)
|
|
396
526
|
tz = inv * (-py * ex + px * ey)
|
|
397
527
|
next if tz < 0.2
|
|
398
528
|
|
|
399
|
-
|
|
529
|
+
@enemy_sprites << [tz, tx, e]
|
|
400
530
|
end
|
|
401
531
|
|
|
402
|
-
proj_sprites
|
|
532
|
+
@proj_sprites.clear
|
|
403
533
|
projectiles.each do |p|
|
|
404
|
-
ex = p.x -
|
|
405
|
-
ey = p.y -
|
|
534
|
+
ex = p.x - player_x
|
|
535
|
+
ey = p.y - player_y
|
|
406
536
|
tx = inv * (dy * ex - dx * ey)
|
|
407
537
|
tz = inv * (-py * ex + px * ey)
|
|
408
538
|
next if tz < 0.2
|
|
409
539
|
|
|
410
|
-
proj_sprites << [tz, tx, p]
|
|
540
|
+
@proj_sprites << [tz, tx, p]
|
|
411
541
|
end
|
|
412
542
|
|
|
413
|
-
|
|
543
|
+
@enemy_sprites.sort! { |a, b| b[0] <=> a[0] }
|
|
414
544
|
|
|
415
|
-
|
|
416
|
-
sx = (
|
|
545
|
+
@enemy_sprites.each do |tz, tx, e|
|
|
546
|
+
sx = (half_view_w * (1 + tx / tz)).to_i
|
|
417
547
|
sprite_h = (virt_h / tz).to_i
|
|
418
|
-
draw_top =
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
548
|
+
draw_top = half_virt_h - sprite_h / 2
|
|
549
|
+
draw_top = 0 if draw_top < 0
|
|
550
|
+
draw_bot = half_virt_h + sprite_h / 2
|
|
551
|
+
draw_bot = virt_h if draw_bot > virt_h
|
|
552
|
+
sprite_w = sprite_h / 2
|
|
553
|
+
start_x = sx - sprite_w / 2
|
|
554
|
+
start_x = 0 if start_x < 0
|
|
555
|
+
end_x = sx + sprite_w / 2
|
|
556
|
+
end_x = view_w_last if end_x > view_w_last
|
|
423
557
|
|
|
424
558
|
actual_h = draw_bot - draw_top
|
|
425
559
|
actual_w = end_x - start_x + 1
|
|
426
560
|
next if actual_h < 1 || actual_w < 1
|
|
427
561
|
|
|
428
|
-
|
|
562
|
+
sprite_id = e.sprite_id
|
|
563
|
+
fallback_color = sprite_id == :executor ? "100;60;200" : "220;140;30"
|
|
429
564
|
use_shape = actual_h >= 6
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
565
|
+
r_top = (draw_top + 1) >> 1
|
|
566
|
+
r_bot = draw_bot >> 1
|
|
567
|
+
actual_h_f = actual_h.to_f
|
|
568
|
+
actual_w_f = actual_w.to_f
|
|
569
|
+
|
|
570
|
+
c = start_x
|
|
571
|
+
while c <= end_x
|
|
572
|
+
if c >= 0 && c < view_w && dists[c] >= tz
|
|
573
|
+
nx = (c - start_x) / actual_w_f
|
|
574
|
+
|
|
575
|
+
r = r_top
|
|
576
|
+
while r < r_bot
|
|
577
|
+
vp0 = r << 1
|
|
578
|
+
vp1 = vp0 + 1
|
|
579
|
+
top_in = vp0 >= draw_top && vp0 < draw_bot
|
|
580
|
+
bot_in = vp1 >= draw_top && vp1 < draw_bot
|
|
581
|
+
|
|
582
|
+
if top_in || bot_in
|
|
583
|
+
if use_shape
|
|
584
|
+
ny0 = top_in ? (vp0 - draw_top) / actual_h_f : nil
|
|
585
|
+
ny1 = bot_in ? (vp1 - draw_top) / actual_h_f : nil
|
|
586
|
+
top_color = ny0 ? Sprite.for(sprite_id, nx, ny0) : nil
|
|
587
|
+
bot_color = ny1 ? Sprite.for(sprite_id, nx, ny1) : nil
|
|
588
|
+
if top_color || bot_color
|
|
589
|
+
pixels[vp0][c] = top_color if top_color
|
|
590
|
+
pixels[vp1][c] = bot_color if bot_color
|
|
591
|
+
end
|
|
592
|
+
else
|
|
593
|
+
pixels[vp0][c] = fallback_color if top_in
|
|
594
|
+
pixels[vp1][c] = fallback_color if bot_in
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
r += 1
|
|
458
598
|
end
|
|
459
599
|
end
|
|
600
|
+
c += 1
|
|
460
601
|
end
|
|
461
602
|
|
|
462
603
|
next unless e.max_hp > 1
|
|
463
604
|
|
|
464
|
-
bar_row =
|
|
605
|
+
bar_row = r_top - 1
|
|
465
606
|
next unless bar_row >= 0 && bar_row < view_h
|
|
466
607
|
|
|
467
|
-
bar_w =
|
|
468
|
-
bar_sx =
|
|
469
|
-
|
|
608
|
+
bar_w = actual_w > 2 ? actual_w : 2
|
|
609
|
+
bar_sx = sx - bar_w / 2
|
|
610
|
+
bar_sx = 0 if bar_sx < 0
|
|
611
|
+
bar_ex = bar_sx + bar_w - 1
|
|
612
|
+
bar_ex = view_w_last if bar_ex > view_w_last
|
|
470
613
|
hp_pct = e.hp.to_f / e.max_hp
|
|
471
614
|
filled = (hp_pct * (bar_ex - bar_sx + 1)).ceil
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
615
|
+
bar_vp0 = bar_row << 1
|
|
616
|
+
bar_vp1 = bar_vp0 + 1
|
|
617
|
+
c = bar_sx
|
|
618
|
+
while c <= bar_ex
|
|
619
|
+
if c >= 0 && c < view_w && dists[c] >= tz
|
|
620
|
+
ci = c - bar_sx
|
|
621
|
+
color = ci < filled ? "0;200;0" : "200;0;0"
|
|
622
|
+
pixels[bar_vp0][c] = color
|
|
623
|
+
pixels[bar_vp1][c] = color
|
|
624
|
+
end
|
|
625
|
+
c += 1
|
|
480
626
|
end
|
|
481
627
|
end
|
|
482
628
|
|
|
483
629
|
# Render weapon drops
|
|
484
|
-
drop_sprites
|
|
630
|
+
@drop_sprites.clear
|
|
485
631
|
drops.each do |d|
|
|
486
|
-
ex = d.x -
|
|
487
|
-
ey = d.y -
|
|
632
|
+
ex = d.x - player_x
|
|
633
|
+
ey = d.y - player_y
|
|
488
634
|
tx = inv * (dy * ex - dx * ey)
|
|
489
635
|
tz = inv * (-py * ex + px * ey)
|
|
490
636
|
next if tz < 0.2
|
|
491
637
|
|
|
492
|
-
drop_sprites << [tz, tx, d]
|
|
638
|
+
@drop_sprites << [tz, tx, d]
|
|
493
639
|
end
|
|
494
|
-
drop_sprites.
|
|
495
|
-
|
|
496
|
-
drop_sprites.each do |tz, tx, d|
|
|
497
|
-
sx = (
|
|
498
|
-
sprite_h = (virt_h / tz * 0.3).to_i.clamp(2,
|
|
499
|
-
ground = (
|
|
500
|
-
draw_bot =
|
|
501
|
-
draw_top =
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
640
|
+
@drop_sprites.sort! { |a, b| b[0] <=> a[0] }
|
|
641
|
+
|
|
642
|
+
@drop_sprites.each do |tz, tx, d|
|
|
643
|
+
sx = (half_view_w * (1 + tx / tz)).to_i
|
|
644
|
+
sprite_h = (virt_h / tz * 0.3).to_i.clamp(2, half_virt_h)
|
|
645
|
+
ground = (half_virt_h + virt_h / tz * 0.35).to_i
|
|
646
|
+
draw_bot = ground < virt_h ? ground : virt_h
|
|
647
|
+
draw_top = draw_bot - sprite_h
|
|
648
|
+
draw_top = 0 if draw_top < 0
|
|
649
|
+
sprite_w = (sprite_h / 2).clamp(1, 6)
|
|
650
|
+
start_x = sx - sprite_w / 2
|
|
651
|
+
start_x = 0 if start_x < 0
|
|
652
|
+
end_x = sx + sprite_w / 2
|
|
653
|
+
end_x = view_w_last if end_x > view_w_last
|
|
505
654
|
|
|
506
655
|
color = d.sprite_color
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
656
|
+
r_top = (draw_top + 1) >> 1
|
|
657
|
+
r_bot = draw_bot >> 1
|
|
658
|
+
|
|
659
|
+
c = start_x
|
|
660
|
+
while c <= end_x
|
|
661
|
+
if c >= 0 && c < view_w && dists[c] >= tz
|
|
662
|
+
r = r_top
|
|
663
|
+
while r < r_bot
|
|
664
|
+
if r >= 0 && r < view_h
|
|
665
|
+
vp0 = r << 1
|
|
666
|
+
vp1 = vp0 + 1
|
|
667
|
+
top_in = vp0 >= draw_top && vp0 < draw_bot
|
|
668
|
+
bot_in = vp1 >= draw_top && vp1 < draw_bot
|
|
669
|
+
if top_in || bot_in
|
|
670
|
+
pixels[vp0][c] = color if top_in
|
|
671
|
+
pixels[vp1][c] = color if bot_in
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
r += 1
|
|
675
|
+
end
|
|
525
676
|
end
|
|
677
|
+
c += 1
|
|
526
678
|
end
|
|
527
679
|
end
|
|
528
680
|
|
|
529
681
|
# Render projectiles
|
|
530
|
-
proj_sprites.
|
|
531
|
-
proj_sprites.each do |tz, tx, p|
|
|
532
|
-
sx = (
|
|
682
|
+
@proj_sprites.sort! { |a, b| b[0] <=> a[0] }
|
|
683
|
+
@proj_sprites.each do |tz, tx, p|
|
|
684
|
+
sx = (half_view_w * (1 + tx / tz)).to_i
|
|
533
685
|
pw = (4.0 / tz).ceil.clamp(1, 5)
|
|
534
686
|
ph = (virt_h / tz * 0.15).ceil.clamp(2, 6)
|
|
535
|
-
|
|
536
|
-
draw_top =
|
|
537
|
-
draw_bot =
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
687
|
+
draw_top = half_virt_h - ph / 2
|
|
688
|
+
draw_top = 0 if draw_top < 0
|
|
689
|
+
draw_bot = half_virt_h + ph / 2
|
|
690
|
+
draw_bot = draw_top + 2 if draw_bot < draw_top + 2
|
|
691
|
+
draw_bot = virt_h if draw_bot > virt_h
|
|
692
|
+
start_x = sx - pw / 2
|
|
693
|
+
start_x = 0 if start_x < 0
|
|
694
|
+
end_x = sx + pw / 2
|
|
695
|
+
end_x = view_w_last if end_x > view_w_last
|
|
696
|
+
proj_color = p.type == :executor ? "94;94;255" : "255;210;80"
|
|
697
|
+
r_top = (draw_top + 1) >> 1
|
|
698
|
+
r_bot = draw_bot >> 1
|
|
699
|
+
r_bot = r_top + 1 if r_bot < r_top + 1
|
|
700
|
+
|
|
701
|
+
c = start_x
|
|
702
|
+
while c <= end_x
|
|
703
|
+
if c >= 0 && c < view_w && dists[c] >= tz
|
|
704
|
+
r = r_top
|
|
705
|
+
while r < r_bot
|
|
706
|
+
if r >= 0 && r < view_h
|
|
707
|
+
vp0 = r << 1
|
|
708
|
+
vp1 = vp0 + 1
|
|
709
|
+
top_in = vp0 >= draw_top && vp0 < draw_bot
|
|
710
|
+
bot_in = vp1 >= draw_top && vp1 < draw_bot
|
|
711
|
+
if top_in || bot_in
|
|
712
|
+
pixels[vp0][c] = proj_color if top_in
|
|
713
|
+
pixels[vp1][c] = proj_color if bot_in
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
r += 1
|
|
717
|
+
end
|
|
560
718
|
end
|
|
719
|
+
c += 1
|
|
561
720
|
end
|
|
562
721
|
end
|
|
563
722
|
end
|
|
@@ -570,7 +729,7 @@ module Termfront
|
|
|
570
729
|
virt_h = view_h * 2
|
|
571
730
|
inv = 1.0 / (px * dy - py * dx)
|
|
572
731
|
|
|
573
|
-
|
|
732
|
+
@ally_sprites.clear
|
|
574
733
|
allies.each do |ally|
|
|
575
734
|
ex = ally.x - player.x
|
|
576
735
|
ey = ally.y - player.y
|
|
@@ -578,11 +737,11 @@ module Termfront
|
|
|
578
737
|
tz = inv * (-py * ex + px * ey)
|
|
579
738
|
next if tz < 0.2
|
|
580
739
|
|
|
581
|
-
|
|
740
|
+
@ally_sprites << [tz, tx, ally]
|
|
582
741
|
end
|
|
583
|
-
|
|
742
|
+
@ally_sprites.sort! { |a, b| b[0] <=> a[0] }
|
|
584
743
|
|
|
585
|
-
|
|
744
|
+
@ally_sprites.each do |tz, tx, ally|
|
|
586
745
|
sx = ((view_w / 2.0) * (1 + tx / tz)).to_i
|
|
587
746
|
sprite_h = (virt_h / tz).to_i
|
|
588
747
|
draw_top = [(virt_h / 2 - sprite_h / 2), 0].max
|
|
@@ -690,17 +849,17 @@ module Termfront
|
|
|
690
849
|
|
|
691
850
|
def ansi_fg(color)
|
|
692
851
|
if color.is_a?(Integer)
|
|
693
|
-
|
|
852
|
+
FG_256[color]
|
|
694
853
|
else
|
|
695
|
-
"\e[38;2;#{color}m"
|
|
854
|
+
@fg_truecolor_cache[color] ||= "\e[38;2;#{color}m".freeze
|
|
696
855
|
end
|
|
697
856
|
end
|
|
698
857
|
|
|
699
858
|
def ansi_bg(color)
|
|
700
859
|
if color.is_a?(Integer)
|
|
701
|
-
|
|
860
|
+
BG_256[color]
|
|
702
861
|
else
|
|
703
|
-
"\e[48;2;#{color}m"
|
|
862
|
+
@bg_truecolor_cache[color] ||= "\e[48;2;#{color}m".freeze
|
|
704
863
|
end
|
|
705
864
|
end
|
|
706
865
|
end
|