australium 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Code Climate](https://codeclimate.com/github/awkisopen/australium.png)](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:
|