tf2_line_parser 0.4.0 → 0.5.0

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: 154268d4f08d4e2884a89fa90b3a18b901c6cafbd846fa58636e84fb2b59822f
4
- data.tar.gz: 4056b354a1d1b3f98b1920250a80b7dc9541042588319510e48d9378bfa7422a
3
+ metadata.gz: 5c1d38c81d58d2ecb10aa612b2602de4abf47b540ec3caf003273a87cdcdb5ba
4
+ data.tar.gz: 7993c5640a38063e5082780475881b1faca2898a5f6555ac4040ff612f341f2c
5
5
  SHA512:
6
- metadata.gz: e154de1385eb0fd25814f087f53578590af635aaff826d6a9a9df822e288359254834c14b0a99170c25dc61bdea57b8712b22913bfe38998fccc07d172b22c8b
7
- data.tar.gz: f8d7bcd26fb42e81b4de558d302e3574754542e0e1d65593bb925b92749453a64f706a41253df9a4eced5579fed7230f75b6aeb31db03615bbc5877f7e1ae1ea
6
+ metadata.gz: 41f0d0adece361d3e97a3ec48466dd8123fdc95d8ea26f5616f57f125426225a36f6dfc2d1a5611115993863980d2d18e08d49f46397bba71dbe8775f0c7ef65
7
+ data.tar.gz: 872376b6810e9d6031cbd0d6cf38156ef99fba2dbf91174761c354a44fa040f9d7809bb5736518406bccdecbfb1f3a3f1314f6c54be14000a88af223a2287ee0
data/Gemfile.lock CHANGED
@@ -1,30 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tf2_line_parser (0.4.0)
5
- activesupport
4
+ tf2_line_parser (0.5.0)
6
5
 
7
6
  GEM
8
7
  remote: https://rubygems.org/
9
8
  specs:
10
- activesupport (8.0.2)
11
- base64
12
- benchmark (>= 0.3)
13
- bigdecimal
14
- concurrent-ruby (~> 1.0, >= 1.3.1)
15
- connection_pool (>= 2.2.5)
16
- drb
17
- i18n (>= 1.6, < 2)
18
- logger (>= 1.4.2)
19
- minitest (>= 5.1)
20
- securerandom (>= 0.3)
21
- tzinfo (~> 2.0, >= 2.0.5)
22
- uri (>= 0.13.1)
23
- base64 (0.3.0)
24
- benchmark (0.4.1)
25
- bigdecimal (3.2.2)
26
- concurrent-ruby (1.3.5)
27
- connection_pool (2.5.3)
9
+ bigdecimal (4.0.1)
28
10
  coveralls_reborn (0.29.0)
29
11
  simplecov (~> 0.22.0)
30
12
  term-ansicolor (~> 1.7)
@@ -32,42 +14,41 @@ GEM
32
14
  tins (~> 1.32)
33
15
  diff-lcs (1.6.2)
34
16
  docile (1.4.1)
35
- drb (2.2.3)
36
- i18n (1.14.7)
37
- concurrent-ruby (~> 1.0)
38
- json (2.12.2)
39
- logger (1.7.0)
40
- minitest (5.25.5)
41
- rspec (3.13.1)
17
+ io-console (0.8.2)
18
+ json (2.18.0)
19
+ mize (0.6.1)
20
+ readline (0.0.4)
21
+ reline
22
+ reline (0.6.3)
23
+ io-console (~> 0.5)
24
+ rspec (3.13.2)
42
25
  rspec-core (~> 3.13.0)
43
26
  rspec-expectations (~> 3.13.0)
44
27
  rspec-mocks (~> 3.13.0)
45
- rspec-core (3.13.4)
28
+ rspec-core (3.13.6)
46
29
  rspec-support (~> 3.13.0)
47
30
  rspec-expectations (3.13.5)
48
31
  diff-lcs (>= 1.2.0, < 2.0)
49
32
  rspec-support (~> 3.13.0)
50
- rspec-mocks (3.13.5)
33
+ rspec-mocks (3.13.7)
51
34
  diff-lcs (>= 1.2.0, < 2.0)
