termfront 0.1.3 → 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.
@@ -3,6 +3,9 @@
3
3
  module Termfront
4
4
  module Network
5
5
  class WavesfightClient
6
+ ALLOWED_WEAPONS = %w[pistol ar shock_pistol shock_rifle].freeze
7
+ ALLOWED_ENEMY_TYPES = %w[crawler executor].freeze
8
+
6
9
  def initialize(stdout)
7
10
  @stdout = stdout
8
11
  @conn = Connection.new
@@ -14,13 +17,14 @@ module Termfront
14
17
  def run(mission_id:, difficulty:)
15
18
  @queue_mission_id = mission_id
16
19
  @queue_difficulty = difficulty
17
- addr = prompt_address
18
- return unless addr
19
20
 
20
- host, port = addr.include?(":") ? addr.split(":", 2).then { |h, p| [h, p.to_i] } : [addr, Config::PVP_PORT]
21
+ host, port = Config::PVP_DEFAULT_ADDRESS.split(":", 2).then { |h, p| [h, p.to_i] }
22
+ queue_msg = { t: "queue", mode: "wavesfight", mission_id: mission_id, difficulty: difficulty }
23
+ token = ENV["TERMFRONT_PVP_TOKEN"]
24
+ queue_msg[:token] = token if token && !token.empty?
21
25
  begin
22
- @conn.connect(host, port)
23
- @conn.send_msg({ t: "queue", mode: "wavesfight", mission_id: mission_id, difficulty: difficulty })
26
+ @conn.connect(host, port, ca_file: ENV["TERMFRONT_TLS_CA_FILE"])
27
+ @conn.send_msg(queue_msg)
24
28
  rescue StandardError => e
25
29
  show_error("Connection failed: #{e.message}")
26
30
  return
@@ -42,51 +46,6 @@ module Termfront
42
46
 
43
47
  private
44
48
 
45
- def prompt_address
46
- input = Config::PVP_DEFAULT_ADDRESS
47
-
48
- STDIN.raw do |stdin|
49
- loop do
50
- rows, cols = @stdout.winsize
51
- buf = TerminalOutput.begin_frame(home: true, clear: true)
52
- lines = Array.new(rows) { " " * cols }
53
-
54
- title = "Wavesfight Co-op - Enter Server Address"
55
- tc = [(cols - title.size) / 2 + 1, 1].max
56
- lines[rows / 2 - 3] = TerminalOutput.fit_ansi("#{" " * (tc - 1)}\e[1;96m#{title}\e[0m", cols)
57
-
58
- pc = [(cols - input.size - 3) / 2 + 1, 1].max
59
- lines[rows / 2 - 1] = TerminalOutput.fit_ansi("#{" " * (pc - 1)}\e[97m> #{input}\e[5m_\e[0m", cols)
60
-
61
- hint = "(Enter to continue, ESC to cancel)"
62
- hc = [(cols - hint.size) / 2 + 1, 1].max
63
- lines[rows / 2 + 1] = TerminalOutput.fit_ansi("#{" " * (hc - 1)}\e[90m#{hint}\e[0m", cols)
64
-
65
- lines.each_with_index do |line, index|
66
- buf << line
67
- buf << "\r\n" if index < rows - 1
68
- end
69
- buf << TerminalOutput.end_frame
70
- TerminalOutput.write_all(@stdout, buf)
71
-
72
- next unless IO.select([stdin], nil, nil, Config::FRAME_DT)
73
-
74
- begin
75
- data = stdin.read_nonblock(64)
76
- data.each_byte do |b|
77
- case b
78
- when 27 then return nil
79
- when 13, 10 then return input.empty? ? Config::PVP_DEFAULT_ADDRESS : input
80
- when 127, 8 then input = input[0...-1] unless input.empty?
81
- when 32..126 then input << b.chr
82
- end
83
- end
84
- rescue IO::WaitReadable
85
- end
86
- end
87
- end
88
- end
89
-
90
49
  def wait_for_start
91
50
  STDIN.raw do |stdin|
92
51
  loop do
@@ -148,7 +107,10 @@ module Termfront
148
107
  end
149
108
  @enemies = []
150
109
  @projectiles = []
110
+ @drops = []
111
+ @server_drops = []
151
112
  @match_end = nil
