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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d84ea40d760b9d62c0037b83a0b83576725a12a16c3aa2937e16025e6a798015
|
|
4
|
+
data.tar.gz: e33b7fc4bde17b2924ea6a1f30bb11f6f8d30fbba75bd9f9130c773b1b1cf33a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc5856f2bb1eea72307f2ba41af96e5b3412e7ef893fe8a3609f0841ab88d693653ed797c072eff8a4092d01511c810edd2fc848c12dcd17a07b8803181cc551
|
|
7
|
+
data.tar.gz: 461847cef45b100959a044dcb22cca9c537a806331168834d9f936fd6055c4c668581f376058003ad2eb28abdd805cc3f2d82d6f52a36644b7f7d53a13635f9f
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,49 @@ The format is based on Keep a Changelog, and this project follows Semantic Versi
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.6] - 2026-05-26
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Memoize the HUD shield and ammo lines so `fit_ansi` only re-runs when the shown shield value, weapon, ammo, pickup hint, or terminal width actually change
|
|
14
|
+
- Inline the color-mode branching and SGR lookups into `Renderer#render_view` to remove per-cell `bg_only?`, `ansi_fg`, and `ansi_bg` method calls (about 72,000 calls per second at 30 Hz on an 80-wide terminal)
|
|
15
|
+
- Enable YJIT on startup so the hot Ruby methods in the renderer (`build_view_pixels`, `render_view`, `cast_ray`, etc.) get JIT-compiled instead of interpreted
|
|
16
|
+
- Rewrite `TerminalOutput.fit_ansi` to walk the input by byte index with `getbyte` and `byteslice` instead of scanning with a regular expression, removing the per-line `Regexp#match` / `MatchData` allocations that dominated the renderer's CPU profile under YJIT
|
|
17
|
+
- Render the title screen at 30 Hz while still advancing its spin animation and polling input at 60 Hz, so the per-frame renderer cost halves without making the demo motion feel choppy
|
|
18
|
+
- Cache the demo mission instance, its tile-map, and its enemy definitions inside `TitleScreen#initialize` instead of rebuilding them every frame
|
|
19
|
+
- Render singleplayer and Wavesfight at 30 Hz while still running the update step, physics, and input polling at 60 Hz, halving the in-game renderer cost without slowing the simulation or input response
|
|
20
|
+
- Rewrite `Renderer#build_view_pixels` with column-major `while` loops that read `wtop`/`wbot`/`wcol` once per column and fill the ceiling / wall / floor segments without per-cell branching, hoisting `Config::CEIL_C` / `Config::FLOOR_C` and `@pixels` as locals so YJIT keeps the hot loop tight
|
|
21
|
+
- Memoize the title screen's static lines (logo, subtitle, and the five menu entries) per terminal width so `fit_ansi` runs once instead of seven times per title-screen frame
|
|
22
|
+
- Memoize every line of `Game#render_mission_select` keyed by terminal width, current selection, difficulty, and mission class, so the nine `fit_ansi` calls per frame collapse to cache lookups while the cursor is idle
|
|
23
|
+
- Reuse the radar's enemy / drop / terminal / ally cell hashes between frames and key them by integer (`sy * diam + sx`) to drop per-cell `Array` allocations, hoisting `Config::RADAR_RANGE`, `RADAR_RANGE_SQ`, `r*r`, and the player position as locals so YJIT keeps the radar projection loops tight
|
|
24
|
+
- Tighten the enemy half of `Renderer#overlay_enemies_3d`: hoist the FOV tangent, player position, and half-frame constants as locals, replace the `upto` loops with `while`, use integer shift instead of `to_f` / `.ceil` / `.floor` for half-block row indexing, and cache the float versions of `actual_h` / `actual_w` so the per-cell sprite division loop stops allocating new floats every iteration
|
|
25
|
+
- Apply the same `while`-loop and integer-math pattern to the drop and projectile branches of `Renderer#overlay_enemies_3d`, reusing the hoisted player position and half-frame constants and computing the projectile color directly instead of through an intermediate ANSI code string
|
|
26
|
+
|
|
27
|
+
## [0.1.5] - 2026-05-25
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Radar distance culling now compares squared distances against `Config::RADAR_RANGE_SQ` instead of taking a square root per entity per frame
|
|
32
|
+
- 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
|
|
33
|
+
- Build the radar background grid, horizontal rule, and ANSI-styled radar glyphs once and reuse them across frames
|
|
34
|
+
- 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
|
|
35
|
+
- Reuse renderer sprite collection arrays and the radar line buffer across frames, and sort sprites with a comparator block instead of `sort_by`
|
|
36
|
+
- Cache the terminal `winsize` inside the renderer and invalidate the cache from a `SIGWINCH` handler instead of issuing an ioctl every frame
|
|
37
|
+
- 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
|
|
38
|
+
- Multiplayer server `broadcast` now serializes each outgoing message once and writes the same JSON line to every recipient
|
|
39
|
+
- 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
|
|
40
|
+
- 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
|
|
41
|
+
- 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
|
|
42
|
+
- Run the title demo, campaign missions, Wavesfight, and PvP at 60 FPS
|
|
43
|
+
|
|
44
|
+
### Removed
|
|
45
|
+
|
|
46
|
+
- 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
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- 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%
|
|
51
|
+
|
|
9
52
|
## [0.1.34] - 2026-05-25
|
|
10
53
|
|
|
11
54
|
### Fixed
|
data/exe/termfront
CHANGED
data/lib/termfront/config.rb
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Termfront
|
|
4
4
|
module Config
|
|
5
|
-
FRAME_DT = 1.0 /
|
|
5
|
+
FRAME_DT = 1.0 / 60.0
|
|
6
|
+
RENDER_DT = 1.0 / 30.0
|
|
6
7
|
FOV = 66.0 * Math::PI / 180.0
|
|
7
8
|
PLAYER_RADIUS = 0.2
|
|
8
9
|
KEY_TIMEOUT = 5
|
|
@@ -29,6 +30,7 @@ module Termfront
|
|
|
29
30
|
|
|
30
31
|
RADAR_RADIUS = 3
|
|
31
32
|
RADAR_RANGE = 12.0
|
|
33
|
+
RADAR_RANGE_SQ = RADAR_RANGE * RADAR_RANGE
|
|
32
34
|
|
|
33
35
|
PVP_PORT = 7777
|
|
34
36
|
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
|
|
@@ -130,6 +131,7 @@ module Termfront
|
|
|
130
131
|
def run_game_loop(show_complete_banner: true)
|
|
131
132
|
STDIN.raw do |stdin|
|
|
132
133
|
last_time = clock
|
|
134
|
+
last_render = last_time - Config::RENDER_DT
|
|
133
135
|
|
|
134
136
|
loop do
|
|
135
137
|
now = clock
|
|
@@ -145,11 +147,14 @@ module Termfront
|
|
|
145
147
|
end
|
|
146
148
|
|
|
147
149
|
update(dt)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
if now - last_render >= Config::RENDER_DT
|
|
151
|
+
@renderer.render(
|
|
152
|
+
player: @player, map: @map,
|
|
153
|
+
enemies: @enemies, projectiles: @projectiles,
|
|
154
|
+
drops: @player.drops, terminals: @terminals
|
|
155
|
+
)
|
|
156
|
+
last_render = now
|
|
157
|
+
end
|
|
153
158
|
|
|
154
159
|
if @player.dead
|
|
155
160
|
rows, cols = @stdout.winsize
|
|
@@ -175,6 +180,7 @@ module Termfront
|
|
|
175
180
|
def run_wavesfight_loop
|
|
176
181
|
STDIN.raw do |stdin|
|
|
177
182
|
last_time = clock
|
|
183
|
+
last_render = last_time - Config::RENDER_DT
|
|
178
184
|
|
|
179
185
|
loop do
|
|
180
186
|
now = clock
|
|
@@ -190,12 +196,15 @@ module Termfront
|
|
|
190
196
|
end
|
|
191
197
|
|
|
192
198
|
update(dt)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
if now - last_render >= Config::RENDER_DT
|
|
200
|
+
@renderer.render(
|
|
201
|
+
player: @player, map: @map,
|
|
202
|
+
enemies: @enemies, projectiles: @projectiles,
|
|
203
|
+
drops: @player.drops, terminals: @terminals,
|
|
204
|
+
status_line: " WAVE #{@wave} #{Enemy::Base::DIFFICULTIES[@difficulty][:name]}"
|
|
205
|
+
)
|
|
206
|
+
last_render = now
|
|
207
|
+
end
|
|
199
208
|
|
|
200
209
|
if @player.dead
|
|
201
210
|
rows, cols = @stdout.winsize
|
|
@@ -208,6 +217,7 @@ module Termfront
|
|
|
208
217
|
show_wave_clear
|
|
209
218
|
start_wavesfight_wave
|
|
210
219
|
last_time = clock
|
|
220
|
+
last_render = clock - Config::RENDER_DT
|
|
211
221
|
end
|
|
212
222
|
|
|
213
223
|
cap_frame(now)
|
|
@@ -413,7 +423,7 @@ module Termfront
|
|
|
413
423
|
end
|
|
414
424
|
|
|
415
425
|
def replenish_wavesfight_loadout
|
|
416
|
-
@player.shield =
|
|
426
|
+
@player.shield = Config::SHIELD_MAX
|
|
417
427
|
@player.health = [@player.health + 20.0, Config::HEALTH_MAX].min
|
|
418
428
|
@player.last_damage = -Config::SHIELD_DELAY
|
|
419
429
|
@player.dead = false
|
|
@@ -542,62 +552,78 @@ module Termfront
|
|
|
542
552
|
end
|
|
543
553
|
end
|
|
544
554
|
|
|
555
|
+
MISSION_SELECT_DIFF_COLORS = ["\e[92m", "\e[93m", "\e[38;2;255;165;0m", "\e[91m"].freeze
|
|
556
|
+
MISSION_SELECT_CTRL = "Up/Down: Select Left/Right: Difficulty Enter/1-5: Start Q: Back"
|
|
557
|
+
|
|
545
558
|
def render_mission_select(selected, missions, title)
|
|
546
559
|
rows, cols = @stdout.winsize
|
|
547
560
|
buf = TerminalOutput.begin_frame(home: true)
|
|
548
561
|
lines = Array.new(rows) { " " * cols }
|
|
562
|
+
cache = (@mission_select_cache ||= {})
|
|
549
563
|
|
|
550
|
-
|
|
551
|
-
|
|
564
|
+
lines[1] = cache[[:title, cols, title]] ||= begin
|
|
565
|
+
tc = [(cols - title.size) / 2 + 1, 1].max
|
|
566
|
+
TerminalOutput.fit_ansi("#{" " * (tc - 1)}\e[1;38;2;120;140;255m#{title}\e[0m", cols)
|
|
567
|
+
end
|
|
552
568
|
|
|
553
569
|
diff = Enemy::Base::DIFFICULTIES[@difficulty]
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
570
|
+
lines[2] = cache[[:diff, cols, @difficulty]] ||= begin
|
|
571
|
+
diff_label = "< #{diff[:name]} >"
|
|
572
|
+
dc = [(cols - diff_label.size) / 2 + 1, 1].max
|
|
573
|
+
TerminalOutput.fit_ansi("#{" " * (dc - 1)}#{MISSION_SELECT_DIFF_COLORS[@difficulty]}#{diff_label}\e[0m", cols)
|
|
574
|
+
end
|
|
558
575
|
|
|
559
576
|
missions.each_with_index do |klass, i|
|
|
560
|
-
m = klass.new
|
|
561
577
|
row = 5 + i * 2
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
578
|
+
is_selected = i == selected
|
|
579
|
+
lines[row - 1] = cache[[:mission, cols, i, is_selected, klass]] ||= begin
|
|
580
|
+
m = klass.new
|
|
581
|
+
label = " #{i + 1}. #{m.name}"
|
|
582
|
+
lc = [(cols - 40) / 2 + 1, 1].max
|
|
583
|
+
text = if is_selected
|
|
584
|
+
"\e[1;97;44m> #{label.strip.ljust(38)}\e[0m"
|
|
585
|
+
else
|
|
586
|
+
"\e[97m #{label.strip.ljust(38)}\e[0m"
|
|
587
|
+
end
|
|
588
|
+
TerminalOutput.fit_ansi("#{" " * (lc - 1)}#{text}", cols)
|
|
589
|
+
end
|
|
570
590
|
end
|
|
571
591
|
|
|
572
592
|
brief_row = 5 + missions.size * 2 + 1
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
593
|
+
lines[brief_row - 1] = cache[[:brief, cols, missions[selected]]] ||= begin
|
|
594
|
+
m = missions[selected].new
|
|
595
|
+
briefing = m.briefing
|
|
596
|
+
bc = [(cols - briefing.size) / 2 + 1, 1].max
|
|
597
|
+
TerminalOutput.fit_ansi("#{" " * (bc - 1)}\e[38;2;180;180;200m#{briefing}\e[0m", cols)
|
|
598
|
+
end
|
|
577
599
|
|
|
578
600
|
info_row = brief_row + 2
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
601
|
+
lines[info_row - 1] = cache[[:info, cols, @difficulty, missions[selected]]] ||= begin
|
|
602
|
+
m = missions[selected].new
|
|
603
|
+
edefs = m.enemy_defs
|
|
604
|
+
base_crawler = edefs.count { |e| e[4] == :crawler }
|
|
605
|
+
base_executor = edefs.count { |e| e[4] == :executor }
|
|
606
|
+
extra = diff[:extra_enemies]
|
|
607
|
+
extra_crawler = 0
|
|
608
|
+
extra_executor = 0
|
|
609
|
+
extra.times do |i|
|
|
610
|
+
src_type = edefs[i % edefs.size][4]
|
|
611
|
+
src_type == :crawler ? (extra_crawler += 1) : (extra_executor += 1)
|
|
612
|
+
end
|
|
613
|
+
crawler_c = base_crawler + extra_crawler
|
|
614
|
+
executor_c = base_executor + extra_executor
|
|
615
|
+
info = "Enemies: #{crawler_c} Crawler#{crawler_c != 1 ? "s" : ""}"
|
|
616
|
+
info += ", #{executor_c} Executor#{executor_c != 1 ? "s" : ""}" if executor_c > 0
|
|
617
|
+
info += " | HP x#{diff[:hp_mult]}"
|
|
618
|
+
ic = [(cols - info.size) / 2 + 1, 1].max
|
|
619
|
+
TerminalOutput.fit_ansi("#{" " * (ic - 1)}\e[38;2;140;140;160m#{info}\e[0m", cols)
|
|
588
620
|
end
|
|
589
|
-
crawler_c = base_crawler + extra_crawler
|
|
590
|
-
executor_c = base_executor + extra_executor
|
|
591
|
-
info = "Enemies: #{crawler_c} Crawler#{crawler_c != 1 ? "s" : ""}"
|
|
592
|
-
info += ", #{executor_c} Executor#{executor_c != 1 ? "s" : ""}" if executor_c > 0
|
|
593
|
-
info += " | HP x#{diff[:hp_mult]}"
|
|
594
|
-
ic = [(cols - info.size) / 2 + 1, 1].max
|
|
595
|
-
lines[info_row - 1] = TerminalOutput.fit_ansi("#{" " * (ic - 1)}\e[38;2;140;140;160m#{info}\e[0m", cols)
|
|
596
621
|
|
|
597
622
|
ctrl_row = info_row + 2
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
623
|
+
lines[ctrl_row - 1] = cache[[:ctrl, cols]] ||= begin
|
|
624
|
+
cc = [(cols - MISSION_SELECT_CTRL.size) / 2 + 1, 1].max
|
|
625
|
+
TerminalOutput.fit_ansi("#{" " * (cc - 1)}\e[38;2;100;100;120m#{MISSION_SELECT_CTRL}\e[0m", cols)
|
|
626
|
+
end
|
|
601
627
|
|
|
602
628
|
lines.each_with_index do |line, index|
|
|
603
629
|
buf << line
|
|
@@ -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
|