openra 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -1
- data/README.md +8 -2
- data/lib/openra/cli.rb +2 -1
- data/lib/openra/cli/command_registry.rb +1 -0
- data/lib/openra/cli/commands/detect_production_macros.rb +130 -0
- data/lib/openra/cli/commands/formatters.rb +33 -0
- data/lib/openra/cli/commands/replay_data.rb +6 -12
- data/lib/openra/replays/replay.rb +4 -1
- data/lib/openra/version.rb +1 -1
- metadata +4 -3
- data/lib/openra/cli/commands/replay_data/formatters.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8e899e24d7c1a64648be5f39fb249439f7ff008
|
4
|
+
data.tar.gz: 6efadd30042d44cfd54bcaecf9594b09d5aed992
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b15a3518af884311eac8fda0ea5323f30af197c1e2b4e00fdf86ae2103094d666ec2ba133a8c96ad7ad6e0f3af1fd27b3008aa5f539dbce03d9c13d9da63cf79
|
7
|
+
data.tar.gz: 1631bcefa11178ba60eb37d4a71019e4e4383b3e33290c29f2ccde99aae410e2e51b98bf9acce0282172f15105bf1405d711d14ebea9a2f487f7b03ed0b8f7d9
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,22 @@
|
|
1
1
|
## Unreleased
|
2
2
|
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [detect-production-macros] A new command to detect production macros ([AMHOL](https://github.com/AMHOL))
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
|
9
|
+
* [core] Fix error when sync info does not exactly precede the start game order ([AMHOL](https://github.com/AMHOL))
|
10
|
+
|
11
|
+
[Compare v1.4.0...HEAD](https://github.com/AMHOL/openra-ruby/compare/v1.4.0...HEAD)
|
12
|
+
|
13
|
+
## v1.4.0
|
14
|
+
|
3
15
|
### Fixed
|
4
16
|
|
5
17
|
* [replay-data] Output timestamps in ISO8601 (thanks to [dragunoff](https://github.com/dragunoff)) ([AMHOL](https://github.com/AMHOL))
|
6
18
|
|
7
|
-
[Compare v1.3.0...
|
19
|
+
[Compare v1.3.0...v1.4.0](https://github.com/AMHOL/openra-ruby/compare/v1.3.0...v1.4.0)
|
8
20
|
|
9
21
|
## v1.3.0
|
10
22
|
|
data/README.md
CHANGED
@@ -2,7 +2,12 @@
|
|
2
2
|
|
3
3
|
Unofficial gem for [OpenRA](https://openra.net); a free, open-source real-time strategy game engine for early Westwood games such as Command & Conquer: Red Alert written in C#
|
4
4
|
|
5
|
-
Currently
|
5
|
+
Currently this gem implements the following commands:
|
6
|
+
|
7
|
+
* replay-data - parse and extract data from replay files
|
8
|
+
* detect-production-macros - parse replay data and attempt to detect production macro usage by players
|
9
|
+
|
10
|
+
If this is not what you're looking for, please see the [Other tools](#other-tools) section, perhaps what you need has already been implemented elsewhere.
|
6
11
|
|
7
12
|
### Installation
|
8
13
|
```sh
|
@@ -19,7 +24,8 @@ gem update openra
|
|
19
24
|
### Example usage
|
20
25
|
|
21
26
|
```sh
|
22
|
-
openra replay-data /
|
27
|
+
openra replay-data /path/to/replay.orarep [--format json|pretty-json|yaml]
|
28
|
+
openra detect-production-macros /path/to/replay.orarep [--format json|pretty-json|yaml]
|
23
29
|
```
|
24
30
|
|
25
31
|
### Other tools
|
data/lib/openra/cli.rb
CHANGED
@@ -4,7 +4,8 @@ require 'json'
|
|
4
4
|
require 'hanami/cli'
|
5
5
|
require 'openra/version'
|
6
6
|
require 'openra/replays'
|
7
|
-
require 'openra/cli/commands/
|
7
|
+
require 'openra/cli/commands/formatters'
|
8
|
+
require 'openra/cli/commands/detect_production_macros'
|
8
9
|
require 'openra/cli/commands/replay_data'
|
9
10
|
require 'openra/cli/commands/version'
|
10
11
|
require 'openra/cli/command_registry'
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Openra
|
2
|
+
class CLI
|
3
|
+
module Commands
|
4
|
+
class DetectProductionMacros < Hanami::CLI::Command
|
5
|
+
ARRAY_HASH = ->(hash, key) { hash[key] = [] }
|
6
|
+
|
7
|
+
desc 'Detect whether players are using production macros'
|
8
|
+
|
9
|
+
argument :replay, required: true, desc: 'Path of the replay file to read data from'
|
10
|
+
option :format, default: 'json', values: %w(json pretty-json yaml), desc: 'Output format'
|
11
|
+
|
12
|
+
def call(replay:, **options)
|
13
|
+
replay = Openra::Replays::Replay.new(replay)
|
14
|
+
commands = Hash.new(&ARRAY_HASH)
|
15
|
+
|
16
|
+
replay.orders.each do |order|
|
17
|
+
case order.command
|
18
|
+
when 'StartProduction'
|
19
|
+
commands[order.client_index.to_s] << {
|
20
|
+
target: order.target_string.to_s,
|
21
|
+
msec: order.frame * replay.frametime_multiplier
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
data = {
|
27
|
+
players: replay.players.select(&:is_human).map { |player|
|
28
|
+
player_commands = commands_with_delay(commands[player.index])
|
29
|
+
production_stats = production_stats(player_commands)
|
30
|
+
sequences = detect_sequences(player_commands)
|
31
|
+
|
32
|
+
{
|
33
|
+
index: player.index,
|
34
|
+
name: player.name,
|
35
|
+
team: player.team,
|
36
|
+
outcome: player.outcome,
|
37
|
+
production_stats: production_stats,
|
38
|
+
sequences: sequences,
|
39
|
+
suspected_macros: suspected_macros(production_stats, sequences)
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
puts FORMATTERS.fetch(options[:format]).call(data)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def commands_with_delay(commands)
|
50
|
+
commands.inject do |last_command, command|
|
51
|
+
command[:delay] = command[:msec] - last_command[:msec]
|
52
|
+
command
|
53
|
+
end
|
54
|
+
|
55
|
+
# Drop the first command, as we don't have a delay for that
|
56
|
+
commands.drop(1)
|
57
|
+
end
|
58
|
+
|
59
|
+
def detect_sequences(player_commands)
|
60
|
+
sequences = []
|
61
|
+
groups = player_commands.each_with_object(Hash.new(&ARRAY_HASH)).with_index do |(command, hash), index|
|
62
|
+
hash[command[:delay]] << command.merge(index: index)
|
63
|
+
end
|
64
|
+
|
65
|
+
groups.each_pair do |delay, commands|
|
66
|
+
sequence = []
|
67
|
+
|
68
|
+
commands.inject do |last_command, command|
|
69
|
+
if last_command[:index].next == command[:index]
|
70
|
+
sequence << command
|
71
|
+
elsif sequence.length > 1
|
72
|
+
sequences << {
|
73
|
+
delay: delay,
|
74
|
+
sequence: sequence.map { |c| c[:target] }
|
75
|
+
}
|
76
|
+
sequence = []
|
77
|
+
end
|
78
|
+
|
79
|
+
command
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
sequences = sequences.uniq.map do |sequence|
|
84
|
+
sequence.merge(
|
85
|
+
occurences: sequences.count { |current| current == sequence }
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
sequences.select { |sequence| sequence[:occurences] > 1 }.sort do |a, b|
|
90
|
+
b[:occurences] <=> a[:occurences]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def production_stats(player_commands)
|
95
|
+
order_delays = player_commands.map { |command| command[:delay] }
|
96
|
+
|
97
|
+
{
|
98
|
+
order_delay: {
|
99
|
+
min: order_delays.min,
|
100
|
+
max: order_delays.max,
|
101
|
+
mean: mean(order_delays),
|
102
|
+
median: median(order_delays)
|
103
|
+
}
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
def suspected_macros(production_stats, sequences)
|
108
|
+
suspicious_sequences = sequences.count do |sequence|
|
109
|
+
sequence[:delay] < 100 &&
|
110
|
+
sequence[:sequence].length > 3 &&
|
111
|
+
sequence[:sequence].uniq.length > 2
|
112
|
+
end
|
113
|
+
|
114
|
+
production_stats[:order_delay][:min] < 50 || suspicious_sequences > 0
|
115
|
+
end
|
116
|
+
|
117
|
+
def mean(ints)
|
118
|
+
ints.sum / ints.length
|
119
|
+
end
|
120
|
+
|
121
|
+
def median(ints)
|
122
|
+
ints = ints.sort
|
123
|
+
middle_index = (ints.length - 1) / 2.0
|
124
|
+
|
125
|
+
(ints[middle_index.floor] + ints[middle_index.ceil]) / 2
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Openra
|
2
|
+
class CLI
|
3
|
+
module Commands
|
4
|
+
module Formatters
|
5
|
+
class JSON
|
6
|
+
def call(data)
|
7
|
+
::JSON.dump(data)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class PrettyJSON
|
12
|
+
def call(data)
|
13
|
+
::JSON.pretty_generate(data)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class YAML
|
18
|
+
def call(data)
|
19
|
+
::YAML.dump(
|
20
|
+
Openra::Struct::Functions[:deep_stringify_keys].(data)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
FORMATTERS = {
|
27
|
+
'json' => Formatters::JSON.new,
|
28
|
+
'pretty-json' => Formatters::PrettyJSON.new,
|
29
|
+
'yaml' => Formatters::YAML.new,
|
30
|
+
}.freeze
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -2,12 +2,6 @@ module Openra
|
|
2
2
|
class CLI
|
3
3
|
module Commands
|
4
4
|
class ReplayData < Hanami::CLI::Command
|
5
|
-
FORMATTERS = {
|
6
|
-
'json' => Formatters::JSON.new,
|
7
|
-
'pretty-json' => Formatters::PrettyJSON.new,
|
8
|
-
'yaml' => Formatters::YAML.new,
|
9
|
-
}.freeze
|
10
|
-
|
11
5
|
desc 'Output replay data to stdout'
|
12
6
|
|
13
7
|
argument :replay, required: true, desc: 'Path of the replay file to read data from'
|
@@ -16,7 +10,7 @@ module Openra
|
|
16
10
|
def call(replay:, **options)
|
17
11
|
replay = Openra::Replays::Replay.new(replay)
|
18
12
|
|
19
|
-
|
13
|
+
data = {
|
20
14
|
mod: replay.metadata.mod,
|
21
15
|
version: replay.metadata.version,
|
22
16
|
server_name: utf8(replay.global_settings.server_name),
|
@@ -92,7 +86,7 @@ module Openra
|
|
92
86
|
Openra::YAML.load(order.target)
|
93
87
|
).clients
|
94
88
|
when 'PlaceBuilding'
|
95
|
-
client_hash =
|
89
|
+
client_hash = data[:clients].find do |candidate|
|
96
90
|
candidate[:index] == order.client_index.to_s
|
97
91
|
end
|
98
92
|
|
@@ -105,19 +99,19 @@ module Openra
|
|
105
99
|
}
|
106
100
|
}
|
107
101
|
when 'Message'
|
108
|
-
|
102
|
+
data[:chat] << {
|
109
103
|
channel: 'server',
|
110
104
|
name: nil,
|
111
105
|
message: utf8(order.target)
|
112
106
|
}
|
113
107
|
when 'Chat'
|
114
|
-
|
108
|
+
data[:chat] << {
|
115
109
|
channel: 'global',
|
116
110
|
name: utf8(client.name),
|
117
111
|
message: utf8(order.target)
|
118
112
|
}
|
119
113
|
when 'TeamChat'
|
120
|
-
|
114
|
+
data[:chat] << {
|
121
115
|
channel: client.team,
|
122
116
|
name: utf8(client.name),
|
123
117
|
message: utf8(order.target)
|
@@ -125,7 +119,7 @@ module Openra
|
|
125
119
|
end
|
126
120
|
end
|
127
121
|
|
128
|
-
puts FORMATTERS.fetch(options[:format]).call(
|
122
|
+
puts FORMATTERS.fetch(options[:format]).call(data)
|
129
123
|
end
|
130
124
|
|
131
125
|
private
|
@@ -56,7 +56,10 @@ module Openra
|
|
56
56
|
|
57
57
|
start_game_order = orders.find { |order| order[:command] == 'StartGame' }
|
58
58
|
start_game_index = orders.index(start_game_order)
|
59
|
-
|
59
|
+
pre_game_orders = orders[0..start_game_index]
|
60
|
+
start_game_sync_order = pre_game_orders.reverse.find do |order|
|
61
|
+
order[:command] == 'SyncInfo'
|
62
|
+
end
|
60
63
|
|
61
64
|
@sync_info = Openra::Struct::SyncInfo.new(
|
62
65
|
Openra::YAML.load(start_game_sync_order.target)
|
data/lib/openra/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Holland
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -133,8 +133,9 @@ files:
|
|
133
133
|
- lib/openra.rb
|
134
134
|
- lib/openra/cli.rb
|
135
135
|
- lib/openra/cli/command_registry.rb
|
136
|
+
- lib/openra/cli/commands/detect_production_macros.rb
|
137
|
+
- lib/openra/cli/commands/formatters.rb
|
136
138
|
- lib/openra/cli/commands/replay_data.rb
|
137
|
-
- lib/openra/cli/commands/replay_data/formatters.rb
|
138
139
|
- lib/openra/cli/commands/version.rb
|
139
140
|
- lib/openra/replays.rb
|
140
141
|
- lib/openra/replays/file.rb
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module Openra
|
2
|
-
class CLI
|
3
|
-
module Commands
|
4
|
-
class ReplayData < Hanami::CLI::Command
|
5
|
-
module Formatters
|
6
|
-
class JSON
|
7
|
-
def call(data)
|
8
|
-
::JSON.dump(data)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
class PrettyJSON
|
13
|
-
def call(data)
|
14
|
-
::JSON.pretty_generate(data)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
class YAML
|
19
|
-
def call(data)
|
20
|
-
::YAML.dump(
|
21
|
-
Openra::Struct::Functions[:deep_stringify_keys].(data)
|
22
|
-
)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|