termfront 0.1.1 → 0.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 361fd9f04e455824ea42245b3dae9c5eda732f7846a8345756809353bebc0b6d
4
- data.tar.gz: 50480fe780ff0cc795004616228aa7042889fbe606289645cb3c973b3657005f
3
+ metadata.gz: 64c01550bc002aa40824296474323c7dc6126447fe52bd8acb8cf150514a68af
4
+ data.tar.gz: 07c871ac0a859394d94d98189700195a8a98469c0f319d8748482ff79f617883
5
5
  SHA512:
6
- metadata.gz: ae66c3672d820736a3155a2d78b41456f90c4f4043421d32b4cbef5b610e4aa697886092e15f64ef086fff5232574bbdae82b977de099df2c9bd00440d1238c8
7
- data.tar.gz: 56ad7d6332ac2828cbd75b0b8087a8230367be8c4462f161c4db731ae4f6203daeaeb77a49d10fd4063f09778b64091406ddd06026e6a9f08ecc3eef397b59eb
6
+ metadata.gz: 665f32c4d60b34330ba53c5beafed57826c5b1402a9855c613cd40941dbb4ea8bd29dd93eeb564b90513809d7f26e7f649624d1fe749054ead899884865f74c3
7
+ data.tar.gz: 5d5f99b125210dbf8980cea284f112279178d228499f1caad9880deb99869cced727bfd180d5b1b8db645c10aa6627c7ba79e218b6093fd8e74b37f2bf5bf102
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 4.0.5
data/CHANGELOG.md CHANGED
@@ -6,6 +6,24 @@ The format is based on Keep a Changelog, and this project follows Semantic Versi
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.3] - 2026-05-24
10
+
11
+ ### Fixed
12
+
13
+ - Fixed TLS server certificate loading to include intermediate certificates from `fullchain.pem`, allowing clients to verify Let's Encrypt chains correctly
14
+
15
+ ## [0.1.2] - 2026-05-23
16
+
17
+ ### Added
18
+
19
+ - Added TLS verification coverage for multiplayer connection settings and certificate trust handling
20
+
21
+ ### Changed
22
+
23
+ - Added visible remote shot tracer effects during PvP and Wavesfight multiplayer matches
24
+ - Enabled TLS certificate chain and hostname verification for multiplayer client connections
25
+ - Added support for `TERMFRONT_TLS_CERT_FILE` and `TERMFRONT_TLS_KEY_FILE` when loading the server certificate
26
+
9
27
  ## [0.1.1] - 2026-05-21
10
28
 
11
29
  ### Changed
data/README.md CHANGED
@@ -54,6 +54,14 @@ Start a PvP server:
54
54
  termfront-server
55
55
  ```
56
56
 
57
+ Use custom TLS certificate paths:
58
+
59
+ ```bash
60
+ TERMFRONT_TLS_CERT_FILE=/path/to/fullchain.pem \
61
+ TERMFRONT_TLS_KEY_FILE=/path/to/privkey.pem \
62
+ termfront-server
63
+ ```
64
+
57
65
  Use a custom port:
58
66
 
59
67
  ```bash
@@ -110,7 +118,7 @@ Wavesfight is a PvE survival mode built on selected campaign maps.
110
118
  PvP is currently marked experimental.
111
119
 
112
120
  - The server listens on TCP and wraps traffic with TLS.
113
- - The client connects directly to `host:port`.
121
+ - The client connects directly to `host:port` and verifies the server certificate and hostname.
114
122
  - The default multiplayer endpoint is `termfront.gamelinks007.net:443`.
115
123
  - Matchmaking now supports `1v1`, `2v2`, and `4v4`.
116
124
  - Players choose the match size on the client, and the server keeps separate queues for each mode.
@@ -336,6 +336,7 @@ module Termfront
336
336
  remote[:current].health = msg[:h]
337
337
  remote[:current].weapon = msg[:w]&.to_sym