52
35
  rspec-support (~> 3.13.0)
53
- rspec-support (3.13.4)
54
- securerandom (0.4.1)
36
+ rspec-support (3.13.6)
55
37
  simplecov (0.22.0)
56
38
  docile (~> 1.1)
57
39
  simplecov-html (~> 0.11)
58
40
  simplecov_json_formatter (~> 0.1)
59
- simplecov-html (0.13.1)
41
+ simplecov-html (0.13.2)
60
42
  simplecov_json_formatter (0.1.4)
61
43
  sync (0.5.0)
62
- term-ansicolor (1.11.2)
63
- tins (~> 1.0)
64
- thor (1.3.2)
65
- tins (1.38.0)
44
+ term-ansicolor (1.11.3)
45
+ tins (~> 1)
46
+ thor (1.4.0)
47
+ tins (1.51.0)
66
48
  bigdecimal
49
+ mize (~> 0.6)
50
+ readline
67
51
  sync
68
- tzinfo (2.0.6)
69
- concurrent-ruby (~> 1.0)
70
- uri (1.0.3)
71
52
 
72
53
  PLATFORMS
73
54
  ruby
@@ -4,11 +4,16 @@ module TF2LineParser
4
4
  module Events
5
5
  class Airshot < Damage
6
6
  def self.regex
7
- @regex ||= /#{regex_time} #{regex_player} triggered "damage" #{regex_damage_against}\(damage "(?'value'\d+)"\)#{regex_realdamage}#{regex_weapon}#{regex_healing}#{regex_crit}#{regex_headshot} #{regex_airshot}$/.freeze
7
+ # Airshot can appear after weapon, with optional height at the end
8
+ @regex ||= /#{regex_time} #{regex_player} triggered "damage" #{regex_damage_against}\(damage "(?'value'\d+)"\)#{regex_realdamage}#{regex_weapon}#{regex_airshot}#{regex_height}/.freeze
8
9
  end
9
10
 
10
11
  def self.regex_airshot
11
- @regex_airshot ||= '\(airshot "(?\'airshot\'\w*)"\)'
12
+ @regex_airshot ||= / \(airshot "(?'airshot'\w*)"\)/
13
+ end
14
+
15
+ def self.regex_height
16
+ @regex_height ||= /(?: \(height "(?'height'\d*)"\))?/
12
17
  end
13
18
 
14
19
  def self.attributes
@@ -40,7 +45,11 @@ module TF2LineParser
40
45
  @player = Player.new(player_name, player_uid, player_steamid, player_team)
41
46
  @target = Player.new(target_name, target_uid, target_steamid, target_team) if target_name
42
47
  @value = value.to_i
48
+ @damage = @value
43
49
  @weapon = weapon
50
+ @healing = nil
51
+ @crit = nil
52
+ @headshot = nil
44
53
  @airshot = (airshot.to_i == 1)
45
54
  end
46
55
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TF2LineParser
4
+ module Events
5
+ class AirshotHeal < Heal
6
+ attr_reader :airshot
7
+
8
+ def self.regex
9
+ @regex ||= /#{regex_time} #{regex_player} triggered "healed" against #{regex_target} \(healing "(?'value'\d+)"\)#{regex_airshot}#{regex_height}/.freeze
10
+ end
11
+
12
+ def self.regex_airshot
13
+ @regex_airshot ||= / \(airshot "(?'airshot'\w*)"\)/
14
+ end
15
+
16
+ def self.regex_height
17
+ @regex_height ||= /(?: \(height "(?'height'\d*)"\))?/
18
+ end
19
+
20
+ def self.attributes
21
+ @attributes ||= %i[time player_section target_section value airshot]
22
+ end
23
+
24
+ def initialize(time, player_name, player_uid, player_steam_id, player_team, target_name, target_uid, target_steam_id, target_team, value, airshot)
25
+ @time = parse_time(time)
26
+ @player = Player.new(player_name, player_uid, player_steam_id, player_team)
27
+ @target = Player.new(target_name, target_uid, target_steam_id, target_team)
28
+ @value = value.to_i
29
+ @healing = @value
30
+ @airshot = (airshot.to_i == 1)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -4,7 +4,7 @@ module TF2LineParser
4
4
  attr_reader :time, :player, :object
