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 +4 -4
- data/Gemfile.lock +20 -39
- data/lib/tf2_line_parser/events/airshot.rb +11 -2
- data/lib/tf2_line_parser/events/airshot_heal.rb +34 -0
- data/lib/tf2_line_parser/events/built_object.rb +1 -1
- data/lib/tf2_line_parser/events/damage.rb +2 -1
- data/lib/tf2_line_parser/events/event.rb +2 -2
- data/lib/tf2_line_parser/events/headshot_damage.rb +10 -3
- data/lib/tf2_line_parser/events/joined_team.rb +4 -0
- data/lib/tf2_line_parser/events/position_report.rb +23 -0
- data/lib/tf2_line_parser/events/shot_fired.rb +29 -0
- data/lib/tf2_line_parser/events/shot_hit.rb +29 -0
- data/lib/tf2_line_parser/line.rb +119 -12
- data/lib/tf2_line_parser/parser.rb +5 -0
- data/lib/tf2_line_parser/version.rb +1 -1
- data/lib/tf2_line_parser.rb +1 -0
- data/spec/fixtures/logs/airshot.log +6 -0
- data/spec/lib/tf2_line_parser/parser_spec.rb +94 -1
- data/tf2_line_parser.gemspec +0 -1
- metadata +5 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5c1d38c81d58d2ecb10aa612b2602de4abf47b540ec3caf003273a87cdcdb5ba
|
|
4
|
+
data.tar.gz: 7993c5640a38063e5082780475881b1faca2898a5f6555ac4040ff612f341f2c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
5
|
-
activesupport
|
|
4
|
+
tf2_line_parser (0.5.0)
|
|
6
5
|
|
|
7
6
|
GEM
|
|
8
7
|
remote: https://rubygems.org/
|
|
9
8
|
specs:
|
|
10
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
63
|
-
tins (~> 1
|
|
64
|
-
thor (1.
|
|
65
|
-
tins (1.
|
|
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
|
-
|
|
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 ||=
|
|
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
|
|
@@ -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
|
data/lib/tf2_line_parser/line.rb
CHANGED
|
@@ -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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
data/lib/tf2_line_parser.rb
CHANGED
|
@@ -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'
|
data/tf2_line_parser.gemspec
CHANGED
|
@@ -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
|
+
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
|