338
338
  remote[:current].ammo = msg[:am]
339
+ spawn_remote_projectile_effect(msg) if (remote[:current].fire_flash || 0) <= 0 && (msg[:ff] || 0) > 0
339
340
  remote[:current].fire_flash = msg[:ff] || 0
340
341
  remote[:lerp_t] = 0.0
341
342
  end
@@ -377,6 +378,7 @@ module Termfront
377
378
 
378
379
  process_fire_pvp if @player.fire_flash == 4
379
380
  @player.fire_flash -= 1 if @player.fire_flash > 0
381
+ update_projectile_effects(dt)
380
382
 
381
383
  @player.update_shield(dt, @stdout, audio: @audio)
382
384
 
@@ -476,6 +478,7 @@ module Termfront
476
478
  render_pvp_hud(buf, cols)
477
479
  render_view(buf, view_h, view_w, wtop, wbot, wcol)
478
480
  render_remote_players_3d(buf, view_h, view_w, virt_h, dists)
481
+ render_pvp_projectiles(buf, view_h, view_w, virt_h, dists)
479
482
  buf << "\e[#{3 + view_h};1H"
480
483
  render_pvp_radar(buf, cols, radar_h)
481
484
  render_crosshair(buf, view_h, view_w, cols)
@@ -635,6 +638,64 @@ module Termfront
635
638
  end
636
639
  end
637
640
 
641
+ def render_pvp_projectiles(buf, view_h, view_w, virt_h, dists)
642
+ dx = Math.cos(@player.angle)
643
+ dy = Math.sin(@player.angle)
644
+ px = -dy * Math.tan(Config::FOV / 2.0)
645
+ py = dx * Math.tan(Config::FOV / 2.0)
646
+ inv = 1.0 / (px * dy - py * dx)
647
+
648
+ sprites = []
649
+ @projectiles.each do |projectile|
650
+ ex = projectile[:x] - @player.x
651
+ ey = projectile[:y] - @player.y
652
+ tx = inv * (dy * ex - dx * ey)
653
+ tz = inv * (-py * ex + px * ey)
654
+ next if tz < 0.2
655
+
656
+ sprites << [tz, tx, projectile]
657
+ end
658
+ sprites.sort_by! { |sprite| -sprite[0] }
659
+
660
+ sprites.each do |tz, tx, projectile|
661
+ sx = ((view_w / 2.0) * (1 + tx / tz)).to_i
662
+ pw = (4.0 / tz).ceil.clamp(1, 5)
663
+ ph = (virt_h / tz * 0.15).ceil.clamp(2, 6)
664
+ vmid = virt_h / 2
665
+ draw_top = [(vmid - ph / 2), 0].max
666
+ draw_bot = [(vmid + ph / 2).clamp(draw_top + 2, virt_h), virt_h].min
667
+ start_x = [sx - pw / 2, 0].max
668
+ end_x = [sx + pw / 2, view_w - 1].min
669
+
670
+ start_x.upto(end_x) do |c|
671
+ next if c < 0 || c >= view_w
672
+ next if dists[c] < tz
673
+
674
+ r_top = (draw_top / 2.0).ceil
675
+ r_bot = [(draw_bot / 2.0).floor, r_top + 1].max
676
+ r_top.upto(r_bot - 1) do |r|
677
+ next if r < 0 || r >= view_h
678
+
679
+ vp0 = r * 2
680
+ vp1 = r * 2 + 1
681
+ top_in = vp0 >= draw_top && vp0 < draw_bot
682
+ bot_in = vp1 >= draw_top && vp1 < draw_bot
683
+ next unless top_in || bot_in
684
+
685
+ proj_color = projectile[:color]
686
+ buf << "\e[#{3 + r};#{c + 1}H"
687
+ buf << if top_in && bot_in
688
+ "\e[38;2;#{proj_color}m\xE2\x96\x88\e[0m"
689
+ elsif top_in
690
+ "\e[38;2;#{proj_color}m\xE2\x96\x80\e[0m"
691
+ else
692
+ "\e[38;2;#{proj_color}m\xE2\x96\x84\e[0m"
693
+ end
694
+ end
695
+ end
696
+ end
697
+ end
698
+
638
699
  def tint_player_color(color, mode)