5
5
 
6
6
  def self.regex
7
- @regex ||= /#{regex_time} #{regex_player} triggered "builtobject" #{regex_object_info}#{regex_position}?/.freeze
7
+ @regex ||= /#{regex_time} #{regex_player} triggered "(?:player_)?builtobject" #{regex_object_info}#{regex_position}?/.freeze
8
8
  end
9
9
 
10
10
  def self.regex_object_info
@@ -3,7 +3,7 @@
3
3
  module TF2LineParser
4
4
  module Events
5
5
  class Damage < Event
6
- attr_reader :crit, :headshot, :healing
6
+ attr_reader :damage, :crit, :headshot, :healing
7
7
 
8
8
  def self.regex
9
9
  @regex ||= /#{regex_time} #{regex_player} triggered "damage" #{regex_damage_against}\(damage "(?'value'\d+)"\)#{regex_realdamage}#{regex_weapon}#{regex_healing}#{regex_crit}#{regex_headshot}/.freeze
@@ -64,6 +64,7 @@ module TF2LineParser
64
64
  @player = Player.new(player_name, player_uid, player_steamid, player_team)
65
65
  @target = Player.new(target_name, target_uid, target_steamid, target_team) if target_name
66
66
  @value = value.to_i
67
+ @damage = @value
67
68
  @weapon = weapon
68
69
  @healing = healing.to_i if healing
69
70
  @crit = crit
@@ -11,7 +11,7 @@ module TF2LineParser
11
11
  end
12
12
 
13
13
  def self.regex_time
14
- @regex_time ||= 'L (?\'time\'.*):'
14
+ @regex_time ||= 'L (?\'time\'\\d{2}/\\d{2}/\\d{4} - \\d{2}:\\d{2}:\\d{2}):'
15
15
  end
16
16
 
17
17
  def self.regex_player
@@ -36,7 +36,7 @@ module TF2LineParser
36
36
 
37
37
  def self.types
38
38
  # ordered by how common the messages are
