australium 1.0.1
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 +7 -0
- data/.gitignore +19 -0
- data/.yardopts +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +52 -0
- data/Rakefile +1 -0
- data/australium.gemspec +24 -0
- data/lib/australium.rb +29 -0
- data/lib/australium/event.rb +47 -0
- data/lib/australium/events/map_load.rb +10 -0
- data/lib/australium/events/map_start.rb +13 -0
- data/lib/australium/events/player_connect.rb +17 -0
- data/lib/australium/events/player_disconnect.rb +15 -0
- data/lib/australium/events/player_enter_game.rb +16 -0
- data/lib/australium/events/player_join_team.rb +20 -0
- data/lib/australium/events/player_kill.rb +14 -0
- data/lib/australium/events/player_name_change.rb +17 -0
- data/lib/australium/events/player_role_change.rb +20 -0
- data/lib/australium/events/player_say.rb +21 -0
- data/lib/australium/events/player_suicide.rb +12 -0
- data/lib/australium/events/trigger.rb +17 -0
- data/lib/australium/game.rb +61 -0
- data/lib/australium/game_state.rb +20 -0
- data/lib/australium/parser.rb +63 -0
- data/lib/australium/player.rb +79 -0
- data/lib/australium/ref/match_data_to_hash.rb +11 -0
- data/lib/australium/ref/open_struct_deep_clone.rb +15 -0
- data/lib/australium/version.rb +3 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6fd3f83b9c9d4e08cb36d103b67037fd12f8d8c3
|
4
|
+
data.tar.gz: 8f942fa3c40bc628ef4582d2ec04a9c64ee148db
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 25969f141083f537faba752bf9916596bce276c87d99557012bf0de6ba91561ac809de48760bc9316158175d2094e805c6877d2a095b3a279cfe11c56a11081e
|
7
|
+
data.tar.gz: ab1a4982f7a2722feea7ab262b466130f1556cb663c8883295ce32df9522bb20547c90bcc8be9b08a26a747d535d71f2f60cabfe2193da8c8fa12227a17ac58b
|
data/.gitignore
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 awkisopen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
[](https://codeclimate.com/github/awkisopen/australium)
|
2
|
+
# Australium
|
3
|
+
|
4
|
+
<img src="http://i.imgur.com/IpOgnjO.png" align="right" />
|
5
|
+
Australium is a powerful TF2 game log parser. It allows access to all game events and player data in a hierarchical,
|
6
|
+
rubyified manner. The timestamp and state of the game are stored within every event for easy access.
|
7
|
+
|
8
|
+
<br clear="all" />
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'australium'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install australium
|
23
|
+
|
24
|
+
## Example Usage
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'australium'
|
28
|
+
|
29
|
+
# Load game data from logfile
|
30
|
+
games = Australium::parse_file('/tmp/tf2game.log')
|
31
|
+
|
32
|
+
# Logfiles may contain multiple games, but chances are good your logfile will only have one.
|
33
|
+
game = games.first
|
34
|
+
|
35
|
+
# Print the name of each player and the team they were on.
|
36
|
+
game.players.each do |player|
|
37
|
+
puts "#{p.nick} - #{p.team.empty? ? 'None' : p.team}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Count the number of backstabs that took place during the game.
|
41
|
+
puts 'Backstabs: ' + game.kills.select { |e| e.customkill == 'backstab' }.count.to_s
|
42
|
+
```
|
43
|
+
|
44
|
+
Check [the documentation](http://rubydoc.info/github/awkisopen/australium/master/frames) for more information on how to access game events.
|
45
|
+
|
46
|
+
## Contributing
|
47
|
+
|
48
|
+
1. Fork it
|
49
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
50
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
51
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
52
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/australium.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'australium/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "australium"
|
8
|
+
spec.version = Australium::VERSION
|
9
|
+
spec.authors = %w[awk]
|
10
|
+
spec.email = %w[awkisopen@shutupandwrite.net]
|
11
|
+
spec.description = %q[TF2 log parser]
|
12
|
+
spec.summary = %q[TF2 log parser]
|
13
|
+
spec.homepage = "http://training.tf"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
spec.required_ruby_version = "~> 2.1"
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
end
|
data/lib/australium.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
path = File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
Dir["#{path}/australium/ref/*.rb"].each { |f| require f }
|
7
|
+
|
8
|
+
require "#{path}/australium/event.rb"
|
9
|
+
Dir["#{path}/australium/events/*.rb"].each { |f| require f }
|
10
|
+
|
11
|
+
Dir["#{path}/australium/*.rb"].each { |f| require f }
|
12
|
+
|
13
|
+
module Australium
|
14
|
+
|
15
|
+
# Parses a file located at the given path and returns an array of {Game}s.
|
16
|
+
# @param [String] logfile the location of the logfile.
|
17
|
+
# @return [Array<Game>] the parsed game data.
|
18
|
+
def self.parse_file(logfile)
|
19
|
+
log = File.read(logfile)
|
20
|
+
Parser::parse(log.split("\n"))
|
21
|
+
end
|
22
|
+
|
23
|
+
# Future expansion: #parse should be able to determine the kind of input given and parse accordingly - i.e.
|
24
|
+
# a past logfile vs a logfile currently being written to vs a network stream.
|
25
|
+
class << self
|
26
|
+
alias_method :parse, :parse_file
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Australium
|
2
|
+
|
3
|
+
# Represents a generic Event that occurs during a Game.
|
4
|
+
class Event < OpenStruct
|
5
|
+
using MatchDataToHash
|
6
|
+
|
7
|
+
# @!attribute raw
|
8
|
+
# @return [String] the raw log message.
|
9
|
+
|
10
|
+
TIMESTAMP_REGEX = %r(L [0-9]{2}/[0-9]{2}/[0-9]{4} - [0-9]{2}:[0-9]{2}:[0-9]{2})
|
11
|
+
PROPERTY_REGEX = /\(([^ ]+) "([^"]+)"\)/
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :event_classes
|
15
|
+
def inherited(by)
|
16
|
+
@event_classes ||= []
|
17
|
+
@event_classes << by
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(data)
|
22
|
+
super(data)
|
23
|
+
|
24
|
+
# Replace anything matching a Player string with a Player object.
|
25
|
+
self.each_pair do |key, value|
|
26
|
+
if value =~ Player::LOG_REGEX && key != :raw
|
27
|
+
player_data = Player::LOG_REGEX.match(value).to_h
|
28
|
+
self[key] = Player.new(player_data)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# If we're being stateful, make sure referenced Players are included in the GameState
|
33
|
+
unless self.state.nil?
|
34
|
+
self.to_h.select { |_, v| v.is_a?(Player) }.each_pair do |key, value|
|
35
|
+
if self.state.players.include?(value)
|
36
|
+
self[key] = self.state.players.find { |p| p == value }
|
37
|
+
else
|
38
|
+
self.state.players << value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s ; raw end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Australium
|
2
|
+
class MapStart < Event
|
3
|
+
|
4
|
+
LOG_REGEX = /Started map "(?<map_name>.+)" \(CRC "(?<crc>.+)"\)/
|
5
|
+
|
6
|
+
# @!attribute map_name
|
7
|
+
# @return [String] the name of the map that was started.
|
8
|
+
|
9
|
+
# @!attribute crc
|
10
|
+
# @return [String] CRC of the map.
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Australium
|
2
|
+
class PlayerConnect < Event
|
3
|
+
|
4
|
+
LOG_REGEX = /"(?<player>.+)" connected, address "(?<address>.+)"/
|
5
|
+
|
6
|
+
# @!attribute player
|
7
|
+
# @return [Player] the {Player} who connected to the server.
|
8
|
+
# @!attribute address
|
9
|
+
# @return [String] the IP address of the connecting player.
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
super(data)
|
13
|
+
player.address = address
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Australium
|
2
|
+
class PlayerDisconnect < Event
|
3
|
+
|
4
|
+
LOG_REGEX = /"(?<player>.+)" disconnected"/
|
5
|
+
|
6
|
+
# @!attribute player
|
7
|
+
# @return [Player] the {Player} who disconnected from the server.
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
super(data)
|
11
|
+
state.players.delete(player) unless state.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Australium
|
2
|
+
class PlayerEnterGame < Event
|
3
|
+
|
4
|
+
LOG_REGEX = /"(?<player>.+)" entered the game/
|
5
|
+
|
6
|
+
# @!attribute player
|
7
|
+
# @return [Player] the {Player} who entered the game. This event always takes place after a corresponding
|
8
|
+
# {PlayerConnect} event.
|
9
|
+
|
10
|
+
def initialize(data)
|
11
|
+
super(data)
|
12
|
+
state.players.find { |p| p == player }[:in_game?] = true
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Australium
|
2
|
+
class PlayerJoinTeam < Event
|
3
|
+
|
4
|
+
LOG_REGEX = /"(?<player>.+)" joined team "(?<team>.+)"/
|
5
|
+
|
6
|
+
# @!attribute player
|
7
|
+
# @return [Player] the {Player} who joined the team.
|
8
|
+
# @!attribute team
|
9
|
+
# @return [String] the name of the team the player joined.
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
super(data)
|
13
|
+
unless state.nil?
|
14
|
+
player = state.players.find { |p| p.nick == self.player.nick }
|
15
|
+
player.team = team
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Australium
|
2
|
+
class PlayerKill < Event
|
3
|
+
|
4
|
+
LOG_REGEX = /"(?<attacker>.+)" killed "(?<victim>.+)" with "(?<weapon>[^"]+)"/
|
5
|
+
|
6
|
+
# @!attribute attacker
|
7
|
+
# @return [Player] the {player} responsible for the kill.
|
8
|
+
# @!attribute victim
|
9
|
+
# @return [Player] the {player} who was killed.
|
10
|
+
# @!attribute weapon
|
11
|
+
# @return [String] the name of the weapon used to kill (can be World).
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Australium
|
2
|
+
class PlayerNameChange < Event
|
3
|
+
|
4
|
+
LOG_REGEX = /"(?<player>.+)" changed name to "(?<new_name>.+)"/
|
5
|
+
|
6
|
+
# @!attribute player
|
7
|
+
# @return [Player] the {Player} whose name changed.
|
8
|
+
# @!attribute new_name
|
9
|
+
# @return [String] the name the player changed to.
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
super(data)
|
13
|
+
player.nick = new_name
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Australium
|
2
|
+
class PlayerRoleChange < Event
|
3
|
+
|
4
|
+
LOG_REGEX = /"(?<player>.+)" changed role to "(?<role>.+)"/
|
5
|
+
|
6
|
+
# @!attribute player
|
7
|
+
# @return [Player] the {Player} who changed role.
|
8
|
+
# @!attribute role
|
9
|
+
# @return [String] the name of the role the player changed to.
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
super(data)
|
13
|
+
unless state.nil?
|
14
|
+
player = state.players.find { |p| p.nick == self.player.nick }
|
15
|
+
player.role = role
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Australium
|
2
|
+
class PlayerSay < Event
|
3
|
+
|
4
|
+
LOG_REGEX = /"(?<player>.+)" say(?<team>_team)? "(?<message>.+)"/
|
5
|
+
|
6
|
+
# @!attribute player
|
7
|
+
# @return [Player] the {Player} who spoke in chat.
|
8
|
+
# @!attribute message
|
9
|
+
# @return [String] the message spoken.
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
super(data)
|
13
|
+
self[:team] = team.eql?('_team')
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the message was a team-only message.
|
17
|
+
# @return [Boolean] true if the message was a team message.
|
18
|
+
def team? ; team end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Australium
|
2
|
+
class PlayerSuicide < Event
|
3
|
+
|
4
|
+
LOG_REGEX = /"(?<player>.+)" committed suicide with "(?<weapon>[^"]+)"/
|
5
|
+
|
6
|
+
# @!attribute player
|
7
|
+
# @return [Player] the {Player} who committed suicide.
|
8
|
+
# @!attribute weapon
|
9
|
+
# @return [String] the name of the weapon the player committed suicide with (can be World).
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Australium
|
2
|
+
# A Trigger Event is a very general Event. Triggers occur on point capture, round end, even damage and healing
|
3
|
+
# with plugins installed. Depending on the kind of trigger, it could involve one player, two players, or no
|
4
|
+
# players at all (in the case of 'World' triggering an event, such as a round end).
|
5
|
+
class Trigger < Event
|
6
|
+
|
7
|
+
LOG_REGEX = /: "?(?<initiator>.*(World|>))"? triggered "(?<action>[^"]+)"(?: against "(?<target>.+>)")?/
|
8
|
+
|
9
|
+
# @!attribute initiator
|
10
|
+
# @return [Player, String] the {Player} who triggered the event, or the string 'World'
|
11
|
+
# @!attribute action
|
12
|
+
# @return [String] the name of the triggered action.
|
13
|
+
# @!attribute target
|
14
|
+
# @return [Player, NilClass] the {Player} who received the action, or nil if no {Player} received the action.
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Australium
|
2
|
+
|
3
|
+
# Represents a full game of TF2, comprised of individual {Event}s.
|
4
|
+
class Game
|
5
|
+
|
6
|
+
class << self
|
7
|
+
private
|
8
|
+
# Creates shortcuts for returning events of a certain type.
|
9
|
+
# @param [Symbol] event_name the friendly name of the event category
|
10
|
+
# @param [Class] event_class the class name corresponding to the event
|
11
|
+
# @!macro [attach] event_selector
|
12
|
+
# @!method $1
|
13
|
+
# @return [Array<$2>] Returns all {$2 $2} events.
|
14
|
+
def event_selector(event_name, event_class)
|
15
|
+
define_method(event_name) { @events.select { |e| e.is_a?(event_class) } }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# @!attribute [r] events
|
20
|
+
# @return [Array<Events>] events that took place during the game, in chronological order
|
21
|
+
attr_reader :events
|
22
|
+
|
23
|
+
# @param events [Array<Events>] events that took place during the game
|
24
|
+
def initialize(events)
|
25
|
+
@events = events
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns an array of all {Player Players} who connected to the server during this game.
|
29
|
+
# Caches the return value after the first call.
|
30
|
+
# @return [Array<Player>] players who participated in the game
|
31
|
+
def players
|
32
|
+
@players ||= @events.reverse.each_with_object([]) do |event, all_players|
|
33
|
+
break all_players if event.state.nil? || event.state.players.nil?
|
34
|
+
all_players.concat event.state.players.reject { |p| all_players.include?(p) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the name of the map that was played.
|
39
|
+
# @return [String] map name
|
40
|
+
def map_name
|
41
|
+
@events.select { |e| e.is_a?(MapStart) }.last.map_name
|
42
|
+
end
|
43
|
+
|
44
|
+
# @!group Event filters
|
45
|
+
event_selector :connects, PlayerConnect
|
46
|
+
event_selector :disconnects, PlayerDisconnect
|
47
|
+
event_selector :role_changes, PlayerRoleChange
|
48
|
+
event_selector :team_joins, PlayerJoinTeam
|
49
|
+
event_selector :kills, PlayerKill
|
50
|
+
event_selector :name_changes, PlayerNameChange
|
51
|
+
event_selector :chat_messages, PlayerSay
|
52
|
+
event_selector :suicides, PlayerSuicide
|
53
|
+
event_selector :triggers, Trigger
|
54
|
+
# @!endgroup
|
55
|
+
|
56
|
+
# Hide instance variables from #inspect to prevent clutter on interactive terminals.
|
57
|
+
# @return [String]
|
58
|
+
def inspect ; self.to_s end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Australium
|
2
|
+
|
3
|
+
# Represents the state of the game at a particular time.
|
4
|
+
class GameState < OpenStruct
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
super(*args)
|
8
|
+
|
9
|
+
# @!attribute players
|
10
|
+
# @return [Array<Player>] player data at this state
|
11
|
+
self.players ||= []
|
12
|
+
|
13
|
+
# @!attribute timestamp
|
14
|
+
# @return [DateTime]
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect; "<GameState:#{timestamp}>" end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Australium
|
2
|
+
|
3
|
+
class Parser
|
4
|
+
using MatchDataToHash
|
5
|
+
using OpenStructDeepClone
|
6
|
+
|
7
|
+
# Parses a full TF2 log.
|
8
|
+
# @param [Array<String>] lines the lines to parse
|
9
|
+
# @return [Array<Game>] the game(s) data
|
10
|
+
def self.parse(lines)
|
11
|
+
games = [] ; state = GameState.new ; events = []
|
12
|
+
|
13
|
+
lines.each_with_index do |line, index|
|
14
|
+
event = parse_line(line, state)
|
15
|
+
|
16
|
+
if event.is_a?(MapLoad) || index == lines.count - 1
|
17
|
+
games << Game.new(events) unless events.empty?
|
18
|
+
events = []
|
19
|
+
event = parse_line(line, GameState.new) # Parse a second time with the correct GameState
|
20
|
+
end
|
21
|
+
|
22
|
+
unless event.nil?
|
23
|
+
events << event
|
24
|
+
state = event.state.deep_clone
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
games
|
29
|
+
end
|
30
|
+
|
31
|
+
# Parses a single line of TF2 log in the context of a game (if a {GameState} is passed).
|
32
|
+
# @param [String] line the line to be parsed
|
33
|
+
# @param [GameState] the {GameState} containing the game context.
|
34
|
+
# @return [Event, NilClass] event if an event has been recognized; nil otherwise.
|
35
|
+
def self.parse_line(line, state = nil)
|
36
|
+
Event::event_classes.each do |event_class|
|
37
|
+
if event_class::LOG_REGEX =~ line
|
38
|
+
# Get timestamp data & timestamp GameState if we are being stateful
|
39
|
+
timestamp = DateTime.strptime(Event::TIMESTAMP_REGEX.match(line)[0], 'L %m/%d/%Y - %H:%M:%S')
|
40
|
+
state.timestamp = timestamp unless state.nil?
|
41
|
+
|
42
|
+
# Get the meat of the event data
|
43
|
+
data = event_class::LOG_REGEX.match(line).to_h
|
44
|
+
|
45
|
+
# Get supplemental property data, if any exists
|
46
|
+
property_data = line.scan(Event::PROPERTY_REGEX).map { |x| [x.first.to_sym, x.last] }.to_h
|
47
|
+
data.merge!(property_data)
|
48
|
+
|
49
|
+
# Add other useful data
|
50
|
+
data[:state] = state
|
51
|
+
data[:raw] = line
|
52
|
+
data[:timestamp] = timestamp
|
53
|
+
|
54
|
+
# Construct and return the new Event
|
55
|
+
return event_class.new(data)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Australium
|
2
|
+
|
3
|
+
# Represents a Player playing a Game.
|
4
|
+
class Player < OpenStruct
|
5
|
+
|
6
|
+
LOG_REGEX = /(?<nick>.*)<(?<won_id>.*)><(?<steam_id>.*)><(?<team>.*)>/
|
7
|
+
|
8
|
+
# @!attribute nick
|
9
|
+
# @return [String] the player's in-game nickname.
|
10
|
+
# @!attribute won_id
|
11
|
+
# @return [String] the player's {http://wiki.hlsw.net/index.php/WONID WONID}.
|
12
|
+
# @!attribute steam_id
|
13
|
+
# @return [String] the player's unique Steam ID, or 'BOT' if this player is a bot.
|
14
|
+
# @!attribute team
|
15
|
+
# @return [String] the name of the team the player is on (can be an empty string).
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
super(*args)
|
19
|
+
|
20
|
+
# @!attribute address
|
21
|
+
# @return [String, NilClass] the player's IP address, or nil if not known
|
22
|
+
self[:address] = nil
|
23
|
+
|
24
|
+
# @!attribute in_game?
|
25
|
+
# @return [Boolean] true if the player is in the game (as determined by a triggered {PlayerEnterGame} event).
|
26
|
+
self[:in_game?] = false
|
27
|
+
|
28
|
+
# @!attribute role
|
29
|
+
# @return [String, NilClass] the name of the role the player is playing, or nil if none yet
|
30
|
+
self[:role] = nil
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
# Compares players by steam IDs and bots by nicks.
|
35
|
+
# @param player [Player] the player to compare against
|
36
|
+
# @return [Boolean]
|
37
|
+
def ==(player)
|
38
|
+
if [self, player].all?(&:bot?)
|
39
|
+
self.nick == player.nick
|
40
|
+
else
|
41
|
+
self.steam_id == player.steam_id
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# (see #==)
|
46
|
+
def eql?(player)
|
47
|
+
if [self, player].all?(&:bot?)
|
48
|
+
self.nick.eql?(player.nick)
|
49
|
+
else
|
50
|
+
self.steam_id.eql?(player.steam_id)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Checks if the player is a bot.
|
55
|
+
# @return [Boolean] true if the player is a bot
|
56
|
+
def bot? ; self.steam_id == 'BOT' end
|
57
|
+
|
58
|
+
# Checks if the player is on the BLU team.
|
59
|
+
# @return [Boolean] true if the player is on the BLU team
|
60
|
+
def blu? ; self.team == 'Blue' end
|
61
|
+
|
62
|
+
# Checks if the player is on the RED team.
|
63
|
+
# @return [Boolean] true if the player is on the RED team
|
64
|
+
def red? ; self.team == 'Red' end
|
65
|
+
|
66
|
+
# Checks if the player is a spectator.
|
67
|
+
# @return [Boolean] true if the player is a spectator
|
68
|
+
def spectator? ; self.team == 'Spectator' end
|
69
|
+
|
70
|
+
# Checks if the player is not assigned to any team. (This can be considered a fourth team in some contexts.)
|
71
|
+
# @return [Boolean] true if the player is unassigned
|
72
|
+
def unassigned? ; self.team == 'Unassigned' end
|
73
|
+
|
74
|
+
# Checks if the player is in the game as either a RED or BLU player.
|
75
|
+
# @return [Boolean] true if the player is either on RED or BLU
|
76
|
+
def has_team? ; blu? || red? end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: australium
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- awk
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: TF2 log parser
|
42
|
+
email:
|
43
|
+
- awkisopen@shutupandwrite.net
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".yardopts"
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- australium.gemspec
|
55
|
+
- lib/australium.rb
|
56
|
+
- lib/australium/event.rb
|
57
|
+
- lib/australium/events/map_load.rb
|
58
|
+
- lib/australium/events/map_start.rb
|
59
|
+
- lib/australium/events/player_connect.rb
|
60
|
+
- lib/australium/events/player_disconnect.rb
|
61
|
+
- lib/australium/events/player_enter_game.rb
|
62
|
+
- lib/australium/events/player_join_team.rb
|
63
|
+
- lib/australium/events/player_kill.rb
|
64
|
+
- lib/australium/events/player_name_change.rb
|
65
|
+
- lib/australium/events/player_role_change.rb
|
66
|
+
- lib/australium/events/player_say.rb
|
67
|
+
- lib/australium/events/player_suicide.rb
|
68
|
+
- lib/australium/events/trigger.rb
|
69
|
+
- lib/australium/game.rb
|
70
|
+
- lib/australium/game_state.rb
|
71
|
+
- lib/australium/parser.rb
|
72
|
+
- lib/australium/player.rb
|
73
|
+
- lib/australium/ref/match_data_to_hash.rb
|
74
|
+
- lib/australium/ref/open_struct_deep_clone.rb
|
75
|
+
- lib/australium/version.rb
|
76
|
+
homepage: http://training.tf
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '2.1'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.2.2
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: TF2 log parser
|
100
|
+
test_files: []
|
101
|
+
has_rdoc:
|