113
+ @regen_active = false
152
114
  end
153
115
 
154
116
  def run_game_loop
@@ -192,6 +154,7 @@ module Termfront
192
154
 
193
155
  def handle_player_actions(keys)
194
156
  @player.swap_weapon if keys.include?(:t)
157
+ send_pickup_request if keys.include?(:e)
195
158
 
196
159
  return unless keys.include?(:space)
197
160
 
@@ -206,6 +169,28 @@ module Termfront
206
169
  @conn.send_msg({ t: "fire" })
207
170
  end
208
171
 
172
+ def send_pickup_request
173
+ nearest = nearest_drop_in_range
174
+ return unless nearest
175
+
176
+ @conn.send_msg({ t: "pickup", id: nearest[:id] })
177
+ end
178
+
179
+ def nearest_drop_in_range
180
+ best = nil
181
+ best_d2 = Config::PICKUP_RADIUS**2
182
+ @server_drops.each do |drop|
183
+ dx = drop[:x] - @player.x
184
+ dy = drop[:y] - @player.y
185
+ d2 = dx * dx + dy * dy
186
+ if d2 < best_d2
187
+ best = drop
188
+ best_d2 = d2
189
+ end
190
+ end
191
+ best
192
+ end
193
+
209
194
  def update_local(dt)
210
195
  @player.game_time += dt
211
196
 
@@ -278,9 +263,12 @@ module Termfront
278
263
 
279
264
  msg[:players].each do |entry|
280
265
  if entry[:id] == @player_id
266
+ prev_shield = @player.shield
281
267
  @player.shield = entry[:s]
282
268
  @player.health = entry[:h]
283
269
  @player.dead = !entry[:alive]
270
+ sync_own_weapon(entry)
271
+ update_regen_audio(prev_shield)
284
272
  else
285
273
  remote = @remote_players[entry[:id]]
286
274
  next unless remote
@@ -290,7 +278,8 @@ module Termfront
290
278
  remote.angle = entry[:a]
291
279
  remote.shield = entry[:s]
292
280
  remote.health = entry[:h]
293
- remote.weapon = entry[:w]&.to_sym
281
+ weapon = safe_weapon(entry[:w])
282
+ remote.weapon = weapon if weapon
294
283
  remote.ammo = entry[:am]
295
284
  remote.fire_flash = entry[:ff] || 0
296
285
  end
@@ -299,19 +288,79 @@ module Termfront
299
288
  @enemies = msg[:enemies].filter_map do |enemy|
300
289
  next unless enemy[:alive]
301
290
 
291
+ sprite = safe_enemy_type(enemy[:type])
292
+ next unless sprite
293
+
302
294
  RemoteEnemy.new(
303
295
  id: enemy[:id],
304
296
  x: enemy[:x],
305
297
  y: enemy[:y],
306
- sprite_id: enemy[:type].to_sym,
298
+ sprite_id: sprite,
307
299
  hp: enemy[:hp],
308
300
  max_hp: enemy[:max_hp],
309
301
  alive: true
310
302
  )
311
303
  end
312
304
 
