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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b648bef069d1c0a709f510f889703f24d4a6d043
4
- data.tar.gz: efedb97d0a81eb63694cedc2f50672e82be71257
3
+ metadata.gz: f8e899e24d7c1a64648be5f39fb249439f7ff008
4
+ data.tar.gz: 6efadd30042d44cfd54bcaecf9594b09d5aed992
5
5
  SHA512:
6
- metadata.gz: 41a705ad37119d25f18d3c5387ffb25b0e36b80d8818e55d8e0c251fe051600de56ed14c21659d93599df2b61fe4483c97fe120f1a74c12701acbd0ca6713178
7
- data.tar.gz: bfe0e10cfdb0292540a3c6adf90d0873012fe5a843050845bf26348d9e2b8f60afbe2aa1a1d69f8b8be4b716cb58fac4088a944644bebf34a7ce2cefda20fca7
6
+ metadata.gz: b15a3518af884311eac8fda0ea5323f30af197c1e2b4e00fdf86ae2103094d666ec2ba133a8c96ad7ad6e0f3af1fd27b3008aa5f539dbce03d9c13d9da63cf79
7
+ data.tar.gz: 1631bcefa11178ba60eb37d4a71019e4e4383b3e33290c29f2ccde99aae410e2e51b98bf9acce0282172f15105bf1405d711d14ebea9a2f487f7b03ed0b8f7d9
@@ -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...HEAD](https://github.com/AMHOL/openra-ruby/compare/v1.3.0...HEAD)
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 the only operation implemented by this gem is to parse and extract data from replay files, 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.
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 /payh/to/replay.orarep [--format json|pretty-json|yaml]
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
@@ -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/replay_data/formatters'
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'
@@ -4,6 +4,7 @@ module Openra
4
4
  extend Hanami::CLI::Registry
5
5
 
6
6
  register 'replay-data', Commands::ReplayData
7
+ register 'detect-production-macros', Commands::DetectProductionMacros
7
8
  register 'version', Commands::Version
8
9
  end
9
10
  end
@@ -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
- replay_data = {
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 = replay_data[:clients].find do |candidate|
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
- replay_data[:chat] << {
102
+ data[:chat] << {
109
103
  channel: 'server',
110
104
  name: nil,
111
105
  message: utf8(order.target)
112
106
  }
113
107
  when 'Chat'
114
- replay_data[:chat] << {
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
- replay_data[:chat] << {
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(replay_data)
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
- start_game_sync_order = orders[start_game_index.pred]
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)
@@ -1,3 +1,3 @@
1
1
  module Openra
2
- VERSION = '1.4.0'.freeze
2
+ VERSION = '1.5.0'.freeze
3
3
  end
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.0
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-07-17 00:00:00.000000000 Z
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