639
700
  return nil unless color
640
701
  return color if mode == :ally
@@ -822,6 +883,30 @@ module Termfront
822
883
  STDIN.raw { |s| s.getc }
823
884
  end
824
885
 
886
+ def spawn_remote_projectile_effect(msg)
887
+ shock = msg[:w].to_s.start_with?("shock")
888
+ color = shock ? "80;220;255" : "255;210;80"
889
+ speed = shock ? 18.0 : 14.0
890
+ angle = msg[:a]
891
+ @projectiles << {
892
+ x: msg[:x],
893
+ y: msg[:y],
894
+ vx: Math.cos(angle) * speed,
895
+ vy: Math.sin(angle) * speed,
896
+ ttl: 0.18,
897
+ color: color
898
+ }
899
+ end
900
+
901
+ def update_projectile_effects(dt)
902
+ @projectiles.reject! do |projectile|
903
+ projectile[:x] += projectile[:vx] * dt
904
+ projectile[:y] += projectile[:vy] * dt
905
+ projectile[:ttl] -= dt
906
+ projectile[:ttl] <= 0 || @map.wall_at?(projectile[:x], projectile[:y])
907
+ end
908
+ end
909
+
825
910
  def notify_death_if_needed
826
911
  return unless @player.dead
827
912
  return if @sent_dead
@@ -3,29 +3,47 @@
3
3
  require "socket"
4
4
  require "openssl"
5
5
  require "json"
6
+ require "time"
6
7
 
7
8
  module Termfront
8
9
  module Network
9
10
  class Connection
11
+ PeerInfo = Struct.new(
12
+ :certificate_sha256,
13
+ :public_key_sha256,
14
+ :subject,
15
+ :issuer,
16
+ :not_after,
17
+ keyword_init: true
18
+ )
19
+
10
20
  attr_reader :rtt
21
+ attr_reader :peer_info
11
22
 
12
23
  def initialize
13
24
  @sock = nil
14
25
  @buf = +""
15
26
  @ping_ts = 0
16
27
  @rtt = 0
28
+ @peer_info = nil
17
29
  end
18
30
 
19
- def connect(host, port)
31
+ def connect(host, port, ca_file: nil)
20
32
  tcp = TCPSocket.new(host, port)
21
33
  tcp.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
22
34
 
23
- ctx = OpenSSL::SSL::SSLContext.new
24
- ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
35
+ ctx = build_ssl_context(ca_file: ca_file)
25
36
  @sock = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
26
37
  @sock.hostname = host if @sock.respond_to?(:hostname=)
27
38
  @sock.sync = true
28
39
  @sock.connect
40
+ @sock.post_connection_check(host)
41
+ @peer_info = build_peer_info(@sock.peer_cert)
42
+ rescue StandardError
43
+ tcp&.close
44
+ @sock = nil
45
+ @peer_info = nil
46
+ raise
29
47
  end
30
48
 
31
49
  def send_msg(hash)
@@ -76,6 +94,7 @@ module Termfront
76
94
  nil
77
95
  end
78
96
  @sock = nil
97
+ @peer_info = nil
79
98
  end
80
99
 
81
100
  def connected?
@@ -96,6 +115,31 @@ module Termfront
96
115
  def clock
97
116
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
98
117
  end
