termfront 0.1.4 → 0.1.5
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 +25 -0
- data/lib/termfront/config.rb +2 -1
- data/lib/termfront/game.rb +2 -1
- data/lib/termfront/network/client.rb +1 -1
- data/lib/termfront/network/server.rb +32 -6
- data/lib/termfront/renderer.rb +128 -73
- data/lib/termfront/terminal_output.rb +1 -6
- data/lib/termfront/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 68852aaca6be7127d8b644aae4670a56e72d5528b249dc081d72ba0d22c5f74a
|
|
4
|
+
data.tar.gz: 440209c869681acd0f9d77d65fb367e6d055c2ffe3a5a57821dd530912199ecd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6efb979956fa649ee558ad0190547fd70c62fda5ef8b8ca4dc06f513c15c87c02c74151756486615a0cc21301e6b45d6f62e01e58230ae28b574ceb3570053ae
|
|
7
|
+
data.tar.gz: 12dfe9fde65aa1517facf4e7e21a37b6db9550e5cc99663801efb33db74f1a1b88af2503c6c6c54be70d60374328009f85334422413737514845602091f70893
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,31 @@ The format is based on Keep a Changelog, and this project follows Semantic Versi
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.5] - 2026-05-25
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Radar distance culling now compares squared distances against `Config::RADAR_RANGE_SQ` instead of taking a square root per entity per frame
|
|
14
|
+
- Renderer reuses the per-column raycast buffers and the virtual pixel grid across frames instead of allocating fresh arrays each tick, only re-allocating when the terminal is resized
|
|
15
|
+
- Build the radar background grid, horizontal rule, and ANSI-styled radar glyphs once and reuse them across frames
|
|
16
|
+
- Look up 256-color SGR escape sequences from a precomputed table and memoize truecolor SGR sequences to avoid rebuilding the same ANSI strings every cell
|
|
17
|
+
- Reuse renderer sprite collection arrays and the radar line buffer across frames, and sort sprites with a comparator block instead of `sort_by`
|
|
18
|
+
- Cache the terminal `winsize` inside the renderer and invalidate the cache from a `SIGWINCH` handler instead of issuing an ioctl every frame
|
|
19
|
+
- PvP hit raycast now culls candidates beyond `MAX_PVP_RANGE` and skips the line-of-sight check when the candidate is already farther than the current best
|
|
20
|
+
- Multiplayer server `broadcast` now serializes each outgoing message once and writes the same JSON line to every recipient
|
|
21
|
+
- PvP server now aggregates outgoing player state on a 30 Hz server tick instead of relaying each incoming state message immediately, reducing TLS write bursts on small VMs
|
|
22
|
+
- PvP match loop `IO.select` timeout shortened from 500 ms to ~16 ms so the new 30 Hz broadcast tick is not delayed by idle reads
|
|
23
|
+
- PvP client opponent interpolation now converges within ~40 ms (lerp factor raised from 15 to 25) so remote players track the new 30 Hz broadcast tick within a single frame
|
|
24
|
+
- Run the title demo, campaign missions, Wavesfight, and PvP at 60 FPS
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- Stopped emitting DEC 2026 synchronized update escape sequences around each frame and removed the `TERMFRONT_SYNC_UPDATES` environment switch; non-supporting terminals (some SSH paths, older xterm) were paying parsing cost for shapes they ignore, while supporting terminals show no visible regression
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Wavesfight wave advance now fully restores each surviving and revived player's shield to `Config::SHIELD_MAX`; previously only a +35 partial recovery was applied, which left revived players at 35%
|
|
33
|
+
|
|
9
34
|
## [0.1.34] - 2026-05-25
|
|
10
35
|
|
|
11
36
|
### Fixed
|
data/lib/termfront/config.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Termfront
|
|
4
4
|
module Config
|
|
5
|
-
FRAME_DT = 1.0 /
|
|
5
|
+
FRAME_DT = 1.0 / 60.0
|
|
6
6
|
FOV = 66.0 * Math::PI / 180.0
|
|
7
7
|
PLAYER_RADIUS = 0.2
|
|
8
8
|
KEY_TIMEOUT = 5
|
|
@@ -29,6 +29,7 @@ module Termfront
|
|
|
29
29
|
|
|
30
30
|
RADAR_RADIUS = 3
|
|
31
31
|
RADAR_RANGE = 12.0
|
|
32
|
+
RADAR_RANGE_SQ = RADAR_RANGE * RADAR_RANGE
|
|
32
33
|
|
|
33
34
|
PVP_PORT = 7777
|
|
34
35
|
PVP_DEFAULT_ADDRESS = "termfront.gamelinks007.net:443"
|
data/lib/termfront/game.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Termfront
|
|
|
10
10
|
@scene_player = ScenePlayer.new(@stdout, audio: @audio)
|
|
11
11
|
@demo_player = DemoPlayer.new(@stdout, @renderer)
|
|
12
12
|
@difficulty = nil
|
|
13
|
+
Signal.trap("WINCH") { @renderer.invalidate_size_cache! }
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def start
|
|
@@ -413,7 +414,7 @@ module Termfront
|
|
|
413
414
|
end
|
|
414
415
|
|
|
415
416
|
def replenish_wavesfight_loadout
|
|
416
|
-
@player.shield =
|
|
417
|
+
@player.shield = Config::SHIELD_MAX
|
|
417
418
|
@player.health = [@player.health + 20.0, Config::HEALTH_MAX].min
|
|
418
419
|
@player.last_damage = -Config::SHIELD_DELAY
|
|
419
420
|
@player.dead = false
|
|
@@ -392,7 +392,7 @@ module Termfront
|
|
|
392
392
|
|
|
393
393
|
def interpolate_opponents(dt)
|
|
394
394
|
@remotes.each_value do |remote|
|
|
395
|
-
remote[:lerp_t] = [remote[:lerp_t] + dt *
|
|
395
|
+
remote[:lerp_t] = [remote[:lerp_t] + dt * 25.0, 1.0].min
|
|
396
396
|
t = remote[:lerp_t]
|
|
397
397
|
remote[:render].x = remote[:prev].x + (remote[:current].x - remote[:prev].x) * t
|
|
398
398
|
remote[:render].y = remote[:prev].y + (remote[:current].y - remote[:prev].y) * t
|
|
@@ -28,6 +28,10 @@ module Termfront
|
|
|
28
28
|
}.freeze
|
|
29
29
|
DEFAULT_RATE_LIMIT = 10
|
|
30
30
|
MAX_DROPPED_MSGS = 200
|
|
31
|
+
MAX_PVP_RANGE = 30.0
|
|
32
|
+
STATE_BROADCAST_HZ = 30
|
|
33
|
+
STATE_BROADCAST_DT = 1.0 / STATE_BROADCAST_HZ
|
|
34
|
+
SELECT_TIMEOUT = 1.0 / 60.0
|
|
31
35
|
PVP_MAP = [
|
|
32
36
|
"####################",
|
|
33
37
|
"#........##........#",
|
|
@@ -271,6 +275,7 @@ module Termfront
|
|
|
271
275
|
match_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
272
276
|
last_activity = match_start
|
|
273
277
|
last_tick_at = match_start
|
|
278
|
+
last_state_flush_at = match_start
|
|
274
279
|
|
|
275
280
|
loop do
|
|
276
281
|
sockets = roster.filter_map do |player|
|
|
@@ -281,7 +286,7 @@ module Termfront
|
|
|
281
286
|
end
|
|
282
287
|
break if sockets.empty?
|
|
283
288
|
|
|
284
|
-
readable, = IO.select(sockets, nil, nil,
|
|
289
|
+
readable, = IO.select(sockets, nil, nil, SELECT_TIMEOUT)
|
|
285
290
|
|
|
286
291
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
287
292
|
if (reason = match_timeout_reason(now, match_start, last_activity))
|
|
@@ -295,6 +300,11 @@ module Termfront
|
|
|
295
300
|
roster.each { |player| regen_player(player, dt, now) }
|
|
296
301
|
last_tick_at = now
|
|
297
302
|
|
|
303
|
+
if now - last_state_flush_at >= STATE_BROADCAST_DT
|
|
304
|
+
flush_pending_states(roster)
|
|
305
|
+
last_state_flush_at = now
|
|
306
|
+
end
|
|
307
|
+
|
|
298
308
|
next unless readable
|
|
299
309
|
|
|
300
310
|
last_activity = now
|
|
@@ -388,7 +398,7 @@ module Termfront
|
|
|
388
398
|
msg = msg.merge(ff: ff || 0)
|
|
389
399
|
end
|
|
390
400
|
msg = msg.merge(s: player[:shield].round(1), h: player[:health].round(1))
|
|
391
|
-
|
|
401
|
+
player[:pending_state] = msg.merge(from: player[:id])
|
|
392
402
|
when "hit"
|
|
393
403
|
route_hit(roster, player, msg, Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
|
394
404
|
when "dead"
|
|
@@ -430,11 +440,12 @@ module Termfront
|
|
|
430
440
|
oy = other[:y] - attacker[:y]
|
|
431
441
|
dot = ox * dx + oy * dy
|
|
432
442
|
next if dot < 0.1
|
|
443
|
+
next if dot > MAX_PVP_RANGE
|
|
444
|
+
next unless dot < best_dot
|
|
433
445
|
|
|
434
446
|
perp = (ox * (-dy) + oy * dx).abs
|
|
435
447
|
next if perp > weapon.hit_width
|
|
436
448
|
next unless pvp_map.line_of_sight?(attacker[:x], attacker[:y], other[:x], other[:y])
|
|
437
|
-
next unless dot < best_dot
|
|
438
449
|
|
|
439
450
|
best = other
|
|
440
451
|
best_dot = dot
|
|
@@ -450,15 +461,30 @@ module Termfront
|
|
|
450
461
|
end
|
|
451
462
|
|
|
452
463
|
def broadcast(roster, msg, except: nil)
|
|
464
|
+
line = JSON.generate(msg) + "\n"
|
|
453
465
|
roster.each do |player|
|
|
454
466
|
next if player[:id] == except
|
|
455
467
|
|
|
456
|
-
|
|
468
|
+
write_line(player[:socket], line)
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def flush_pending_states(roster)
|
|
473
|
+
roster.each do |player|
|
|
474
|
+
state = player[:pending_state]
|
|
475
|
+
next unless state
|
|
476
|
+
|
|
477
|
+
broadcast(roster, state, except: player[:id])
|
|
478
|
+
player[:pending_state] = nil
|
|
457
479
|
end
|
|
458
480
|
end
|
|
459
481
|
|
|
460
482
|
def send_json(socket, msg)
|
|
461
|
-
socket
|
|
483
|
+
write_line(socket, JSON.generate(msg) + "\n")
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def write_line(socket, line)
|
|
487
|
+
socket.write(line)
|
|
462
488
|
rescue Errno::EPIPE, Errno::ECONNRESET, IOError, OpenSSL::SSL::SSLError
|
|
463
489
|
nil
|
|
464
490
|
end
|
|
@@ -919,7 +945,7 @@ module Termfront
|
|
|
919
945
|
|
|
920
946
|
def replenish_wavesfight_roster(roster, session)
|
|
921
947
|
roster.each do |player|
|
|
922
|
-
player[:shield] =
|
|
948
|
+
player[:shield] = Config::SHIELD_MAX
|
|
923
949
|
player[:health] = [player[:health] + 20.0, Config::HEALTH_MAX].min
|
|
924
950
|
player[:last_damage] = session[:clock] - Config::SHIELD_DELAY
|
|
925
951
|
player[:alive] = true
|
data/lib/termfront/renderer.rb
CHANGED
|
@@ -2,12 +2,40 @@
|
|
|
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
|
+
@fg_truecolor_cache = {}
|
|
23
|
+
@bg_truecolor_cache = {}
|
|
24
|
+
@enemy_sprites = []
|
|
25
|
+
@proj_sprites = []
|
|
26
|
+
@drop_sprites = []
|
|
27
|
+
@ally_sprites = []
|
|
28
|
+
@radar_line_buf = +""
|
|
29
|
+
@size_cache = nil
|
|
30
|
+
@size_cache_at = -Float::INFINITY
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def invalidate_size_cache!
|
|
34
|
+
@size_cache = nil
|
|
7
35
|
end
|
|
8
36
|
|
|
9
37
|
def render(player:, map:, enemies:, projectiles:, drops:, terminals: [], status_line: nil, allies: [])
|
|
10
|
-
rows, cols =
|
|
38
|
+
rows, cols = current_size
|
|
11
39
|
rows = [rows, 6].max
|
|
12
40
|
cols = [cols, 20].max
|
|
13
41
|
|
|
@@ -16,38 +44,35 @@ module Termfront
|
|
|
16
44
|
view_w = cols
|
|
17
45
|
virt_h = view_h * 2
|
|
18
46
|
|
|
47
|
+
prepare_frame_buffers(view_w, virt_h)
|
|
48
|
+
|
|
19
49
|
dx = Math.cos(player.angle)
|
|
20
50
|
dy = Math.sin(player.angle)
|
|
21
51
|
plane_x = -dy * Math.tan(Config::FOV / 2.0)
|
|
22
52
|
plane_y = dx * Math.tan(Config::FOV / 2.0)
|
|
23
53
|
|
|
24
|
-
dists = Array.new(view_w)
|
|
25
|
-
sides = Array.new(view_w)
|
|
26
54
|
view_w.times do |c|
|
|
27
55
|
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)
|
|
56
|
+
@dists[c], @sides[c] = cast_ray(map, player.x, player.y, dx + plane_x * cam, dy + plane_y * cam)
|
|
29
57
|
end
|
|
30
58
|
|
|
31
59
|
vmid = virt_h / 2.0
|
|
32
|
-
wtop = Array.new(view_w)
|
|
33
|
-
wbot = Array.new(view_w)
|
|
34
|
-
wcol = Array.new(view_w)
|
|
35
60
|
view_w.times do |c|
|
|
36
|
-
d = dists[c]
|
|
61
|
+
d = @dists[c]
|
|
37
62
|
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])
|
|
63
|
+
@wtop[c] = [(vmid - lh / 2.0).to_i, 0].max
|
|
64
|
+
@wbot[c] = [(vmid + lh / 2.0).to_i, virt_h].min
|
|
65
|
+
@wcol[c] = Sprite.wall_brightness(d, @sides[c])
|
|
41
66
|
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)
|
|
67
|
+
build_view_pixels(virt_h, view_w, @wtop, @wbot, @wcol)
|
|
68
|
+
overlay_enemies_3d(@pixels, view_h, view_w, @dists, player, enemies, projectiles, drops)
|
|
69
|
+
overlay_allies_3d(@pixels, view_h, view_w, @dists, player, allies)
|
|
70
|
+
overlay_damage_flash(@pixels, view_h, view_w, player)
|
|
46
71
|
|
|
47
72
|
buf = TerminalOutput.begin_frame(home: true)
|
|
48
73
|
|
|
49
74
|
render_hud(buf, cols, player, drops, terminals, status_line)
|
|
50
|
-
render_view(buf, view_h, view_w, pixels)
|
|
75
|
+
render_view(buf, view_h, view_w, @pixels)
|
|
51
76
|
buf << "\e[#{3 + view_h};1H"
|
|
52
77
|
render_radar(buf, cols, radar_h, player, enemies, drops, terminals, allies)
|
|
53
78
|
render_crosshair(buf, view_h, view_w, cols, player)
|
|
@@ -132,6 +157,59 @@ module Termfront
|
|
|
132
157
|
|
|
133
158
|
private
|
|
134
159
|
|
|
160
|
+
def current_size
|
|
161
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
162
|
+
if @size_cache.nil? || now - @size_cache_at >= 0.25
|
|
163
|
+
@size_cache = @stdout.winsize
|
|
164
|
+
@size_cache_at = now
|
|
165
|
+
end
|
|
166
|
+
@size_cache
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def build_radar_grid_template
|
|
170
|
+
r = Config::RADAR_RADIUS
|
|
171
|
+
diam = r * 2 + 1
|
|
172
|
+
Array.new(diam) do |ry|
|
|
173
|
+
Array.new(diam) do |rx|
|
|
174
|
+
if ry == r && rx == r
|
|
175
|
+
"^"
|
|
176
|
+
else
|
|
177
|
+
dx = rx - r
|
|
178
|
+
dy = ry - r
|
|
179
|
+
d2 = dx * dx + dy * dy
|
|
180
|
+
if d2 <= r * r
|
|
181
|
+
"."
|
|
182
|
+
elsif d2 <= (r + 1) * (r + 1)
|
|
183
|
+
"#"
|
|
184
|
+
else
|
|
185
|
+
" "
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end.freeze
|
|
189
|
+
end.freeze
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def radar_drop_glyph(drop)
|
|
193
|
+
@radar_drop_glyphs[drop.type] ||= begin
|
|
194
|
+
dc = drop.type.to_s.start_with?("shock") ? "\e[96m" : "\e[93m"
|
|
195
|
+
dl = Weapon::Base.registry[drop.type].new.name[0]
|
|
196
|
+
"#{dc}#{dl}\e[0m".freeze
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def prepare_frame_buffers(view_w, virt_h)
|
|
201
|
+
if @buf_view_w != view_w || @buf_virt_h != virt_h
|
|
202
|
+
@buf_view_w = view_w
|
|
203
|
+
@buf_virt_h = virt_h
|
|
204
|
+
@dists = Array.new(view_w)
|
|
205
|
+
@sides = Array.new(view_w)
|
|
206
|
+
@wtop = Array.new(view_w)
|
|
207
|
+
@wbot = Array.new(view_w)
|
|
208
|
+
@wcol = Array.new(view_w)
|
|
209
|
+
@pixels = Array.new(virt_h) { Array.new(view_w) }
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
135
213
|
def render_hud(buf, cols, player, drops, terminals, status_line)
|
|
136
214
|
bar_w = [cols - 20, 10].max
|
|
137
215
|
pct = player.shield / Config::SHIELD_MAX.to_f
|
|
@@ -180,9 +258,8 @@ module Termfront
|
|
|
180
258
|
end
|
|
181
259
|
|
|
182
260
|
def build_view_pixels(virt_h, view_w, wtop, wbot, wcol)
|
|
183
|
-
pixels = Array.new(virt_h) { Array.new(view_w) }
|
|
184
261
|
virt_h.times do |vr|
|
|
185
|
-
row = pixels[vr]
|
|
262
|
+
row = @pixels[vr]
|
|
186
263
|
view_w.times do |c|
|
|
187
264
|
row[c] = if vr < wtop[c]
|
|
188
265
|
Config::CEIL_C
|
|
@@ -193,7 +270,6 @@ module Termfront
|
|
|
193
270
|
end
|
|
194
271
|
end
|
|
195
272
|
end
|
|
196
|
-
pixels
|
|
197
273
|
end
|
|
198
274
|
|
|
199
275
|
def render_view(buf, view_h, view_w, pixels)
|
|
@@ -239,25 +315,11 @@ module Termfront
|
|
|
239
315
|
end
|
|
240
316
|
|
|
241
317
|
def render_radar(buf, cols, radar_h, player, enemies, drops, terminals, allies = [])
|
|
242
|
-
buf <<
|
|
318
|
+
buf << @hrule_cache[cols] << "\r\n"
|
|
243
319
|
|
|
244
320
|
r = Config::RADAR_RADIUS
|
|
245
321
|
diam = r * 2 + 1
|
|
246
|
-
|
|
247
|
-
grid = Array.new(diam) { Array.new(diam, " ") }
|
|
248
|
-
diam.times do |ry|
|
|
249
|
-
diam.times do |rx|
|
|
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] = "^"
|
|
322
|
+
grid = @radar_grid_template
|
|
261
323
|
|
|
262
324
|
cos_a = Math.cos(-player.angle + Math::PI / 2)
|
|
263
325
|
sin_a = Math.sin(-player.angle + Math::PI / 2)
|
|
@@ -267,8 +329,7 @@ module Termfront
|
|
|
267
329
|
|
|
268
330
|
ex = e.x - player.x
|
|
269
331
|
ey = e.y - player.y
|
|
270
|
-
|
|
271
|
-
next if dist > Config::RADAR_RANGE
|
|
332
|
+
next if ex * ex + ey * ey > Config::RADAR_RANGE_SQ
|
|
272
333
|
|
|
273
334
|
rx = -(ex * cos_a - ey * sin_a)
|
|
274
335
|
ry = -(ex * sin_a + ey * cos_a)
|
|
@@ -286,8 +347,7 @@ module Termfront
|
|
|
286
347
|
drops.each do |d|
|
|
287
348
|
ex = d.x - player.x
|
|
288
349
|
ey = d.y - player.y
|
|
289
|
-
|
|
290
|
-
next if dist > Config::RADAR_RANGE
|
|
350
|
+
next if ex * ex + ey * ey > Config::RADAR_RANGE_SQ
|
|
291
351
|
|
|
292
352
|
rx = -(ex * cos_a - ey * sin_a)
|
|
293
353
|
ry = -(ex * sin_a + ey * cos_a)
|
|
@@ -305,8 +365,7 @@ module Termfront
|
|
|
305
365
|
terminals.each do |terminal|
|
|
306
366
|
ex = terminal[:x] - player.x
|
|
307
367
|
ey = terminal[:y] - player.y
|
|
308
|
-
|
|
309
|
-
next if dist > Config::RADAR_RANGE
|
|
368
|
+
next if ex * ex + ey * ey > Config::RADAR_RANGE_SQ
|
|
310
369
|
|
|
311
370
|
rx = -(ex * cos_a - ey * sin_a)
|
|
312
371
|
ry = -(ex * sin_a + ey * cos_a)
|
|
@@ -324,8 +383,7 @@ module Termfront
|
|
|
324
383
|
allies.each do |ally|
|
|
325
384
|
ex = ally.x - player.x
|
|
326
385
|
ey = ally.y - player.y
|
|
327
|
-
|
|
328
|
-
next if dist > Config::RADAR_RANGE
|
|
386
|
+
next if ex * ex + ey * ey > Config::RADAR_RANGE_SQ
|
|
329
387
|
|
|
330
388
|
rx = -(ex * cos_a - ey * sin_a)
|
|
331
389
|
ry = -(ex * sin_a + ey * cos_a)
|
|
@@ -348,25 +406,22 @@ module Termfront
|
|
|
348
406
|
]
|
|
349
407
|
|
|
350
408
|
radar_h.times do |row|
|
|
351
|
-
line =
|
|
409
|
+
line = @radar_line_buf.clear
|
|
352
410
|
if row < diam
|
|
353
411
|
line << " "
|
|
354
412
|
diam.times do |cx|
|
|
355
413
|
if (etype = enemy_cells[[row, cx]])
|
|
356
|
-
|
|
357
|
-
line << "#{ec}*\e[0m"
|
|
414
|
+
line << (etype == :executor ? RADAR_EXECUTOR : RADAR_CRAWLER)
|
|
358
415
|
elsif ally_cells[[row, cx]]
|
|
359
|
-
line <<
|
|
416
|
+
line << RADAR_ALLY
|
|
360
417
|
elsif (drop = drop_cells[[row, cx]])
|
|
361
|
-
|
|
362
|
-
dl = Weapon::Base.registry[drop.type].new.name[0]
|
|
363
|
-
line << "#{dc}#{dl}\e[0m"
|
|
418
|
+
line << radar_drop_glyph(drop)
|
|
364
419
|
elsif terminal_cells[[row, cx]]
|
|
365
|
-
line <<
|
|
420
|
+
line << RADAR_TERMINAL
|
|
366
421
|
elsif row == r && cx == r
|
|
367
|
-
line <<
|
|
422
|
+
line << RADAR_PLAYER
|
|
368
423
|
elsif grid[row][cx] == "#"
|
|
369
|
-
line <<
|
|
424
|
+
line << RADAR_WALL
|
|
370
425
|
else
|
|
371
426
|
line << grid[row][cx]
|
|
372
427
|
end
|
|
@@ -386,7 +441,7 @@ module Termfront
|
|
|
386
441
|
virt_h = view_h * 2
|
|
387
442
|
inv = 1.0 / (px * dy - py * dx)
|
|
388
443
|
|
|
389
|
-
|
|
444
|
+
@enemy_sprites.clear
|
|
390
445
|
enemies.each do |e|
|
|
391
446
|
next unless e.alive
|
|
392
447
|
|
|
@@ -396,10 +451,10 @@ module Termfront
|
|
|
396
451
|
tz = inv * (-py * ex + px * ey)
|
|
397
452
|
next if tz < 0.2
|
|
398
453
|
|
|
399
|
-
|
|
454
|
+
@enemy_sprites << [tz, tx, e]
|
|
400
455
|
end
|
|
401
456
|
|
|
402
|
-
proj_sprites
|
|
457
|
+
@proj_sprites.clear
|
|
403
458
|
projectiles.each do |p|
|
|
404
459
|
ex = p.x - player.x
|
|
405
460
|
ey = p.y - player.y
|
|
@@ -407,12 +462,12 @@ module Termfront
|
|
|
407
462
|
tz = inv * (-py * ex + px * ey)
|
|
408
463
|
next if tz < 0.2
|
|
409
464
|
|
|
410
|
-
proj_sprites << [tz, tx, p]
|
|
465
|
+
@proj_sprites << [tz, tx, p]
|
|
411
466
|
end
|
|
412
467
|
|
|
413
|
-
|
|
468
|
+
@enemy_sprites.sort! { |a, b| b[0] <=> a[0] }
|
|
414
469
|
|
|
415
|
-
|
|
470
|
+
@enemy_sprites.each do |tz, tx, e|
|
|
416
471
|
sx = ((view_w / 2.0) * (1 + tx / tz)).to_i
|
|
417
472
|
sprite_h = (virt_h / tz).to_i
|
|
418
473
|
draw_top = [(virt_h / 2 - sprite_h / 2), 0].max
|
|
@@ -481,7 +536,7 @@ module Termfront
|
|
|
481
536
|
end
|
|
482
537
|
|
|
483
538
|
# Render weapon drops
|
|
484
|
-
drop_sprites
|
|
539
|
+
@drop_sprites.clear
|
|
485
540
|
drops.each do |d|
|
|
486
541
|
ex = d.x - player.x
|
|
487
542
|
ey = d.y - player.y
|
|
@@ -489,11 +544,11 @@ module Termfront
|
|
|
489
544
|
tz = inv * (-py * ex + px * ey)
|
|
490
545
|
next if tz < 0.2
|
|
491
546
|
|
|
492
|
-
drop_sprites << [tz, tx, d]
|
|
547
|
+
@drop_sprites << [tz, tx, d]
|
|
493
548
|
end
|
|
494
|
-
drop_sprites.
|
|
549
|
+
@drop_sprites.sort! { |a, b| b[0] <=> a[0] }
|
|
495
550
|
|
|
496
|
-
drop_sprites.each do |tz, tx, d|
|
|
551
|
+
@drop_sprites.each do |tz, tx, d|
|
|
497
552
|
sx = ((view_w / 2.0) * (1 + tx / tz)).to_i
|
|
498
553
|
sprite_h = (virt_h / tz * 0.3).to_i.clamp(2, virt_h / 2)
|
|
499
554
|
ground = (virt_h / 2 + virt_h / tz * 0.35).to_i
|
|
@@ -527,8 +582,8 @@ module Termfront
|
|
|
527
582
|
end
|
|
528
583
|
|
|
529
584
|
# Render projectiles
|
|
530
|
-
proj_sprites.
|
|
531
|
-
proj_sprites.each do |tz, tx, p|
|
|
585
|
+
@proj_sprites.sort! { |a, b| b[0] <=> a[0] }
|
|
586
|
+
@proj_sprites.each do |tz, tx, p|
|
|
532
587
|
sx = ((view_w / 2.0) * (1 + tx / tz)).to_i
|
|
533
588
|
pw = (4.0 / tz).ceil.clamp(1, 5)
|
|
534
589
|
ph = (virt_h / tz * 0.15).ceil.clamp(2, 6)
|
|
@@ -570,7 +625,7 @@ module Termfront
|
|
|
570
625
|
virt_h = view_h * 2
|
|
571
626
|
inv = 1.0 / (px * dy - py * dx)
|
|
572
627
|
|
|
573
|
-
|
|
628
|
+
@ally_sprites.clear
|
|
574
629
|
allies.each do |ally|
|
|
575
630
|
ex = ally.x - player.x
|
|
576
631
|
ey = ally.y - player.y
|
|
@@ -578,11 +633,11 @@ module Termfront
|
|
|
578
633
|
tz = inv * (-py * ex + px * ey)
|
|
579
634
|
next if tz < 0.2
|
|
580
635
|
|
|
581
|
-
|
|
636
|
+
@ally_sprites << [tz, tx, ally]
|
|
582
637
|
end
|
|
583
|
-
|
|
638
|
+
@ally_sprites.sort! { |a, b| b[0] <=> a[0] }
|
|
584
639
|
|
|
585
|
-
|
|
640
|
+
@ally_sprites.each do |tz, tx, ally|
|
|
586
641
|
sx = ((view_w / 2.0) * (1 + tx / tz)).to_i
|
|
587
642
|
sprite_h = (virt_h / tz).to_i
|
|
588
643
|
draw_top = [(virt_h / 2 - sprite_h / 2), 0].max
|
|
@@ -690,17 +745,17 @@ module Termfront
|
|
|
690
745
|
|
|
691
746
|
def ansi_fg(color)
|
|
692
747
|
if color.is_a?(Integer)
|
|
693
|
-
|
|
748
|
+
FG_256[color]
|
|
694
749
|
else
|
|
695
|
-
"\e[38;2;#{color}m"
|
|
750
|
+
@fg_truecolor_cache[color] ||= "\e[38;2;#{color}m".freeze
|
|
696
751
|
end
|
|
697
752
|
end
|
|
698
753
|
|
|
699
754
|
def ansi_bg(color)
|
|
700
755
|
if color.is_a?(Integer)
|
|
701
|
-
|
|
756
|
+
BG_256[color]
|
|
702
757
|
else
|
|
703
|
-
"\e[48;2;#{color}m"
|
|
758
|
+
@bg_truecolor_cache[color] ||= "\e[48;2;#{color}m".freeze
|
|
704
759
|
end
|
|
705
760
|
end
|
|
706
761
|
end
|
|
@@ -5,20 +5,15 @@ module Termfront
|
|
|
5
5
|
module_function
|
|
6
6
|
ANSI_PATTERN = /\e\[[0-9;]*[A-Za-z]/.freeze
|
|
7
7
|
|
|
8
|
-
def sync_updates?
|
|
9
|
-
ENV.fetch("TERMFRONT_SYNC_UPDATES", "1") == "1"
|
|
10
|
-
end
|
|
11
|
-
|
|
12
8
|
def begin_frame(home: false, clear: false)
|
|
13
9
|
buf = +""
|
|
14
|
-
buf << "\e[?2026h" if sync_updates?
|
|
15
10
|
buf << "\e[H" if home
|
|
16
11
|
buf << "\e[2J" if clear
|
|
17
12
|
buf
|
|
18
13
|
end
|
|
19
14
|
|
|
20
15
|
def end_frame
|
|
21
|
-
|
|
16
|
+
""
|
|
22
17
|
end
|
|
23
18
|
|
|
24
19
|
def fit_ansi(text, width)
|
data/lib/termfront/version.rb
CHANGED