termfront 0.1.6 → 0.1.7
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 +22 -0
- data/lib/termfront/async_writer.rb +45 -0
- data/lib/termfront/audio_manager.rb +10 -2
- data/lib/termfront/color.rb +19 -0
- data/lib/termfront/config.rb +2 -1
- data/lib/termfront/drop_item/weapon.rb +4 -1
- data/lib/termfront/game.rb +4 -1
- data/lib/termfront/network/client.rb +32 -31
- data/lib/termfront/network/wavesfight_client.rb +2 -1
- data/lib/termfront/renderer.rb +106 -80
- data/lib/termfront/sprite.rb +60 -24
- data/lib/termfront/terminal_output.rb +1 -1
- data/lib/termfront/title_screen.rb +141 -26
- data/lib/termfront/version.rb +1 -1
- data/lib/termfront.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6d91fa7b7758b2151fc31bb9f7862dbda3ad311edea638aa9b11ca50a8b9a8d
|
|
4
|
+
data.tar.gz: 3a8ce081de76ddd04c45949c490ba4b513f42eb94eb39fce37fef292abdbb004
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a79ab596683eea6f8914c17cf88ad000d7a1b33453820526310f25fd43dbabb1f21e6f65f16a390fa92e491d6cd2da86e981c17bc971161316a6d4f7b431c98a
|
|
7
|
+
data.tar.gz: 556667b34ba430c7af848fca7d31f5937e24d328480a29a3191db88ca4286d579081b0f6e18c34622d87cb9ea401c20e19da6aaf0a6fbc2b892f15fdbb9f7b76
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,28 @@ The format is based on Keep a Changelog, and this project follows Semantic Versi
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.7] - 2026-05-26
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Restore the in-game and title screen render rate to 60 Hz (`Config::RENDER_DT = 1.0 / 60.0`) after testers reported choppy motion at 30 Hz; the hybrid rendering scaffolding is kept in place so the rate can be tuned later without code changes
|
|
14
|
+
- Cap the per-frame `dt` at `Config::MAX_DT` (50 ms / 20 FPS-equivalent) in the singleplayer, Wavesfight, and PvP game loops so a frame that runs long under a constrained host no longer translates into a single oversized physics step. Movement, weapon timers, and projectile motion now stay smooth — the simulation simply slows for that frame instead of producing a teleporting opponent or instant fire-rate
|
|
15
|
+
- Hoist the sprite shape function out of the per-cell loop in `Renderer#overlay_enemies_3d`: look up `Sprite::REGISTRY[sprite_id]` once per enemy and call it directly from the inner cell loop instead of going through `Sprite.for` (which re-hashed `REGISTRY` and re-checked the result for `nil` on every cell). With several enemies on-screen the per-cell hash lookup overhead disappears
|
|
16
|
+
- Migrate the PvP/Wavesfight client render paths to 256-color SGR sequences as well: add a `Sprite.player_enemy` variant pre-tinted toward red instead of recomputing the tint per cell, replace the per-cell `\e[38;2;…m` (and `\e[48;2;…m` for damage flash) escapes with `\e[38;5;Nm` lookups, drop the `tint_player_color` helper and its per-cell `String#split`/`Array#map` work, and reuse the renderer's shared `DAMAGE_FLASH_RAMP` for the screen-edge flash
|
|
17
|
+
- Hand frame output off to a dedicated writer thread (`AsyncWriter`) so the game loop's `syswrite` becomes a non-blocking `Queue#push` and the actual `IO#syswrite` runs on a background thread that releases the GVL during the PTY write. The main loop no longer pays the wall-clock cost of waiting for the terminal to drain its buffer; when a write is still in progress, the latest frame replaces the queued one so the terminal always sees the freshest frame instead of a backlog
|
|
18
|
+
- Drop truecolor SGR sequences from the in-game and title 3D views; convert every sprite, fallback, bar, projectile, drop, and damage-flash color to an xterm 256-color index once with `Color.rgb_to_256`, simplify `Renderer#render_view` to a pure 256-color path that drops the per-cell `is_a?(Integer)` test and the truecolor SGR caches, and switch the title screen's half-block compositor to look up its escapes in the shared `FG_256` / `BG_256` tables. The 13-byte truecolor SGR shrinks to a 9-byte 256-color SGR (or shorter on cell boundaries via `\e[K`), the renderer's hottest cell loop loses its type dispatch, and the dead `bg_only?` / `ansi_fg` / `ansi_bg` helpers go away
|
|
19
|
+
- Build the half-block rows in `TitleScreen#render` directly into the final per-row string with the column count already capped, skipping the per-row `TerminalOutput.fit_ansi` walk entirely; rows that come out uniformly the same color collapse to `\e[bg]\e[K\e[0m`, fully-blank rows ship as a single space run, and the byte stream the terminal sees for each title frame shrinks substantially
|
|
20
|
+
- Coalesce same-color cell runs in `TitleScreen#render`'s half-block compositor and only emit SGR escapes on transitions instead of wrapping every cell in `\e[…m…\e[0m`; the per-row string handed to `fit_ansi` shrinks from one bracketed SGR per cell to a handful of color changes, cutting both the title frame's byte volume and `fit_ansi`'s per-row scan cost
|
|
21
|
+
- Emit `\e[K` (Erase to End of Line) for rows in `Renderer#render_view` whose top/bottom cells are all the same integer background color; the ceiling-only and floor-only bands now ship as `\e[bg]\e[K\r\n` per row instead of the full per-cell glyph sequence, slashing the ANSI byte volume of those rows from `view_w` bytes plus per-cell SGR transitions down to about a dozen bytes total
|
|
22
|
+
- Memoize `AudioManager#which` results in a class-level cache so the per-command `PATH` walk (and the per-directory `File.executable?` stat) only runs the first time a binary is looked up; subsequent `AudioManager.new` calls during mode transitions hit the cache instead of re-scanning `PATH`, which removes a noticeable stutter on WSL hosts where Windows-mounted `PATH` entries are slow to stat
|
|
23
|
+
- Coalesce same-color runs of cells in `Renderer#render_view` with a single `String#*` instead of one `buf << " "` (or `buf << "█"`) per cell, and rewrite the row/column loops as `while` so the ceiling/floor bands cost one short string copy per row instead of dozens of one-byte appends
|
|
24
|
+
- Skip the per-column DDA in `Renderer#render` when the player position, facing, map, and viewport are unchanged from the previous frame; the cached `@dists`, `@sides`, `@wtop`, `@wbot`, and `@wcol` arrays are reused so `cast_ray` and the wall-column projection only run when something actually moved
|
|
25
|
+
- Bulk-fill the ceiling-only and floor-only rows of `Renderer#build_view_pixels` with `Array#fill` so each fully ceiling or fully floor row is written as a single C-level call, with the column-major wall loop kept for the mixed band in the middle
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Treat `Errno::EAGAIN` from `syswrite` the same as `IO::WaitWritable` in `TerminalOutput.write_all` so the renderer keeps draining its frame buffer instead of crashing when the terminal's PTY temporarily refuses additional bytes
|
|
30
|
+
|
|
9
31
|
## [0.1.6] - 2026-05-26
|
|
10
32
|
|
|
11
33
|
### Changed
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Termfront
|
|
4
|
+
class AsyncWriter
|
|
5
|
+
def initialize(io)
|
|
6
|
+
@io = io
|
|
7
|
+
@queue = Queue.new
|
|
8
|
+
@closed = false
|
|
9
|
+
@thread = Thread.new do
|
|
10
|
+
Thread.current.report_on_exception = false
|
|
11
|
+
while (data = @queue.pop)
|
|
12
|
+
begin
|
|
13
|
+
TerminalOutput.write_all(@io, data)
|
|
14
|
+
rescue IOError, Errno::EBADF, Errno::EPIPE
|
|
15
|
+
break
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def syswrite(data)
|
|
22
|
+
raise IOError, "writer closed" if @closed
|
|
23
|
+
|
|
24
|
+
@queue.clear if @queue.size >= 1
|
|
25
|
+
@queue.push(data)
|
|
26
|
+
data.bytesize
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def winsize
|
|
30
|
+
@io.winsize
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def raw(&block)
|
|
34
|
+
@io.raw(&block)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def close
|
|
38
|
+
return if @closed
|
|
39
|
+
|
|
40
|
+
@closed = true
|
|
41
|
+
@queue.push(nil)
|
|
42
|
+
@thread.join
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -8,6 +8,8 @@ module Termfront
|
|
|
8
8
|
class AudioManager
|
|
9
9
|
Player = Struct.new(:command, :supports_loop, keyword_init: true)
|
|
10
10
|
|
|
11
|
+
WHICH_CACHE = {}
|
|
12
|
+
|
|
11
13
|
def initialize
|
|
12
14
|
@manifest = load_manifest
|
|
13
15
|
@bgm_player = detect_player(%w[ffplay afplay paplay aplay], prefer_loop: true)
|
|
@@ -160,12 +162,18 @@ module Termfront
|
|
|
160
162
|
end
|
|
161
163
|
|
|
162
164
|
def which(command)
|
|
165
|
+
return WHICH_CACHE[command] if WHICH_CACHE.key?(command)
|
|
166
|
+
|
|
167
|
+
resolved = nil
|
|
163
168
|
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |dir|
|
|
164
169
|
candidate = File.join(dir, command)
|
|
165
|
-
|
|
170
|
+
if File.executable?(candidate) && !File.directory?(candidate)
|
|
171
|
+
resolved = candidate
|
|
172
|
+
break
|
|
173
|
+
end
|
|
166
174
|
end
|
|
167
175
|
|
|
168
|
-
|
|
176
|
+
WHICH_CACHE[command] = resolved
|
|
169
177
|
end
|
|
170
178
|
|
|
171
179
|
def asset_path(kind, name)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Termfront
|
|
4
|
+
module Color
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def rgb_to_256(r, g, b)
|
|
8
|
+
if (r - g).abs < 8 && (g - b).abs < 8 && (r - b).abs < 8
|
|
9
|
+
avg = (r + g + b) / 3
|
|
10
|
+
return 16 if avg < 8
|
|
11
|
+
return 231 if avg > 247
|
|
12
|
+
|
|
13
|
+
232 + (avg - 8) * 23 / 240
|
|
14
|
+
else
|
|
15
|
+
16 + (r * 5 / 255) * 36 + (g * 5 / 255) * 6 + (b * 5 / 255)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/termfront/config.rb
CHANGED
|
@@ -22,8 +22,11 @@ module Termfront
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
SHOCK_COLOR = Color.rgb_to_256(60, 200, 220)
|
|
26
|
+
NORMAL_COLOR = Color.rgb_to_256(220, 200, 60)
|
|
27
|
+
|
|
25
28
|
def sprite_color
|
|
26
|
-
@type.to_s.start_with?("shock") ?
|
|
29
|
+
@type.to_s.start_with?("shock") ? SHOCK_COLOR : NORMAL_COLOR
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
def radar_color
|
data/lib/termfront/game.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Termfront
|
|
4
4
|
class Game
|
|
5
5
|
def initialize
|
|
6
|
-
@stdout = STDOUT
|
|
6
|
+
@stdout = AsyncWriter.new(STDOUT)
|
|
7
7
|
@audio = AudioManager.new
|
|
8
8
|
@renderer = Renderer.new(@stdout)
|
|
9
9
|
@input = Input.new
|
|
@@ -36,6 +36,7 @@ module Termfront
|
|
|
36
36
|
@crash = e
|
|
37
37
|
ensure
|
|
38
38
|
@audio.close
|
|
39
|
+
@stdout.close if @stdout.respond_to?(:close) && !@stdout.equal?(STDOUT)
|
|
39
40
|
leave_alt_screen
|
|
40
41
|
if @crash
|
|
41
42
|
warn "#{@crash.class}: #{@crash.message}"
|
|
@@ -136,6 +137,7 @@ module Termfront
|
|
|
136
137
|
loop do
|
|
137
138
|
now = clock
|
|
138
139
|
dt = now - last_time
|
|
140
|
+
dt = Config::MAX_DT if dt > Config::MAX_DT
|
|
139
141
|
last_time = now
|
|
140
142
|
|
|
141
143
|
keys = @input.process(stdin, player: @player)
|
|
@@ -185,6 +187,7 @@ module Termfront
|
|
|
185
187
|
loop do
|
|
186
188
|
now = clock
|
|
187
189
|
dt = now - last_time
|
|
190
|
+
dt = Config::MAX_DT if dt > Config::MAX_DT
|
|
188
191
|
last_time = now
|
|
189
192
|
|
|
190
193
|
keys = @input.process(stdin, player: @player)
|
|
@@ -6,6 +6,14 @@ module Termfront
|
|
|
6
6
|
TEAM_SIZES = [1, 2, 4].freeze
|
|
7
7
|
ALLOWED_WEAPONS = %w[pistol ar].freeze
|
|
8
8
|
|
|
9
|
+
ALLY_PLAYER_FALLBACK = Color.rgb_to_256(70, 210, 255)
|
|
10
|
+
ENEMY_PLAYER_FALLBACK = Color.rgb_to_256(255, 110, 80)
|
|
11
|
+
ALLY_BAR_FILL = Color.rgb_to_256(0, 180, 255)
|
|
12
|
+
ENEMY_BAR_FILL = Color.rgb_to_256(255, 80, 80)
|
|
13
|
+
BAR_EMPTY = Color.rgb_to_256(80, 20, 20)
|
|
14
|
+
PROJ_SHOCK = Color.rgb_to_256(80, 220, 255)
|
|
15
|
+
PROJ_NORMAL = Color.rgb_to_256(255, 210, 80)
|
|
16
|
+
|
|
9
17
|
def initialize(stdout)
|
|
10
18
|
@stdout = stdout
|
|
11
19
|
@conn = Connection.new
|
|
@@ -199,6 +207,7 @@ module Termfront
|
|
|
199
207
|
loop do
|
|
200
208
|
now = clock
|
|
201
209
|
dt = now - last_time
|
|
210
|
+
dt = Config::MAX_DT if dt > Config::MAX_DT
|
|
202
211
|
last_time = now
|
|
203
212
|
|
|
204
213
|
keys = @input.process(stdin, player: @player)
|
|
@@ -548,33 +557,34 @@ module Termfront
|
|
|
548
557
|
next unless top_in || bot_in
|
|
549
558
|
|
|
550
559
|
if use_shape
|
|
560
|
+
sprite_fn = color_mode == :ally ? Sprite.method(:player) : Sprite.method(:player_enemy)
|
|
551
561
|
ny0 = top_in ? (vp0 - draw_top).to_f / actual_h : nil
|
|
552
562
|
ny1 = bot_in ? (vp1 - draw_top).to_f / actual_h : nil
|
|
553
|
-
top_color = ny0 ?
|
|
554
|
-
bot_color = ny1 ?
|
|
563
|
+
top_color = ny0 ? sprite_fn.call(nx, ny0) : nil
|
|
564
|
+
bot_color = ny1 ? sprite_fn.call(nx, ny1) : nil
|
|
555
565
|
next unless top_color || bot_color
|
|
556
566
|
|
|
557
567
|
buf << "\e[#{3 + r};#{c + 1}H"
|
|
558
568
|
buf << if top_color && bot_color
|
|
559
569
|
if top_color == bot_color
|
|
560
|
-
"\e[38;
|
|
570
|
+
"\e[38;5;#{top_color}m\xE2\x96\x88\e[0m"
|
|
561
571
|
else
|
|
562
|
-
"\e[38;
|
|
572
|
+
"\e[38;5;#{top_color};48;5;#{bot_color}m\xE2\x96\x80\e[0m"
|
|
563
573
|
end
|
|
564
574
|
elsif top_color
|
|
565
|
-
"\e[38;
|
|
575
|
+
"\e[38;5;#{top_color}m\xE2\x96\x80\e[0m"
|
|
566
576
|
else
|
|
567
|
-
"\e[38;
|
|
577
|
+
"\e[38;5;#{bot_color}m\xE2\x96\x84\e[0m"
|
|
568
578
|
end
|
|
569
579
|
else
|
|
570
|
-
fc = color_mode == :ally ?
|
|
580
|
+
fc = color_mode == :ally ? ALLY_PLAYER_FALLBACK : ENEMY_PLAYER_FALLBACK
|
|
571
581
|
buf << "\e[#{3 + r};#{c + 1}H"
|
|
572
582
|
buf << if top_in && bot_in
|
|
573
|
-
"\e[38;
|
|
583
|
+
"\e[38;5;#{fc}m\xE2\x96\x88\e[0m"
|
|
574
584
|
elsif top_in
|
|
575
|
-
"\e[38;
|
|
585
|
+
"\e[38;5;#{fc}m\xE2\x96\x80\e[0m"
|
|
576
586
|
else
|
|
577
|
-
"\e[38;
|
|
587
|
+
"\e[38;5;#{fc}m\xE2\x96\x84\e[0m"
|
|
578
588
|
end
|
|
579
589
|
end
|
|
580
590
|
end
|
|
@@ -590,15 +600,15 @@ module Termfront
|
|
|
590
600
|
max_total = Config::SHIELD_MAX + Config::HEALTH_MAX
|
|
591
601
|
hp_pct = total.to_f / max_total
|
|
592
602
|
filled = (hp_pct * (bar_ex - bar_sx + 1)).ceil
|
|
593
|
-
fill_color = color_mode == :ally ?
|
|
603
|
+
fill_color = color_mode == :ally ? ALLY_BAR_FILL : ENEMY_BAR_FILL
|
|
594
604
|
|
|
595
605
|
bar_sx.upto(bar_ex) do |c|
|
|
596
606
|
next if c < 0 || c >= view_w
|
|
597
607
|
next if dists[c] < tz
|
|
598
608
|
|
|
599
609
|
ci = c - bar_sx
|
|
600
|
-
color = ci < filled ? fill_color :
|
|
601
|
-
buf << "\e[#{3 + bar_row};#{c + 1}H\e[38;
|
|
610
|
+
color = ci < filled ? fill_color : BAR_EMPTY
|
|
611
|
+
buf << "\e[#{3 + bar_row};#{c + 1}H\e[38;5;#{color}m\xE2\x96\x88\e[0m"
|
|
602
612
|
end
|
|
603
613
|
end
|
|
604
614
|
|
|
@@ -649,28 +659,17 @@ module Termfront
|
|
|
649
659
|
proj_color = projectile[:color]
|
|
650
660
|
buf << "\e[#{3 + r};#{c + 1}H"
|
|
651
661
|
buf << if top_in && bot_in
|
|
652
|
-
"\e[38;
|
|
662
|
+
"\e[38;5;#{proj_color}m\xE2\x96\x88\e[0m"
|
|
653
663
|
elsif top_in
|
|
654
|
-
"\e[38;
|
|
664
|
+
"\e[38;5;#{proj_color}m\xE2\x96\x80\e[0m"
|
|
655
665
|
else
|
|
656
|
-
"\e[38;
|
|
666
|
+
"\e[38;5;#{proj_color}m\xE2\x96\x84\e[0m"
|
|
657
667
|
end
|
|
658
668
|
end
|
|
659
669
|
end
|
|
660
670
|
end
|
|
661
671
|
end
|
|
662
672
|
|
|
663
|
-
def tint_player_color(color, mode)
|
|
664
|
-
return nil unless color
|
|
665
|
-
return color if mode == :ally
|
|
666
|
-
|
|
667
|
-
r, g, b = color.split(";").map(&:to_i)
|
|
668
|
-
nr = [[r + 70, 255].min, 0].max
|
|
669
|
-
ng = [[g - 50, 0].max, 0].max
|
|
670
|
-
nb = [[b - 90, 0].max, 0].max
|
|
671
|
-
"#{nr};#{ng};#{nb}"
|
|
672
|
-
end
|
|
673
|
-
|
|
674
673
|
def render_pvp_hud(buf, cols)
|
|
675
674
|
bar_w = [cols - 30, 10].max
|
|
676
675
|
pct = @player.shield / Config::SHIELD_MAX.to_f
|
|
@@ -806,13 +805,15 @@ module Termfront
|
|
|
806
805
|
def render_damage_flash(buf, view_h, view_w)
|
|
807
806
|
return unless @player.damage_flash > 0
|
|
808
807
|
|
|
809
|
-
intensity = @player.damage_flash * 60
|
|
808
|
+
intensity = (@player.damage_flash * 60).to_i
|
|
809
|
+
intensity = 255 if intensity > 255
|
|
810
|
+
color = Renderer::DAMAGE_FLASH_RAMP[intensity]
|
|
810
811
|
flash_w = 2
|
|
811
812
|
|
|
812
813
|
view_h.times do |r|
|
|
813
|
-
buf << "\e[#{3 + r};1H\e[48;
|
|
814
|
+
buf << "\e[#{3 + r};1H\e[48;5;#{color}m#{" " * flash_w}\e[0m"
|
|
814
815
|
rc = [view_w - flash_w + 1, 1].max
|
|
815
|
-
buf << "\e[#{3 + r};#{rc}H\e[48;
|
|
816
|
+
buf << "\e[#{3 + r};#{rc}H\e[48;5;#{color}m#{" " * flash_w}\e[0m"
|
|
816
817
|
end
|
|
817
818
|
end
|
|
818
819
|
|
|
@@ -849,7 +850,7 @@ module Termfront
|
|
|
849
850
|
|
|
850
851
|
def spawn_remote_projectile_effect(msg)
|
|
851
852
|
shock = msg[:w].to_s.start_with?("shock")
|
|
852
|
-
color = shock ?
|
|
853
|
+
color = shock ? PROJ_SHOCK : PROJ_NORMAL
|
|
853
854
|
speed = shock ? 18.0 : 14.0
|
|
854
855
|
angle = msg[:a]
|
|
855
856
|
@projectiles << {
|
|
@@ -57,7 +57,7 @@ module Termfront
|
|
|
57
57
|
lines[rows / 2 - 2] = TerminalOutput.fit_ansi("#{" " * (mc - 1)}\e[1;93m#{msg}\e[0m", cols)
|
|
58
58
|
detail = "#{queued_mission_name} | #{queued_difficulty_name}"
|
|
59
59
|
dc = [(cols - detail.size) / 2 + 1, 1].max
|
|
60
|
-
lines[rows / 2] = TerminalOutput.fit_ansi("#{" " * (dc - 1)}\e[38;
|
|
60
|
+
lines[rows / 2] = TerminalOutput.fit_ansi("#{" " * (dc - 1)}\e[38;5;#{Color.rgb_to_256(170, 170, 190)}m#{detail}\e[0m", cols)
|
|
61
61
|
hint = "(ESC to cancel)"
|
|
62
62
|
hc = [(cols - hint.size) / 2 + 1, 1].max
|
|
63
63
|
lines[rows / 2 + 2] = TerminalOutput.fit_ansi("#{" " * (hc - 1)}\e[90m#{hint}\e[0m", cols)
|
|
@@ -121,6 +121,7 @@ module Termfront
|
|
|
121
121
|
loop do
|
|
122
122
|
now = clock
|
|
123
123
|
dt = now - last_time
|
|
124
|
+
dt = Config::MAX_DT if dt > Config::MAX_DT
|
|
124
125
|
last_time = now
|
|
125
126
|
|
|
126
127
|
keys = @input.process(stdin, player: @player)
|
data/lib/termfront/renderer.rb
CHANGED
|
@@ -12,6 +12,17 @@ module Termfront
|
|
|
12
12
|
FG_256 = Array.new(256) { |i| "\e[38;5;#{i}m".freeze }.freeze
|
|
13
13
|
BG_256 = Array.new(256) { |i| "\e[48;5;#{i}m".freeze }.freeze
|
|
14
14
|
|
|
15
|
+
EXECUTOR_FALLBACK = Color.rgb_to_256(100, 60, 200)
|
|
16
|
+
CRAWLER_FALLBACK = Color.rgb_to_256(220, 140, 30)
|
|
17
|
+
ENEMY_BAR_FILL = Color.rgb_to_256(0, 200, 0)
|
|
18
|
+
ENEMY_BAR_EMPTY = Color.rgb_to_256(200, 0, 0)
|
|
19
|
+
PROJ_EXECUTOR = Color.rgb_to_256(94, 94, 255)
|
|
20
|
+
PROJ_DEFAULT = Color.rgb_to_256(255, 210, 80)
|
|
21
|
+
ALLY_FALLBACK = Color.rgb_to_256(70, 210, 255)
|
|
22
|
+
ALLY_BAR_FILL = Color.rgb_to_256(0, 180, 255)
|
|
23
|
+
ALLY_BAR_EMPTY = Color.rgb_to_256(80, 20, 20)
|
|
24
|
+
DAMAGE_FLASH_RAMP = Array.new(256) { |i| Color.rgb_to_256(i, 0, 0) }.freeze
|
|
25
|
+
|
|
15
26
|
def initialize(stdout)
|
|
16
27
|
@stdout = stdout
|
|
17
28
|
@buf_view_w = 0
|
|
@@ -23,8 +34,6 @@ module Termfront
|
|
|
23
34
|
@radar_drop_cells = {}
|
|
24
35
|
@radar_terminal_cells = {}
|
|
25
36
|
@radar_ally_cells = {}
|
|
26
|
-
@fg_truecolor_cache = {}
|
|
27
|
-
@bg_truecolor_cache = {}
|
|
28
37
|
@enemy_sprites = []
|
|
29
38
|
@proj_sprites = []
|
|
30
39
|
@drop_sprites = []
|
|
@@ -54,23 +63,27 @@ module Termfront
|
|
|
54
63
|
|
|
55
64
|
prepare_frame_buffers(view_w, virt_h)
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
cast_state = [player.x, player.y, player.angle, map.object_id, view_w, virt_h]
|
|
67
|
+
if cast_state != @last_cast_state
|
|
68
|
+
dx = Math.cos(player.angle)
|
|
69
|
+
dy = Math.sin(player.angle)
|
|
70
|
+
plane_x = -dy * Math.tan(Config::FOV / 2.0)
|
|
71
|
+
plane_y = dx * Math.tan(Config::FOV / 2.0)
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
view_w.times do |c|
|
|
74
|
+
cam = 2.0 * c / view_w - 1.0
|
|
75
|
+
@dists[c], @sides[c] = cast_ray(map, player.x, player.y, dx + plane_x * cam, dy + plane_y * cam)
|
|
76
|
+
end
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
vmid = virt_h / 2.0
|
|
79
|
+
view_w.times do |c|
|
|
80
|
+
d = @dists[c]
|
|
81
|
+
lh = d > 0.01 ? (virt_h / d).to_i : virt_h
|
|
82
|
+
@wtop[c] = [(vmid - lh / 2.0).to_i, 0].max
|
|
83
|
+
@wbot[c] = [(vmid + lh / 2.0).to_i, virt_h].min
|
|
84
|
+
@wcol[c] = Sprite.wall_brightness(d, @sides[c])
|
|
85
|
+
end
|
|
86
|
+
@last_cast_state = cast_state
|
|
74
87
|
end
|
|
75
88
|
build_view_pixels(virt_h, view_w, @wtop, @wbot, @wcol)
|
|
76
89
|
overlay_enemies_3d(@pixels, view_h, view_w, @dists, player, enemies, projectiles, drops)
|
|
@@ -289,22 +302,41 @@ module Termfront
|
|
|
289
302
|
floor_c = Config::FLOOR_C
|
|
290
303
|
pixels = @pixels
|
|
291
304
|
|
|
305
|
+
min_wt = wtop.min
|
|
306
|
+
max_wb = wbot.max
|
|
307
|
+
upper_done = min_wt < virt_h ? min_wt : virt_h
|
|
308
|
+
lower_start = max_wb > upper_done ? max_wb : upper_done
|
|
309
|
+
lower_start = virt_h if lower_start > virt_h
|
|
310
|
+
|
|
311
|
+
vr = 0
|
|
312
|
+
while vr < upper_done
|
|
313
|
+
pixels[vr].fill(ceil_c, 0, view_w)
|
|
314
|
+
vr += 1
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
vr_bot = virt_h - 1
|
|
318
|
+
while vr_bot >= lower_start
|
|
319
|
+
pixels[vr_bot].fill(floor_c, 0, view_w)
|
|
320
|
+
vr_bot -= 1
|
|
321
|
+
end
|
|
322
|
+
middle_end = vr_bot + 1
|
|
323
|
+
|
|
292
324
|
c = 0
|
|
293
325
|
while c < view_w
|
|
294
326
|
wt = wtop[c]
|
|
295
327
|
wb = wbot[c]
|
|
296
328
|
wc = wcol[c]
|
|
297
329
|
|
|
298
|
-
vr =
|
|
299
|
-
while vr < wt && vr <
|
|
330
|
+
vr = upper_done
|
|
331
|
+
while vr < wt && vr < middle_end
|
|
300
332
|
pixels[vr][c] = ceil_c
|
|
301
333
|
vr += 1
|
|
302
334
|
end
|
|
303
|
-
while vr < wb && vr <
|
|
335
|
+
while vr < wb && vr < middle_end
|
|
304
336
|
pixels[vr][c] = wc
|
|
305
337
|
vr += 1
|
|
306
338
|
end
|
|
307
|
-
while vr <
|
|
339
|
+
while vr < middle_end
|
|
308
340
|
pixels[vr][c] = floor_c
|
|
309
341
|
vr += 1
|
|
310
342
|
end
|
|
@@ -316,49 +348,60 @@ module Termfront
|
|
|
316
348
|
def render_view(buf, view_h, view_w, pixels)
|
|
317
349
|
fg_256 = FG_256
|
|
318
350
|
bg_256 = BG_256
|
|
319
|
-
fg_cache = @fg_truecolor_cache
|
|
320
|
-
bg_cache = @bg_truecolor_cache
|
|
321
351
|
|
|
322
|
-
|
|
352
|
+
r = 0
|
|
353
|
+
while r < view_h
|
|
323
354
|
vp0 = r * 2
|
|
324
|
-
vp1 =
|
|
325
|
-
pfg = nil
|
|
326
|
-
pbg = nil
|
|
355
|
+
vp1 = vp0 + 1
|
|
327
356
|
top_row = pixels[vp0]
|
|
328
357
|
bot_row = pixels[vp1]
|
|
329
358
|
|
|
330
|
-
|
|
359
|
+
first = top_row[0]
|
|
360
|
+
if bot_row[0] == first
|
|
361
|
+
uniform = true
|
|
362
|
+
cu = 1
|
|
363
|
+
while cu < view_w
|
|
364
|
+
if top_row[cu] != first || bot_row[cu] != first
|
|
365
|
+
uniform = false
|
|
366
|
+
break
|
|
367
|
+
end
|
|
368
|
+
cu += 1
|
|
369
|
+
end
|
|
370
|
+
if uniform
|
|
371
|
+
buf << bg_256[first] << "\e[K\e[0m\r\n"
|
|
372
|
+
r += 1
|
|
373
|
+
next
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
pbg = nil
|
|
378
|
+
|
|
379
|
+
c = 0
|
|
380
|
+
while c < view_w
|
|
331
381
|
tc = top_row[c]
|
|
332
382
|
bc = bot_row[c]
|
|
333
383
|
|
|
334
384
|
if tc == bc
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
pbg = tc
|
|
339
|
-
pfg = nil
|
|
340
|
-
end
|
|
341
|
-
buf << " "
|
|
342
|
-
else
|
|
343
|
-
if tc != pfg || pbg
|
|
344
|
-
buf << (fg_cache[tc] ||= "\e[38;2;#{tc}m".freeze)
|
|
345
|
-
pfg = tc
|
|
346
|
-
pbg = nil
|
|
347
|
-
end
|
|
348
|
-
buf << "\xE2\x96\x88"
|
|
385
|
+
run_end = c + 1
|
|
386
|
+
while run_end < view_w && top_row[run_end] == tc && bot_row[run_end] == bc
|
|
387
|
+
run_end += 1
|
|
349
388
|
end
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
pfg = tc
|
|
356
|
-
pbg = bc
|
|
389
|
+
n = run_end - c
|
|
390
|
+
|
|
391
|
+
if tc != pbg
|
|
392
|
+
buf << bg_256[tc]
|
|
393
|
+
pbg = tc
|
|
357
394
|
end
|
|
358
|
-
buf << "
|
|
395
|
+
buf << (n == 1 ? " " : " " * n)
|
|
396
|
+
c = run_end
|
|
397
|
+
else
|
|
398
|
+
buf << fg_256[tc] << bg_256[bc] << "\xE2\x96\x80"
|
|
399
|
+
pbg = bc
|
|
400
|
+
c += 1
|
|
359
401
|
end
|
|
360
402
|
end
|
|
361
403
|
buf << "\e[0m\r\n"
|
|
404
|
+
r += 1
|
|
362
405
|
end
|
|
363
406
|
end
|
|
364
407
|
|
|
@@ -560,8 +603,9 @@ module Termfront
|
|
|
560
603
|
next if actual_h < 1 || actual_w < 1
|
|
561
604
|
|
|
562
605
|
sprite_id = e.sprite_id
|
|
563
|
-
|
|
564
|
-
|
|
606
|
+
sprite_fn = Sprite::REGISTRY[sprite_id]
|
|
607
|
+
fallback_color = sprite_id == :executor ? EXECUTOR_FALLBACK : CRAWLER_FALLBACK
|
|
608
|
+
use_shape = actual_h >= 6 && sprite_fn
|
|
565
609
|
r_top = (draw_top + 1) >> 1
|
|
566
610
|
r_bot = draw_bot >> 1
|
|
567
611
|
actual_h_f = actual_h.to_f
|
|
@@ -583,8 +627,8 @@ module Termfront
|
|
|
583
627
|
if use_shape
|
|
584
628
|
ny0 = top_in ? (vp0 - draw_top) / actual_h_f : nil
|
|
585
629
|
ny1 = bot_in ? (vp1 - draw_top) / actual_h_f : nil
|
|
586
|
-
top_color = ny0 ?
|
|
587
|
-
bot_color = ny1 ?
|
|
630
|
+
top_color = ny0 ? sprite_fn.call(nx, ny0) : nil
|
|
631
|
+
bot_color = ny1 ? sprite_fn.call(nx, ny1) : nil
|
|
588
632
|
if top_color || bot_color
|
|
589
633
|
pixels[vp0][c] = top_color if top_color
|
|
590
634
|
pixels[vp1][c] = bot_color if bot_color
|
|
@@ -618,7 +662,7 @@ module Termfront
|
|
|
618
662
|
while c <= bar_ex
|
|
619
663
|
if c >= 0 && c < view_w && dists[c] >= tz
|
|
620
664
|
ci = c - bar_sx
|
|
621
|
-
color = ci < filled ?
|
|
665
|
+
color = ci < filled ? ENEMY_BAR_FILL : ENEMY_BAR_EMPTY
|
|
622
666
|
pixels[bar_vp0][c] = color
|
|
623
667
|
pixels[bar_vp1][c] = color
|
|
624
668
|
end
|
|
@@ -693,7 +737,7 @@ module Termfront
|
|
|
693
737
|
start_x = 0 if start_x < 0
|
|
694
738
|
end_x = sx + pw / 2
|
|
695
739
|
end_x = view_w_last if end_x > view_w_last
|
|
696
|
-
proj_color = p.type == :executor ?
|
|
740
|
+
proj_color = p.type == :executor ? PROJ_EXECUTOR : PROJ_DEFAULT
|
|
697
741
|
r_top = (draw_top + 1) >> 1
|
|
698
742
|
r_bot = draw_bot >> 1
|
|
699
743
|
r_bot = r_top + 1 if r_bot < r_top + 1
|
|
@@ -781,8 +825,8 @@ module Termfront
|
|
|
781
825
|
pixels[vp0][c] = top_color if top_color
|
|
782
826
|
pixels[vp1][c] = bot_color if bot_color
|
|
783
827
|
else
|
|
784
|
-
pixels[vp0][c] =
|
|
785
|
-
pixels[vp1][c] =
|
|
828
|
+
pixels[vp0][c] = ALLY_FALLBACK if top_in
|
|
829
|
+
pixels[vp1][c] = ALLY_FALLBACK if bot_in
|
|
786
830
|
end
|
|
787
831
|
end
|
|
788
832
|
end
|
|
@@ -802,7 +846,7 @@ module Termfront
|
|
|
802
846
|
next if dists[c] < tz
|
|
803
847
|
|
|
804
848
|
ci = c - bar_sx
|
|
805
|
-
color = ci < filled ?
|
|
849
|
+
color = ci < filled ? ALLY_BAR_FILL : ALLY_BAR_EMPTY
|
|
806
850
|
pixels[bar_row * 2][c] = color
|
|
807
851
|
pixels[bar_row * 2 + 1][c] = color
|
|
808
852
|
end
|
|
@@ -812,9 +856,10 @@ module Termfront
|
|
|
812
856
|
def overlay_damage_flash(pixels, view_h, view_w, player)
|
|
813
857
|
return unless player.damage_flash > 0
|
|
814
858
|
|
|
815
|
-
intensity = player.damage_flash * 60
|
|
859
|
+
intensity = (player.damage_flash * 60).to_i
|
|
860
|
+
intensity = 255 if intensity > 255
|
|
816
861
|
flash_w = 2
|
|
817
|
-
color =
|
|
862
|
+
color = DAMAGE_FLASH_RAMP[intensity]
|
|
818
863
|
|
|
819
864
|
view_h.times do |r|
|
|
820
865
|
vp0 = r * 2
|
|
@@ -843,24 +888,5 @@ module Termfront
|
|
|
843
888
|
buf << "\e[#{cr};#{fs}H\e[93m#{"*" * (fe - fs + 1)}\e[0m"
|
|
844
889
|
end
|
|
845
890
|
|
|
846
|
-
def bg_only?(color)
|
|
847
|
-
color.is_a?(Integer)
|
|
848
|
-
end
|
|
849
|
-
|
|
850
|
-
def ansi_fg(color)
|
|
851
|
-
if color.is_a?(Integer)
|
|
852
|
-
FG_256[color]
|
|
853
|
-
else
|
|
854
|
-
@fg_truecolor_cache[color] ||= "\e[38;2;#{color}m".freeze
|
|
855
|
-
end
|
|
856
|
-
end
|
|
857
|
-
|
|
858
|
-
def ansi_bg(color)
|
|
859
|
-
if color.is_a?(Integer)
|
|
860
|
-
BG_256[color]
|
|
861
|
-
else
|
|
862
|
-
@bg_truecolor_cache[color] ||= "\e[48;2;#{color}m".freeze
|
|
863
|
-
end
|
|
864
|
-
end
|
|
865
891
|
end
|
|
866
892
|
end
|
data/lib/termfront/sprite.rb
CHANGED
|
@@ -4,46 +4,82 @@ module Termfront
|
|
|
4
4
|
module Sprite
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
+
EXECUTOR_EYE = Color.rgb_to_256(180, 120, 255)
|
|
8
|
+
EXECUTOR_HEAD = Color.rgb_to_256(130, 80, 220)
|
|
9
|
+
EXECUTOR_NECK = Color.rgb_to_256(90, 50, 180)
|
|
10
|
+
EXECUTOR_BODY = Color.rgb_to_256(80, 40, 160)
|
|
11
|
+
|
|
12
|
+
CRAWLER_EYE = Color.rgb_to_256(255, 240, 100)
|
|
13
|
+
CRAWLER_BODY = Color.rgb_to_256(220, 140, 30)
|
|
14
|
+
CRAWLER_LEG = Color.rgb_to_256(160, 100, 20)
|
|
15
|
+
|
|
16
|
+
PLAYER_EYE = Color.rgb_to_256(140, 220, 255)
|
|
17
|
+
PLAYER_HEAD = Color.rgb_to_256(40, 130, 180)
|
|
18
|
+
PLAYER_NECK = Color.rgb_to_256(30, 100, 160)
|
|
19
|
+
PLAYER_BODY = Color.rgb_to_256(25, 80, 140)
|
|
20
|
+
|
|
21
|
+
PLAYER_ENEMY_EYE = Color.rgb_to_256(210, 170, 165)
|
|
22
|
+
PLAYER_ENEMY_HEAD = Color.rgb_to_256(110, 80, 90)
|
|
23
|
+
PLAYER_ENEMY_NECK = Color.rgb_to_256(100, 50, 70)
|
|
24
|
+
PLAYER_ENEMY_BODY = Color.rgb_to_256(95, 30, 50)
|
|
25
|
+
|
|
26
|
+
DUMMY_HEAD = Color.rgb_to_256(235, 80, 80)
|
|
27
|
+
DUMMY_TORSO = Color.rgb_to_256(210, 210, 210)
|
|
28
|
+
DUMMY_LOWER = Color.rgb_to_256(200, 200, 200)
|
|
29
|
+
DUMMY_LEG = Color.rgb_to_256(180, 180, 180)
|
|
30
|
+
|
|
7
31
|
def executor(nx, ny)
|
|
8
|
-
return
|
|
9
|
-
return
|
|
10
|
-
return
|
|
11
|
-
return
|
|
12
|
-
return
|
|
13
|
-
return
|
|
14
|
-
return
|
|
32
|
+
return EXECUTOR_EYE if ((nx - 0.43) / 0.045)**2 + ((ny - 0.11) / 0.045)**2 <= 1.0
|
|
33
|
+
return EXECUTOR_EYE if ((nx - 0.57) / 0.045)**2 + ((ny - 0.11) / 0.045)**2 <= 1.0
|
|
34
|
+
return EXECUTOR_HEAD if ((nx - 0.5) / 0.18)**2 + ((ny - 0.12) / 0.12)**2 <= 1.0
|
|
35
|
+
return EXECUTOR_NECK if ((nx - 0.5) / 0.38)**2 + ((ny - 0.30) / 0.08)**2 <= 1.0
|
|
36
|
+
return EXECUTOR_BODY if ((nx - 0.5) / 0.25)**2 + ((ny - 0.50) / 0.22)**2 <= 1.0
|
|
37
|
+
return EXECUTOR_BODY if ((nx - 0.38) / 0.10)**2 + ((ny - 0.85) / 0.15)**2 <= 1.0
|
|
38
|
+
return EXECUTOR_BODY if ((nx - 0.62) / 0.10)**2 + ((ny - 0.85) / 0.15)**2 <= 1.0
|
|
15
39
|
|
|
16
40
|
nil
|
|
17
41
|
end
|
|
18
42
|
|
|
19
43
|
def crawler(nx, ny)
|
|
20
|
-
return
|
|
21
|
-
return
|
|
22
|
-
return
|
|
23
|
-
return
|
|
24
|
-
return
|
|
44
|
+
return CRAWLER_EYE if ((nx - 0.36) / 0.063)**2 + ((ny - 0.28) / 0.063)**2 <= 1.0
|
|
45
|
+
return CRAWLER_EYE if ((nx - 0.64) / 0.063)**2 + ((ny - 0.28) / 0.063)**2 <= 1.0
|
|
46
|
+
return CRAWLER_BODY if ((nx - 0.5) / 0.40)**2 + ((ny - 0.40) / 0.40)**2 <= 1.0
|
|
47
|
+
return CRAWLER_LEG if ((nx - 0.35) / 0.12)**2 + ((ny - 0.90) / 0.10)**2 <= 1.0
|
|
48
|
+
return CRAWLER_LEG if ((nx - 0.65) / 0.12)**2 + ((ny - 0.90) / 0.10)**2 <= 1.0
|
|
25
49
|
|
|
26
50
|
nil
|
|
27
51
|
end
|
|
28
52
|
|
|
29
53
|
def player(nx, ny)
|
|
30
|
-
return
|
|
31
|
-
return
|
|
32
|
-
return
|
|
33
|
-
return
|
|
34
|
-
return
|
|
35
|
-
return
|
|
36
|
-
return
|
|
54
|
+
return PLAYER_EYE if ((nx - 0.43) / 0.045)**2 + ((ny - 0.11) / 0.045)**2 <= 1.0
|
|
55
|
+
return PLAYER_EYE if ((nx - 0.57) / 0.045)**2 + ((ny - 0.11) / 0.045)**2 <= 1.0
|
|
56
|
+
return PLAYER_HEAD if ((nx - 0.5) / 0.18)**2 + ((ny - 0.12) / 0.12)**2 <= 1.0
|
|
57
|
+
return PLAYER_NECK if ((nx - 0.5) / 0.38)**2 + ((ny - 0.30) / 0.08)**2 <= 1.0
|
|
58
|
+
return PLAYER_BODY if ((nx - 0.5) / 0.25)**2 + ((ny - 0.50) / 0.22)**2 <= 1.0
|
|
59
|
+
return PLAYER_BODY if ((nx - 0.38) / 0.10)**2 + ((ny - 0.85) / 0.15)**2 <= 1.0
|
|
60
|
+
return PLAYER_BODY if ((nx - 0.62) / 0.10)**2 + ((ny - 0.85) / 0.15)**2 <= 1.0
|
|
61
|
+
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def player_enemy(nx, ny)
|
|
66
|
+
return PLAYER_ENEMY_EYE if ((nx - 0.43) / 0.045)**2 + ((ny - 0.11) / 0.045)**2 <= 1.0
|
|
67
|
+
return PLAYER_ENEMY_EYE if ((nx - 0.57) / 0.045)**2 + ((ny - 0.11) / 0.045)**2 <= 1.0
|
|
68
|
+
return PLAYER_ENEMY_HEAD if ((nx - 0.5) / 0.18)**2 + ((ny - 0.12) / 0.12)**2 <= 1.0
|
|
69
|
+
return PLAYER_ENEMY_NECK if ((nx - 0.5) / 0.38)**2 + ((ny - 0.30) / 0.08)**2 <= 1.0
|
|
70
|
+
return PLAYER_ENEMY_BODY if ((nx - 0.5) / 0.25)**2 + ((ny - 0.50) / 0.22)**2 <= 1.0
|
|
71
|
+
return PLAYER_ENEMY_BODY if ((nx - 0.38) / 0.10)**2 + ((ny - 0.85) / 0.15)**2 <= 1.0
|
|
72
|
+
return PLAYER_ENEMY_BODY if ((nx - 0.62) / 0.10)**2 + ((ny - 0.85) / 0.15)**2 <= 1.0
|
|
37
73
|
|
|
38
74
|
nil
|
|
39
75
|
end
|
|
40
76
|
|
|
41
77
|
def training_dummy(nx, ny)
|
|
42
|
-
return
|
|
43
|
-
return
|
|
44
|
-
return
|
|
45
|
-
return
|
|
46
|
-
return
|
|
78
|
+
return DUMMY_HEAD if ((nx - 0.5) / 0.18)**2 + ((ny - 0.18) / 0.14)**2 <= 1.0
|
|
79
|
+
return DUMMY_TORSO if ((nx - 0.5) / 0.08)**2 + ((ny - 0.42) / 0.14)**2 <= 1.0
|
|
80
|
+
return DUMMY_LOWER if ((nx - 0.5) / 0.22)**2 + ((ny - 0.66) / 0.12)**2 <= 1.0
|
|
81
|
+
return DUMMY_LEG if ((nx - 0.38) / 0.08)**2 + ((ny - 0.90) / 0.12)**2 <= 1.0
|
|
82
|
+
return DUMMY_LEG if ((nx - 0.62) / 0.08)**2 + ((ny - 0.90) / 0.12)**2 <= 1.0
|
|
47
83
|
|
|
48
84
|
nil
|
|
49
85
|
end
|
|
@@ -11,6 +11,11 @@ module Termfront
|
|
|
11
11
|
SUB_TEXT = "Terminal FPS"
|
|
12
12
|
MENU_ITEMS = ["[P] PvP", "[F] Wavesfight", "[C] Campaign", "[S] Training", "[Q] Quit"].freeze
|
|
13
13
|
|
|
14
|
+
TITLE_CEIL_C = Color.rgb_to_256(0, 0, 95)
|
|
15
|
+
TITLE_FLOOR_C = Color.rgb_to_256(28, 28, 28)
|
|
16
|
+
TITLE_EXECUTOR_FALLBACK = Color.rgb_to_256(100, 60, 200)
|
|
17
|
+
TITLE_CRAWLER_FALLBACK = Color.rgb_to_256(220, 140, 30)
|
|
18
|
+
|
|
14
19
|
def initialize(stdout)
|
|
15
20
|
@stdout = stdout
|
|
16
21
|
@title_spin = 0.0
|
|
@@ -143,8 +148,8 @@ module Termfront
|
|
|
143
148
|
dists = Array.new(tw, 100.0)
|
|
144
149
|
horizon = (virt_h / 2 + bob).to_i
|
|
145
150
|
|
|
146
|
-
ceil_c =
|
|
147
|
-
floor_c =
|
|
151
|
+
ceil_c = TITLE_CEIL_C
|
|
152
|
+
floor_c = TITLE_FLOOR_C
|
|
148
153
|
|
|
149
154
|
tw.times do |col|
|
|
150
155
|
ray_a = cam_a - half_fov + fov * col.to_f / tw
|
|
@@ -201,9 +206,9 @@ module Termfront
|
|
|
201
206
|
flash = @demo_fire / 4.0 * (1.0 - dist / 4.0)
|
|
202
207
|
rr = (grey + flash * 160).to_i.clamp(0, 255)
|
|
203
208
|
gg = (grey + flash * 60).to_i.clamp(0, 255)
|
|
204
|
-
wall_c =
|
|
209
|
+
wall_c = Color.rgb_to_256(rr, gg, grey)
|
|
205
210
|
else
|
|
206
|
-
wall_c =
|
|
211
|
+
wall_c = Color.rgb_to_256(grey, grey, grey)
|
|
207
212
|
end
|
|
208
213
|
|
|
209
214
|
virt_h.times do |vr|
|
|
@@ -271,36 +276,146 @@ module Termfront
|
|
|
271
276
|
sc = Sprite.for(type, nx, ny)
|
|
272
277
|
next unless sc
|
|
273
278
|
else
|
|
274
|
-
sc = type == :executor ?
|
|
279
|
+
sc = type == :executor ? TITLE_EXECUTOR_FALLBACK : TITLE_CRAWLER_FALLBACK
|
|
275
280
|
end
|
|
276
281
|
color[vr * tw + c] = sc
|
|
277
282
|
end
|
|
278
283
|
end
|
|
279
284
|
end
|
|
280
285
|
|
|
281
|
-
# Half-block rendering
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
# Half-block rendering - build each line directly to skip fit_ansi
|
|
287
|
+
fg_256 = Renderer::FG_256
|
|
288
|
+
bg_256 = Renderer::BG_256
|
|
289
|
+
cap_w = tw < cols ? tw : cols
|
|
290
|
+
|
|
291
|
+
r = 0
|
|
292
|
+
while r < th
|
|
293
|
+
vp0_offset = (r * 2) * tw
|
|
294
|
+
vp1_offset = vp0_offset + tw
|
|
295
|
+
|
|
296
|
+
first_tc = color[vp0_offset]
|
|
297
|
+
first_bc = color[vp1_offset]
|
|
298
|
+
|
|
299
|
+
if first_tc && first_tc == first_bc
|
|
300
|
+
uniform = true
|
|
301
|
+
cu = 1
|
|
302
|
+
while cu < cap_w
|
|
303
|
+
if color[vp0_offset + cu] != first_tc || color[vp1_offset + cu] != first_bc
|
|
304
|
+
uniform = false
|
|
305
|
+
break
|
|
306
|
+
end
|
|
307
|
+
cu += 1
|
|
308
|
+
end
|
|
309
|
+
if uniform
|
|
310
|
+
lines[r] = +bg_256[first_tc] << "\e[K\e[0m"
|
|
311
|
+
r += 1
|
|
312
|
+
next
|
|
313
|
+
end
|
|
314
|
+
elsif first_tc.nil? && first_bc.nil?
|
|
315
|
+
all_nil = true
|
|
316
|
+
cu = 1
|
|
317
|
+
while cu < cap_w
|
|
318
|
+
if !color[vp0_offset + cu].nil? || !color[vp1_offset + cu].nil?
|
|
319
|
+
all_nil = false
|
|
320
|
+
break
|
|
321
|
+
end
|
|
322
|
+
cu += 1
|
|
323
|
+
end
|
|
324
|
+
if all_nil
|
|
325
|
+
lines[r] = " " * cols
|
|
326
|
+
r += 1
|
|
327
|
+
next
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
285
331
|
line = +""
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
332
|
+
pfg = nil
|
|
333
|
+
pbg = nil
|
|
334
|
+
visible = 0
|
|
335
|
+
|
|
336
|
+
c = 0
|
|
337
|
+
while c < tw && visible < cap_w
|
|
338
|
+
tc = color[vp0_offset + c]
|
|
339
|
+
bc = color[vp1_offset + c]
|
|
340
|
+
|
|
341
|
+
if tc == bc
|
|
342
|
+
run_end = c + 1
|
|
343
|
+
while run_end < tw &&
|
|
344
|
+
color[vp0_offset + run_end] == tc &&
|
|
345
|
+
color[vp1_offset + run_end] == bc
|
|
346
|
+
run_end += 1
|
|
347
|
+
end
|
|
348
|
+
n = run_end - c
|
|
349
|
+
n = cap_w - visible if visible + n > cap_w
|
|
350
|
+
|
|
351
|
+
if tc.nil?
|
|
352
|
+
if pfg || pbg
|
|
353
|
+
line << "\e[0m"
|
|
354
|
+
pfg = nil
|
|
355
|
+
pbg = nil
|
|
356
|
+
end
|
|
357
|
+
line << (n == 1 ? " " : " " * n)
|
|
358
|
+
else
|
|
359
|
+
if pfg != tc || pbg
|
|
360
|
+
line << fg_256[tc]
|
|
361
|
+
line << "\e[49m" if pbg
|
|
362
|
+
pfg = tc
|
|
363
|
+
pbg = nil
|
|
364
|
+
end
|
|
365
|
+
line << (n == 1 ? "\xE2\x96\x88" : "\xE2\x96\x88" * n)
|
|
366
|
+
end
|
|
367
|
+
visible += n
|
|
368
|
+
c += n
|
|
369
|
+
else
|
|
370
|
+
if tc && bc
|
|
371
|
+
if pfg != tc || pbg != bc
|
|
372
|
+
line << fg_256[tc]
|
|
373
|
+
line << bg_256[bc]
|
|
374
|
+
pfg = tc
|
|
375
|
+
pbg = bc
|
|
376
|
+
end
|
|
377
|
+
line << "\xE2\x96\x80"
|
|
378
|
+
elsif tc
|
|
379
|
+
if pfg != tc || pbg
|
|
380
|
+
line << fg_256[tc]
|
|
381
|
+
line << "\e[49m" if pbg
|
|
382
|
+
pfg = tc
|
|
383
|
+
pbg = nil
|
|
384
|
+
end
|
|
385
|
+
line << "\xE2\x96\x80"
|
|
386
|
+
elsif bc
|
|
387
|
+
if pfg != bc || pbg
|
|
388
|
+
line << fg_256[bc]
|
|
389
|
+
line << "\e[49m" if pbg
|
|
390
|
+
pfg = bc
|
|
391
|
+
pbg = nil
|
|
392
|
+
end
|
|
393
|
+
line << "\xE2\x96\x84"
|
|
394
|
+
else
|
|
395
|
+
if pfg || pbg
|
|
396
|
+
line << "\e[0m"
|
|
397
|
+
pfg = nil
|
|
398
|
+
pbg = nil
|
|
399
|
+
end
|
|
400
|
+
line << " "
|
|
401
|
+
end
|
|
402
|
+
visible += 1
|
|
403
|
+
c += 1
|
|
404
|
+
end
|
|
302
405
|
end
|
|
303
|
-
|
|
406
|
+
|
|
407
|
+
if visible < cols
|
|
408
|
+
if pfg || pbg
|
|
409
|
+
line << "\e[0m"
|
|
410
|
+
pfg = nil
|
|
411
|
+
pbg = nil
|
|
412
|
+
end
|
|
413
|
+
line << (" " * (cols - visible))
|
|
414
|
+
end
|
|
415
|
+
line << "\e[0m" if pfg || pbg
|
|
416
|
+
|
|
417
|
+
lines[r] = line
|
|
418
|
+
r += 1
|
|
304
419
|
end
|
|
305
420
|
|
|
306
421
|
# Title text + menu items (memoized per cols)
|
data/lib/termfront/version.rb
CHANGED
data/lib/termfront.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "io/console"
|
|
|
4
4
|
|
|
5
5
|
require_relative "termfront/version"
|
|
6
6
|
require_relative "termfront/config"
|
|
7
|
+
require_relative "termfront/color"
|
|
7
8
|
require_relative "termfront/map"
|
|
8
9
|
require_relative "termfront/weapon/base"
|
|
9
10
|
require_relative "termfront/weapon/pistol"
|
|
@@ -32,6 +33,7 @@ require_relative "termfront/remote_enemy"
|
|
|
32
33
|
require_relative "termfront/sprite"
|
|
33
34
|
require_relative "termfront/input"
|
|
34
35
|
require_relative "termfront/terminal_output"
|
|
36
|
+
require_relative "termfront/async_writer"
|
|
35
37
|
require_relative "termfront/renderer"
|
|
36
38
|
require_relative "termfront/demo_player"
|
|
37
39
|
require_relative "termfront/scene_player"
|
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.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- S-H-GAMELINKS
|
|
@@ -51,7 +51,9 @@ files:
|
|
|
51
51
|
- exe/termfront
|
|
52
52
|
- exe/termfront-server
|
|
53
53
|
- lib/termfront.rb
|
|
54
|
+
- lib/termfront/async_writer.rb
|
|
54
55
|
- lib/termfront/audio_manager.rb
|
|
56
|
+
- lib/termfront/color.rb
|
|
55
57
|
- lib/termfront/config.rb
|
|
56
58
|
- lib/termfront/demo_player.rb
|
|
57
59
|
- lib/termfront/drop_item/base.rb
|