313
- @projectiles = msg[:projectiles].map do |projectile|
314
- Projectile.new(x: projectile[:x], y: projectile[:y], vx: 0.0, vy: 0.0, type: projectile[:type].to_sym)
305
+ @projectiles = msg[:projectiles].filter_map do |projectile|
306
+ type = safe_enemy_type(projectile[:type])
307
+ next unless type
308
+
309
+ Projectile.new(x: projectile[:x], y: projectile[:y], vx: 0.0, vy: 0.0, type: type)
310
+ end
311
+
312
+ @server_drops = (msg[:drops] || []).filter_map do |raw|
313
+ drop_type = safe_weapon(raw[:type])
314
+ next unless drop_type
315
+ next unless raw[:id].is_a?(Numeric)
316
+
317
+ { id: raw[:id], x: raw[:x].to_f, y: raw[:y].to_f, type: drop_type, ammo: raw[:am].to_i }
318
+ end
319
+ @drops = @server_drops.map do |drop|
320
+ DropItem::Weapon.new(x: drop[:x], y: drop[:y], type: drop[:type], ammo: drop[:ammo])
321
+ end
322
+ end
323
+
324
+ def sync_own_weapon(entry)
325
+ weapon_sym = safe_weapon(entry[:w])
326
+ return unless weapon_sym
327
+
328
+ current = @player.current_weapon
329
+ if current.type_id != weapon_sym
330
+ @player.weapons[@player.weapon_idx] = Weapon::Base.build(weapon_sym, entry[:am])
331
+ elsif entry.key?(:am) && current.respond_to?(:ammo=)
332
+ current.ammo = entry[:am]
333
+ end
334
+ end
335
+
336
+ def safe_weapon(value)
337
+ return nil unless value.is_a?(String) || value.is_a?(Symbol)
338
+
339
+ name = value.to_s
340
+ return nil unless ALLOWED_WEAPONS.include?(name)
341
+
342
+ name.to_sym
343
+ end
344
+
345
+ def safe_enemy_type(value)
346
+ return nil unless value.is_a?(String) || value.is_a?(Symbol)
347
+
348
+ name = value.to_s
349
+ return nil unless ALLOWED_ENEMY_TYPES.include?(name)
350
+
351
+ name.to_sym
352
+ end
353
+
354
+ def update_regen_audio(prev_shield)
355
+ regen_now = !@player.dead && @player.shield > prev_shield && @player.shield < Config::SHIELD_MAX
356
+ if regen_now
357
+ unless @regen_active
358
+ @audio.play_loop_se(:shield_regen)
359
+ @regen_active = true
360
+ end
361
+ elsif @regen_active
362
+ @audio.stop_loop_se(:shield_regen)
363
+ @regen_active = false
315
364
  end
316
365
  end
317
366
 
@@ -323,7 +372,7 @@ module Termfront
323
372
  map: @map,
324
373
  enemies: @enemies,
325
374
  projectiles: @projectiles,
326
- drops: [],
375
+ drops: @drops,
327
376
  terminals: [],
328
377
  status_line: status,
329
378
  allies: allies
@@ -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 = @stdout.winsize
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
- pixels = build_view_pixels(virt_h, view_w, wtop, wbot, wcol)
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 << ("\xE2\x94\x80" * cols)[0, cols * 3] << "\r\n"
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
- dist = Math.sqrt(ex * ex + ey * ey)
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
- dist = Math.sqrt(ex * ex + ey * ey)
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
- dist = Math.sqrt(ex * ex + ey * ey)
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
- dist = Math.sqrt(ex * ex + ey * ey)
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
- ec = etype == :executor ? "\e[95m" : "\e[91m"
357
- line << "#{ec}*\e[0m"
414
+ line << (etype == :executor ? RADAR_EXECUTOR : RADAR_CRAWLER)
358
415
  elsif ally_cells[[row, cx]]
359
- line << "\e[96m+\e[0m"
416
+ line << RADAR_ALLY
360
417
  elsif (drop = drop_cells[[row, cx]])
361
- dc = drop.type.to_s.start_with?("shock") ? "\e[96m" : "\e[93m"
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 << "\e[96mT\e[0m"
420
+ line << RADAR_TERMINAL
366
421
  elsif row == r && cx == r
367
- line << "\e[92m^\e[0m"
422
+ line << RADAR_PLAYER
368
423
  elsif grid[row][cx] == "#"
369
- line << "\e[90m#\e[0m"
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
- sprites = []
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
- sprites << [tz, tx, e]
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
- sprites.sort_by! { |s| -s[0] }
468
+ @enemy_sprites.sort! { |a, b| b[0] <=> a[0] }
414
469
 
415
- sprites.each do |tz, tx, e|
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.sort_by! { |s| -s[0] }
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.sort_by! { |s| -s[0] }
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
- sprites = []
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
- sprites << [tz, tx, ally]
636
+ @ally_sprites << [tz, tx, ally]
582
637
  end
583
- sprites.sort_by! { |s| -s[0] }
638
+ @ally_sprites.sort! { |a, b| b[0] <=> a[0] }
584
639
 
585
- sprites.each do |tz, tx, ally|
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
- "\e[38;5;#{color}m"
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
- "\e[48;5;#{color}m"
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
- sync_updates? ? "\e[?2026l" : ""
16
+ ""
22
17
  end
23
18
 
24
19
  def fit_ansi(text, width)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Termfront
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: termfront
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - S-H-GAMELINKS