39
- @types ||= [HeadshotDamage, Airshot, Damage, Heal, PickupItem, Assist, Kill, CaptureBlock, PointCapture, ChargeDeployed,
39
+ @types ||= [ShotFired, ShotHit, PositionReport, HeadshotDamage, Airshot, Damage, AirshotHeal, Heal, PickupItem, Assist, Kill, CaptureBlock, PointCapture, ChargeDeployed,
40
40
  MedicDeath, MedicDeathEx, EmptyUber, ChargeReady, ChargeEnded, FirstHealAfterSpawn, LostUberAdvantage, BuiltObject, PlayerExtinguished, JoinedTeam, EnteredGame, RoleChange, Spawn, Suicide, KilledObject, Say, TeamSay, Domination, Revenge, RoundWin, CurrentScore,
41
41
  RoundLength, RoundStart, RoundSetupBegin, RoundSetupEnd, MiniRoundStart, MiniRoundSelected, IntermissionWinLimit, Connect, Disconnect, RconCommand, ConsoleSay, MatchEnd, FinalScore,
42
42
  RoundStalemate, Unknown].freeze
@@ -12,7 +12,7 @@ module TF2LineParser
12
12
  end
13
13
 
14
14
  def self.attributes
15
- @attributes ||= %i[time player_section target_section value weapon]
15
+ @attributes ||= %i[time player_section target_section value weapon healing crit headshot]
16
16
  end
17
17
 
18
18
  def self.regex_results(matched_line)
@@ -21,6 +21,9 @@ module TF2LineParser
21
21
  target_section = matched_line['target_section']
22
22
  value = matched_line['value']
23
23
  weapon = matched_line['weapon']
24
+ healing = matched_line['healing']
25
+ crit = matched_line['crit']
26
+ headshot = matched_line['headshot']
24
27
 
25
28
  # Parse player section
26
29
  player_name, player_uid, player_steamid, player_team = parse_player_section(player_section)
@@ -31,15 +34,19 @@ module TF2LineParser
31
34
  target_name, target_uid, target_steamid, target_team = parse_target_section(target_section)
32
35
  end
33
36
 
34
- [time, player_name, player_uid, player_steamid, player_team, target_name, target_uid, target_steamid, target_team, value, weapon]
37
+ [time, player_name, player_uid, player_steamid, player_team, target_name, target_uid, target_steamid, target_team, value, weapon, healing, crit, headshot]
35
38
  end
36
39
 
37
- def initialize(time, player_name, player_uid, player_steamid, player_team, target_name, target_uid, target_steamid, target_team, value, weapon)
40
+ def initialize(time, player_name, player_uid, player_steamid, player_team, target_name, target_uid, target_steamid, target_team, value, weapon, healing, crit, headshot)
38
41
  @time = parse_time(time)
39
42
  @player = Player.new(player_name, player_uid, player_steamid, player_team)
40
43
  @target = Player.new(target_name, target_uid, target_steamid, target_team) if target_name
41
44
  @value = value.to_i
45
+ @damage = @value
42
46
  @weapon = weapon
47
+ @healing = healing.to_i if healing
48
+ @crit = crit
49
+ @headshot = true # Always true for HeadshotDamage events
43
50
  end
44
51
  end
45
52
  end
@@ -16,6 +16,10 @@ module TF2LineParser
16
16
  def self.attributes
17
17
  @attributes ||= %i[time player_section team_name]
18
18
  end
19
+
20
+ def team
21
+ @team_name
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TF2LineParser
4
+ module Events
5
+ class PositionReport < Event
6
+ attr_reader :time, :player, :position
7
+
8
+ def self.regex
9
+ @regex ||= /#{regex_time} #{regex_player} position_report \(position "(?'position'[^"]+)"\)/.freeze
10
+ end
11
+
12
+ def self.attributes
13
+ @attributes ||= %i[time player_section position]
14
+ end
15
+
16
+ def initialize(time, name, uid, steam_id, team, position)
17
+ @time = parse_time(time)
18
+ @player = Player.new(name, uid, steam_id, team)
19
+ @position = position
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TF2LineParser
4
+ module Events
5
+ class ShotFired < PlayerActionEvent
6
+ def self.action_text
7
+ @action_text ||= 'triggered "shot_fired"'
8
+ end
9
+
10
+ def self.regex_action
11
+ @regex_action ||= '\(weapon "(?\'weapon\'[^\"]+)"\)'
12
+ end
13
+
14
+ def self.item
15
+ :weapon
16
+ end
17
+
18
+ def self.attributes
19
+ @attributes ||= %i[time player_section weapon]
20
+ end
21
+
22
+ def initialize(time, player_name, player_uid, player_steam_id, player_team, weapon)
23
+ @time = parse_time(time)
24
+ @player = Player.new(player_name, player_uid, player_steam_id, player_team)
25
+ @weapon = weapon
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TF2LineParser
4
+ module Events
5
+ class ShotHit < PlayerActionEvent
6
+ def self.action_text
7
+ @action_text ||= 'triggered "shot_hit"'
8
+ end
9
+
10
+ def self.regex_action
11
+ @regex_action ||= '\(weapon "(?\'weapon\'[^\"]+)"\)'
12
+ end
13
+
14
+ def self.item
15
+ :weapon
16
+ end
17
+
18
+ def self.attributes
19
+ @attributes ||= %i[time player_section weapon]
20
+ end
21
+
22
+ def initialize(time, player_name, player_uid, player_steam_id, player_team, weapon)
23
+ @time = parse_time(time)
24
+ @player = Player.new(player_name, player_uid, player_steam_id, player_team)
25
+ @weapon = weapon
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,31 +1,138 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'time'
4
- require 'active_support/multibyte/chars'
5
- require 'active_support/multibyte/unicode'
6
4
 
7
5
  module TF2LineParser
8
6
  class Line
9
7
  attr_accessor :line
10
8
 
9
+ # Keyword to event types mapping (order matters for subtypes!)
10
+ KEYWORD_DISPATCH = {
11
+ 'shot_fired' => [Events::ShotFired],
12
+ 'shot_hit' => [Events::ShotHit],
13
+ 'damage' => [Events::HeadshotDamage, Events::Airshot, Events::Damage],
14
+ 'healed' => [Events::AirshotHeal, Events::Heal],
15
+ 'picked up' => [Events::PickupItem],
16
+ 'kill assist' => [Events::Assist],
17
+ 'killed' => [Events::Kill],
18
+ 'killedobject' => [Events::KilledObject],
19
+ 'captureblocked' => [Events::CaptureBlock],
20
+ 'pointcaptured' => [Events::PointCapture],
21
+ 'chargedeployed' => [Events::ChargeDeployed],
22
+ 'medic_death_ex' => [Events::MedicDeathEx],
23
+ 'medic_death' => [Events::MedicDeath],
24
+ 'empty_uber' => [Events::EmptyUber],
25
+ 'chargeready' => [Events::ChargeReady],
26
+ 'chargeended' => [Events::ChargeEnded],
27
+ 'first_heal_after_spawn' => [Events::FirstHealAfterSpawn],
28
+ 'lost_uber_advantage' => [Events::LostUberAdvantage],
29
+ 'builtobject' => [Events::BuiltObject],
30
+ 'player_builtobject' => [Events::BuiltObject],
31
+ 'player_extinguished' => [Events::PlayerExtinguished],
32
+ 'joined team' => [Events::JoinedTeam],
33
+ 'entered the game' => [Events::EnteredGame],
34
+ 'changed role' => [Events::RoleChange],
35
+ 'spawned as' => [Events::Spawn],
36
+ 'committed suicide' => [Events::Suicide],
37
+ 'domination' => [Events::Domination],
38
+ 'revenge' => [Events::Revenge],
39
+ 'Round_Win' => [Events::RoundWin],
40
+ 'Round_Length' => [Events::RoundLength],
41
+ 'Round_Start' => [Events::RoundStart],
42
+ 'Round_Setup_Begin' => [Events::RoundSetupBegin],
43
+ 'Round_Setup_End' => [Events::RoundSetupEnd],
44
+ 'Mini_Round_Start' => [Events::MiniRoundStart],
45
+ 'Mini_Round_Selected' => [Events::MiniRoundSelected],
46
+ 'Intermission_Win_Limit' => [Events::IntermissionWinLimit],
47
+ 'Round_Stalemate' => [Events::RoundStalemate],
48
+ 'Game_Over' => [Events::MatchEnd],
49
+ 'connected, address' => [Events::Connect],
50
+ 'disconnected' => [Events::Disconnect],
51
+ 'say_team' => [Events::TeamSay],
52
+ 'say "' => [Events::Say],
53
+ 'position_report' => [Events::PositionReport],
54
+ }.freeze
55
+
56
+ # Fallback types when no keyword matches
57
+ FALLBACK_TYPES = [
58
+ Events::CurrentScore, Events::FinalScore, Events::RconCommand,
59
+ Events::ConsoleSay, Events::Unknown
60
+ ].freeze
61
+
11
62
  def initialize(line)
12
63
  @line = line
13
64
  end
14
65
 
15
66
  def parse
16
- Events::Event.types.each do |type|
17
- begin
18
- match = line.match(type.regex)
19
- rescue ArgumentError
20
- tidied_line = ActiveSupport::Multibyte::Chars.new(line).tidy_bytes
21
- match = tidied_line.match(type.regex)
67
+ self.class.parse(@line)
68
+ end
69
+
70
+ # Class method to parse without object allocation
71
+ def self.parse(line)
72
+ types = find_candidate_types(line)
73
+ try_parse_types(line, types)
74
+ end
75
+
76
+ class << self
77
+ private
78
+
79
+ def find_candidate_types(line)
80
+ # Fast path: check for "triggered" which covers most events
81
+ if line.include?('triggered "')
82
+ start_idx = line.index('triggered "')
83
+ if start_idx
84
+ end_idx = line.index('"', start_idx + 11)
85
+ if end_idx
86
+ keyword = line[start_idx + 11...end_idx]
87
+ types = KEYWORD_DISPATCH[keyword]
88
+ return types if types
89
+ end
90
+ end
91
+ end
92
+
93
+ # Check other common patterns with simple string operations
94
+ if line.include?('" killed "')
95
+ [Events::Kill]
96
+ elsif line.include?('position_report')
97
+ [Events::PositionReport]
98
+ elsif line.include?('" say_team "')
99
+ [Events::TeamSay]
100
+ elsif line.include?('" say "')
101
+ [Events::Say]
102
+ elsif line.include?('picked up item')
103
+ [Events::PickupItem]
104
+ elsif line.include?('joined team "')
105
+ [Events::JoinedTeam]
106
+ elsif line.include?('entered the game')
107
+ [Events::EnteredGame]
108
+ elsif line.include?('changed role to')
109
+ [Events::RoleChange]
110
+ elsif line.include?('spawned as "')
111
+ [Events::Spawn]
112
+ elsif line.include?('committed suicide')
113
+ [Events::Suicide]
114
+ elsif line.include?('connected, address')
115
+ [Events::Connect]
116
+ elsif line.include?('disconnected (reason')
117
+ [Events::Disconnect]
118
+ else
119
+ FALLBACK_TYPES
22
120
  end
23
- if match
24
- @match ||= type.new(*type.regex_results(match))
25
- break
121
+ end
122
+
123
+ def try_parse_types(line, types)
124
+ types.each do |type|
125
+ begin
126
+ match = line.match(type.regex)
127
+ rescue ArgumentError
128
+ # Handle invalid byte sequences by forcing UTF-8 encoding
129
+ tidied_line = line.encode('UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: '')
130
+ match = tidied_line.match(type.regex)
131
+ end
132
+ return type.new(*type.regex_results(match)) if match
26
133
  end
134
+ nil
27
135
  end
28
- @match
29
136
  end
30
137
  end
31
138
  end
@@ -11,5 +11,10 @@ module TF2LineParser
11
11
  def parse
12
12
  line.parse
13
13
  end
14
+
15
+ # Class method to parse without object allocation overhead
16
+ def self.parse(line)
17
+ Line.parse(line.to_s)
18
+ end
14
19
  end
15
20
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TF2LineParser
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -13,6 +13,7 @@ require 'tf2_line_parser/events/connect'
13
13
  require 'tf2_line_parser/events/score'
14
14
  require 'tf2_line_parser/events/role_change'
15
15
  require 'tf2_line_parser/events/damage'
16
+ require 'tf2_line_parser/events/heal'
16
17
  Dir["#{File.dirname(__FILE__)}/tf2_line_parser/events/*.rb"].sort.each { |file| require file }
17
18
  require 'tf2_line_parser/line'
18
19
 
@@ -1 +1,7 @@
1
1
  L 05/15/2014 - 18:22:27: "super sanic<5><STEAM_0:0:64268661><Red>" triggered "damage" against "kerrieebOb<11><STEAM_0:0:11431287><Blue>" (damage "47") (weapon "tf_projectile_rocket") (airshot "1")
2
+ L 12/18/2025 - 22:06:01: "tantwo<17><[U:1:191375689]><Blue>" triggered "damage" against "ryftomania(big/guy)<8><[U:1:468872526]><Red>" (damage "105") (realdamage "88") (weapon "quake_rl") (airshot "1") (height "353")
3
+ L 12/18/2025 - 22:07:05: "merkules<11><[U:1:86331856]><Blue>" triggered "healed" against "fy<14><[U:1:442791013]><Blue>" (healing "82") (airshot "1") (height "261")
4
+ L 12/18/2025 - 22:10:55: "fy<14><[U:1:442791013]><Blue>" triggered "damage" against "keicam<9><[U:1:163231154]><Red>" (damage "50") (realdamage "26") (weapon "tf_projectile_rocket") (airshot "1") (height "292")
5
+ L 12/18/2025 - 22:12:57: "tantwo<17><[U:1:191375689]><Blue>" triggered "damage" against "keicam<9><[U:1:163231154]><Red>" (damage "105") (weapon "quake_rl") (airshot "1") (height "318")
6
+ L 12/18/2025 - 22:12:58: "tantwo<17><[U:1:191375689]><Blue>" triggered "damage" against "keicam<9><[U:1:163231154]><Red>" (damage "99") (realdamage "9") (weapon "quake_rl") (airshot "1") (height "427")
7
+ L 12/18/2025 - 22:15:21: "fy<14><[U:1:442791013]><Blue>" triggered "damage" against "gibusmain<19><[U:1:258908204]><Red>" (damage "109") (realdamage "66") (weapon "tf_projectile_rocket") (airshot "1") (height "228")
@@ -96,11 +96,71 @@ module TF2LineParser
96
96
  parse(line).inspect
97
97
  end
98
98
 
99
+ it 'recognizes airshots with realdamage and height' do
100
+ line = airshot_log_lines[1]
101
+ result = parse(line)
102
+ expect(result).to be_a(Events::Airshot)
103
+ expect(result.player.name).to eq('tantwo')
104
+ expect(result.player.steam_id).to eq('[U:1:191375689]')
105
+ expect(result.player.team).to eq('Blue')
106
+ expect(result.target.name).to eq('ryftomania(big/guy)')
107
+ expect(result.target.steam_id).to eq('[U:1:468872526]')
108
+ expect(result.target.team).to eq('Red')
109
+ expect(result.damage).to eq(105)
110
+ expect(result.weapon).to eq('quake_rl')
111
+ expect(result.airshot).to eq(true)
112
+ end
113
+
114
+ it 'recognizes airshots without realdamage' do
115
+ line = airshot_log_lines[4]
116
+ result = parse(line)
117
+ expect(result).to be_a(Events::Airshot)
118
+ expect(result.player.name).to eq('tantwo')
119
+ expect(result.damage).to eq(105)
120
+ expect(result.weapon).to eq('quake_rl')
121
+ expect(result.airshot).to eq(true)
122
+ end
123
+
124
+ it 'recognizes airshot heals (Crusader Crossbow mid-air)' do
125
+ line = airshot_log_lines[2]
126
+ result = parse(line)
127
+ expect(result).to be_a(Events::AirshotHeal)
128
+ expect(result.player.name).to eq('merkules')
129
+ expect(result.player.steam_id).to eq('[U:1:86331856]')
130
+ expect(result.player.team).to eq('Blue')
131
+ expect(result.target.name).to eq('fy')
132
+ expect(result.target.steam_id).to eq('[U:1:442791013]')
133
+ expect(result.target.team).to eq('Blue')
134
+ expect(result.healing).to eq(82)
135
+ expect(result.airshot).to eq(true)
136
+ end
137
+
138
+ it 'parses all damage airshot log lines correctly' do
139
+ damage_airshot_lines = airshot_log_lines.reject { |l| l.include?('healed') }
140
+ damage_airshot_lines.each do |line|
141
+ result = parse(line)
142
+ expect(result).to be_a(Events::Airshot), "Expected Airshot for: #{line}"
143
+ expect(result.airshot).to eq(true)
144
+ end
145
+ end
146
+
147
+ it 'parses all heal airshot log lines correctly' do
148
+ heal_airshot_lines = airshot_log_lines.select { |l| l.include?('healed') }
149
+ heal_airshot_lines.each do |line|
150
+ result = parse(line)
151
+ expect(result).to be_a(Events::AirshotHeal), "Expected AirshotHeal for: #{line}"
152
+ expect(result.airshot).to eq(true)
153
+ end
154
+ end
155
+
99
156
  it 'recognizes sniper headshot damage' do
100
157
  line = detailed_log_lines[3645]
101
158
  weapon = 'sniperrifle'
159
+ healing = nil
160
+ crit = nil
161
+ headshot = '1'
102
162
  expect(Events::HeadshotDamage).to receive(:new).with(anything, anything, anything, anything, anything, anything,
103
- anything, anything, anything, anything, weapon)
163
+ anything, anything, anything, anything, weapon, healing, crit, headshot)
104
164
  parse(line).inspect
105
165
  end
106
166
 
@@ -585,6 +645,39 @@ module TF2LineParser
585
645
  parse(line)
586
646
  end
587
647
 
648
+ it 'recognizes shot_fired' do
649
+ line = 'L 12/30/2025 - 18:34:19: "Jib<34><[U:1:367944796]><Blue>" triggered "shot_fired" (weapon "tf_projectile_rocket")'
650
+ result = parse(line)
651
+ expect(result).to be_a(Events::ShotFired)
652
+ expect(result.player.name).to eq('Jib')
653
+ expect(result.player.uid).to eq('34')
654
+ expect(result.player.steam_id).to eq('[U:1:367944796]')
655
+ expect(result.player.team).to eq('Blue')
656
+ expect(result.weapon).to eq('tf_projectile_rocket')
657
+ end
658
+
659
+ it 'recognizes shot_hit' do
660
+ line = 'L 12/30/2025 - 18:34:19: "Jib<34><[U:1:367944796]><Blue>" triggered "shot_hit" (weapon "tf_projectile_rocket")'
661
+ result = parse(line)
662
+ expect(result).to be_a(Events::ShotHit)
663
+ expect(result.player.name).to eq('Jib')
664
+ expect(result.player.uid).to eq('34')
665
+ expect(result.player.steam_id).to eq('[U:1:367944796]')
666
+ expect(result.player.team).to eq('Blue')
667
+ expect(result.weapon).to eq('tf_projectile_rocket')
668
+ end
669
+
670
+ it 'recognizes position_report' do
671
+ line = 'L 12/30/2025 - 18:34:19: "Jib<34><[U:1:367944796]><Blue>" position_report (position "306 -1464 -237")'
672
+ result = parse(line)
673
+ expect(result).to be_a(Events::PositionReport)
674
+ expect(result.player.name).to eq('Jib')
675
+ expect(result.player.uid).to eq('34')
676
+ expect(result.player.steam_id).to eq('[U:1:367944796]')
677
+ expect(result.player.team).to eq('Blue')
678
+ expect(result.position).to eq('306 -1464 -237')
679
+ end
680
+
588
681
  it 'recognizes suicides' do
589
682
  line = log_lines[76]
590
683
  name = '.schocky'
@@ -15,7 +15,6 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ['lib']
16
16
  gem.homepage = 'http://github.com/Arie/tf2_line_parser'
17
17
 
18
- gem.add_dependency 'activesupport'
19
18
  gem.add_development_dependency 'coveralls_reborn'
20
19
  gem.add_development_dependency 'rspec'
21
20
  gem.add_development_dependency 'simplecov'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tf2_line_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arie
@@ -10,20 +10,6 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2025-12-31 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: coveralls_reborn
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -95,6 +81,7 @@ files:
95
81
  - README.md
96
82
  - lib/tf2_line_parser.rb
97
83
  - lib/tf2_line_parser/events/airshot.rb
84
+ - lib/tf2_line_parser/events/airshot_heal.rb
98
85
  - lib/tf2_line_parser/events/assist.rb
99
86
  - lib/tf2_line_parser/events/built_object.rb
100
87
  - lib/tf2_line_parser/events/capture_block.rb
@@ -129,6 +116,7 @@ files:
129
116
  - lib/tf2_line_parser/events/player_action_event.rb
130
117
  - lib/tf2_line_parser/events/player_extinguished.rb
131
118
  - lib/tf2_line_parser/events/point_capture.rb
119
+ - lib/tf2_line_parser/events/position_report.rb
132
120
  - lib/tf2_line_parser/events/pvp_event.rb
133
121
  - lib/tf2_line_parser/events/rcon_command.rb
134
122
  - lib/tf2_line_parser/events/revenge.rb
@@ -142,6 +130,8 @@ files:
142
130
  - lib/tf2_line_parser/events/round_start.rb
143
131
  - lib/tf2_line_parser/events/round_win.rb
144
132
  - lib/tf2_line_parser/events/score.rb
133
+ - lib/tf2_line_parser/events/shot_fired.rb
134
+ - lib/tf2_line_parser/events/shot_hit.rb
145
135
  - lib/tf2_line_parser/events/spawn.rb
146
136
  - lib/tf2_line_parser/events/suicide.rb
147
137
  - lib/tf2_line_parser/events/unknown.rb