118
+
119
+ def build_ssl_context(ca_file:)
120
+ ctx = OpenSSL::SSL::SSLContext.new
121
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
122
+ ctx.verify_hostname = true if ctx.respond_to?(:verify_hostname=)
123
+ ctx.cert_store = build_cert_store(ca_file: ca_file)
124
+ ctx
125
+ end
126
+
127
+ def build_cert_store(ca_file:)
128
+ store = OpenSSL::X509::Store.new
129
+ store.set_default_paths
130
+ store.add_file(ca_file) if ca_file
131
+ store
132
+ end
133
+
134
+ def build_peer_info(cert)
135
+ PeerInfo.new(
136
+ certificate_sha256: OpenSSL::Digest::SHA256.hexdigest(cert.to_der),
137
+ public_key_sha256: OpenSSL::Digest::SHA256.hexdigest(cert.public_key.to_der),
138
+ subject: cert.subject.to_s,
139
+ issuer: cert.issuer.to_s,
140
+ not_after: cert.not_after.utc
141
+ )
142
+ end
99
143
  end
100
144
  end
101
145
  end
@@ -43,11 +43,12 @@ module Termfront
43
43
  end
44
44
 
45
45
  def run
46
- cert, key = load_or_create_cert
46
+ cert, key, chain = load_or_create_cert
47
47
 
48
48
  ctx = OpenSSL::SSL::SSLContext.new
49
49
  ctx.cert = cert
50
50
  ctx.key = key
51
+ ctx.extra_chain_cert = chain unless chain.empty?
51
52
 
52
53
  tcp_server = TCPServer.new("0.0.0.0", @port)
53
54
  ssl_server = OpenSSL::SSL::SSLServer.new(tcp_server, ctx)
@@ -589,11 +590,14 @@ module Termfront
589
590
  end
590
591
 
591
592
  def load_or_create_cert
592
- cert_file = "termfront_server.crt"
593
- key_file = "termfront_server.key"
593
+ cert_file = ENV.fetch("TERMFRONT_TLS_CERT_FILE", "termfront_server.crt")
594
+ key_file = ENV.fetch("TERMFRONT_TLS_KEY_FILE", "termfront_server.key")
594
595
 
595
596
  if File.exist?(cert_file) && File.exist?(key_file)
596
- cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
597
+ certs = OpenSSL::X509::Certificate.load(File.read(cert_file))
598
+ certs = [certs] unless certs.is_a?(Array)
599
+ cert = certs.first
600
+ chain = certs.drop(1)
597
601
  key = OpenSSL::PKey::RSA.new(File.read(key_file))
598
602
  puts "Loaded existing certificate."
599
603
  else
@@ -601,8 +605,9 @@ module Termfront
601
605
  File.write(cert_file, cert.to_pem)
602
606
  File.write(key_file, key.to_pem)
603
607
  puts "Generated new self-signed certificate."
608
+ chain = []
604
609
  end
605
- [cert, key]
610
+ [cert, key, chain]
606
611
  end
607
612
 
608
613
  def pvp_spawns
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Termfront
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
metadata CHANGED
@@ -1,119 +1,119 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: termfront
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
- - S-H-GAMELINKS
7
+ - S-H-GAMELINKS
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: "A terminal-based first-person shooter with DDA raycasting, campaign mode, and PvP multiplayer."
12
+ description: A terminal-based first-person shooter with DDA raycasting, campaign mode,
13
+ and PvP multiplayer.
13
14
  email:
14
- - gamelinks007@gmail.com
15
+ - gamelinks007@gmail.com
15
16
  executables:
16
- - termfront
17
- - termfront-server
17
+ - termfront
18
+ - termfront-server
18
19
  extensions: []
19
20
  extra_rdoc_files: []
20
21
  files:
21
- - CHANGELOG.md
22
- - LICENSE
23
- - LICENSE.txt
24
- - README.md
25
- - Rakefile
26
- - data/audio/THIRD_PARTY_NOTICES.md
27
- - data/audio/beep_02.ogg
28
- - data/audio/button1.ogg
29
- - data/audio/complete.ogg
30
- - data/audio/manifest.json
31
- - data/audio/mission_bgm.wav
32
- - data/audio/mission_clear_se.wav
33
- - data/audio/on.ogg
34
- - data/audio/page_se.wav
35
- - data/audio/sector.mp3
36
- - data/audio/sfx_22b.ogg
37
- - data/audio/shield_alarm_se.wav
38
- - data/audio/shield_regen_se.wav
39
- - data/audio/shoot_01.ogg
40
- - data/audio/terminal_se.wav
41
- - data/audio/title.mp3
42
- - data/audio/title_bgm.wav
43
- - data/audio/victory.mp3
44
- - data/events/corridor_sweep.json
45
- - data/events/final_push.json
46
- - data/events/stronghold.json
47
- - data/events/the_gauntlet.json
48
- - data/events/training_grounds.json
49
- - exe/termfront
50
- - exe/termfront-server
51
- - lib/termfront.rb
52
- - lib/termfront/audio_manager.rb
53
- - lib/termfront/config.rb
54
- - lib/termfront/demo_player.rb
55
- - lib/termfront/drop_item/base.rb
56
- - lib/termfront/drop_item/weapon.rb
57
- - lib/termfront/enemy/base.rb
58
- - lib/termfront/enemy/crawler.rb
59
- - lib/termfront/enemy/executor.rb
60
- - lib/termfront/game.rb
61
- - lib/termfront/input.rb
62
- - lib/termfront/map.rb
63
- - lib/termfront/mission/base.rb
64
- - lib/termfront/mission/corridor_sweep.rb
65
- - lib/termfront/mission/event_loader.rb
66
- - lib/termfront/mission/event_runtime.rb
67
- - lib/termfront/mission/final_push.rb
68
- - lib/termfront/mission/stronghold.rb
69
- - lib/termfront/mission/the_gauntlet.rb
70
- - lib/termfront/mission/training.rb
71
- - lib/termfront/mission/training_grounds.rb
72
- - lib/termfront/network/client.rb
73
- - lib/termfront/network/connection.rb
74
- - lib/termfront/network/server.rb
75
- - lib/termfront/network/wavesfight_client.rb
76
- - lib/termfront/opponent.rb
77
- - lib/termfront/player.rb
78
- - lib/termfront/projectile.rb
79
- - lib/termfront/remote_enemy.rb
80
- - lib/termfront/renderer.rb
81
- - lib/termfront/scene_player.rb
82
- - lib/termfront/sprite.rb
83
- - lib/termfront/terminal_output.rb
84
- - lib/termfront/title_screen.rb
85
- - lib/termfront/version.rb
86
- - lib/termfront/weapon/assault_rifle.rb
87
- - lib/termfront/weapon/base.rb
88
- - lib/termfront/weapon/pistol.rb
89
- - lib/termfront/weapon/shock_pistol.rb
90
- - lib/termfront/weapon/shock_rifle.rb
91
- - sig/termfront.rbs
92
- homepage: "https://github.com/S-H-GAMELINKS/termfront"
22
+ - ".ruby-version"
23
+ - CHANGELOG.md
24
+ - LICENSE
25
+ - LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - data/audio/THIRD_PARTY_NOTICES.md
29
+ - data/audio/beep_02.ogg
30
+ - data/audio/button1.ogg
31
+ - data/audio/complete.ogg
32
+ - data/audio/manifest.json
33
+ - data/audio/mission_bgm.wav
34
+ - data/audio/mission_clear_se.wav
35
+ - data/audio/on.ogg
36
+ - data/audio/page_se.wav
37
+ - data/audio/sector.mp3
38
+ - data/audio/sfx_22b.ogg
39
+ - data/audio/shield_alarm_se.wav
40
+ - data/audio/shield_regen_se.wav
41
+ - data/audio/shoot_01.ogg
42
+ - data/audio/terminal_se.wav
43
+ - data/audio/title.mp3
44
+ - data/audio/title_bgm.wav
45
+ - data/audio/victory.mp3
46
+ - data/events/corridor_sweep.json
47
+ - data/events/final_push.json
48
+ - data/events/stronghold.json
49
+ - data/events/the_gauntlet.json
50
+ - data/events/training_grounds.json
51
+ - exe/termfront
52
+ - exe/termfront-server
53
+ - lib/termfront.rb
54
+ - lib/termfront/audio_manager.rb
55
+ - lib/termfront/config.rb
56
+ - lib/termfront/demo_player.rb
57
+ - lib/termfront/drop_item/base.rb
58
+ - lib/termfront/drop_item/weapon.rb
59
+ - lib/termfront/enemy/base.rb
60
+ - lib/termfront/enemy/crawler.rb
61
+ - lib/termfront/enemy/executor.rb
62
+ - lib/termfront/game.rb
63
+ - lib/termfront/input.rb
64
+ - lib/termfront/map.rb
65
+ - lib/termfront/mission/base.rb
66
+ - lib/termfront/mission/corridor_sweep.rb
67
+ - lib/termfront/mission/event_loader.rb
68
+ - lib/termfront/mission/event_runtime.rb
69
+ - lib/termfront/mission/final_push.rb
70
+ - lib/termfront/mission/stronghold.rb
71
+ - lib/termfront/mission/the_gauntlet.rb
72
+ - lib/termfront/mission/training.rb
73
+ - lib/termfront/mission/training_grounds.rb
74
+ - lib/termfront/network/client.rb
75
+ - lib/termfront/network/connection.rb
76
+ - lib/termfront/network/server.rb
77
+ - lib/termfront/network/wavesfight_client.rb
78
+ - lib/termfront/opponent.rb
79
+ - lib/termfront/player.rb
80
+ - lib/termfront/projectile.rb
81
+ - lib/termfront/remote_enemy.rb
82
+ - lib/termfront/renderer.rb
83
+ - lib/termfront/scene_player.rb
84
+ - lib/termfront/sprite.rb
85
+ - lib/termfront/terminal_output.rb
86
+ - lib/termfront/title_screen.rb
87
+ - lib/termfront/version.rb
88
+ - lib/termfront/weapon/assault_rifle.rb
89
+ - lib/termfront/weapon/base.rb
90
+ - lib/termfront/weapon/pistol.rb
91
+ - lib/termfront/weapon/shock_pistol.rb
92
+ - lib/termfront/weapon/shock_rifle.rb
93
+ - sig/termfront.rbs
94
+ homepage: https://github.com/S-H-GAMELINKS/termfront
93
95
  licenses:
94
- - MIT
96
+ - MIT
95
97
  metadata:
96
- allowed_push_host: "https://rubygems.org"
97
- homepage_uri: "https://github.com/S-H-GAMELINKS/termfront"
98
- source_code_uri: "https://github.com/S-H-GAMELINKS/termfront"
99
- changelog_uri: "https://github.com/S-H-GAMELINKS/termfront/blob/main/CHANGELOG.md"
98
+ allowed_push_host: https://rubygems.org
99
+ homepage_uri: https://github.com/S-H-GAMELINKS/termfront
100
+ source_code_uri: https://github.com/S-H-GAMELINKS/termfront
101
+ changelog_uri: https://github.com/S-H-GAMELINKS/termfront/blob/main/CHANGELOG.md
100
102
  rdoc_options: []
101
103
  require_paths:
102
- - lib
104
+ - lib
103
105
  required_ruby_version: !ruby/object:Gem::Requirement
104
106
  requirements:
105
- -
106
- - ">="
107
- - !ruby/object:Gem::Version
108
- version: 3.2.0
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 3.2.0
109
110
  required_rubygems_version: !ruby/object:Gem::Requirement
110
111
  requirements:
111
- -
112
- - ">="
113
- - !ruby/object:Gem::Version
114
- version: "0"
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
115
  requirements: []
116
- rubygems_version: 4.1.0.dev
116
+ rubygems_version: 4.0.10
117
117
  specification_version: 4
118
118
  summary: Terminal FPS game with raycasting engine
119
119
  test_files: []