openra 2.3.0 → 4.0.1

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
  SHA256:
3
- metadata.gz: 935333ec74bcce9f22c729a66247b9adbd18c25810254b60ffaae435c51f884f
4
- data.tar.gz: 375cd8e312984afcf9316c1a58f6292be496f4177fd582c859490ee608341cbd
3
+ metadata.gz: 2f78a7b23b3de1f8ff86b7da07a600488a25cbe70e3474f4da6d90842ff311ae
4
+ data.tar.gz: 626f986c4fe746e1bbbc24668545f1f07ea821c6e6992268df3bb633ac159809
5
5
  SHA512:
6
- metadata.gz: 43037b2237e9128f250417b965282a0f3b84aca84694f32044e6f43b2221230bf327a3e066884754861b7d130315323e2afc3addc24000a555146ede6c99dbed
7
- data.tar.gz: d31fe43a09269b77a7a10cd0b6fb0f1ad88884f45ed9f801a2b11866d83f580a89daca4b9b95b9836175399ed1d4fedf7c8a5b8d51850e2823288bd07fa45e63
6
+ metadata.gz: 0a5b606ddfc95cf24786c87a4801eca3242becd9a82e3585a5df80e921f821bf174f4a8c9b30e70bfc39ae8732d49b077ecc82f77ed1403245eadb03d083dfbe
7
+ data.tar.gz: b0c10b6128285503389c919c0db3cb0e587ce9d9cff7c6788fbebea06a1c303a2c82a2e4a601c0bc225a7fea34eca00ca84748657828fd7d88f8c9a95136c852
@@ -1,5 +1,48 @@
1
1
  ## Unreleased
2
2
 
3
+ [Compare v4.0.1...HEAD](https://github.com/AMHOL/openra-ruby/compare/v4.0.1...HEAD)
4
+
5
+ ## v4.0.1
6
+
7
+ ### Fixed
8
+
9
+ * [maint] Ignore empty SyncLobbyClients orders ([AMHOL](https://github.com/AMHOL))
10
+
11
+ [Compare v4.0.0...v4.0.1](https://github.com/AMHOL/openra-ruby/compare/v4.0.0...v4.0.1)
12
+
13
+ ## v4.0.0
14
+
15
+ ### Added
16
+
17
+ * [maint] Support playtest-20201213 - drops support for all older clients ([AMHOL](https://github.com/AMHOL))
18
+ * [core] Generate file hash in replay-data and replay-metadata commands ([AMHOL](https://github.com/AMHOL))
19
+
20
+ [Compare v3.0.1...v4.0.0](https://github.com/AMHOL/openra-ruby/compare/v3.0.1...v4.0.0)
21
+
22
+ ## v3.0.1
23
+
24
+ ### Added
25
+
26
+ * [maint] Added openra-commands file to load stand-alone commands ([AMHOL](https://github.com/AMHOL))
27
+
28
+ [Compare v3.0.0...v3.0.1](https://github.com/AMHOL/openra-ruby/compare/v3.0.0...v3.0.1)
29
+
30
+ ## v3.0.0
31
+
32
+ ### Added
33
+
34
+ * [replay-metadata] Renamed metadata command to replay-metadata ([AMHOL](https://github.com/AMHOL))
35
+
36
+ [Compare v2.3.1...v3.0.0](https://github.com/AMHOL/openra-ruby/compare/v2.3.0...v3.0.0)
37
+
38
+ ## v2.3.1
39
+
40
+ * [replay-data] Fixed error when parsing replays with spectators ([AMHOL](https://github.com/AMHOL))
41
+
42
+ [Compare v2.3.0...v2.3.1](https://github.com/AMHOL/openra-ruby/compare/v2.3.0...v2.3.1)
43
+
44
+ ## v2.3.0
45
+
3
46
  ### Added
4
47
 
5
48
  * [replay-data] Add AdvancedChronoshift to Openra::SUPPORT_POWERS ([AMHOL](https://github.com/AMHOL))
@@ -9,7 +52,7 @@
9
52
 
10
53
  * [replay-data] Store clients from SyncInfo command and keep syncing after game start ([AMHOL](https://github.com/AMHOL))
11
54
 
12
- [Compare v2.2.0...HEAD](https://github.com/AMHOL/openra-ruby/compare/v2.2.0...HEAD)
55
+ [Compare v2.2.0...v2.3.0](https://github.com/AMHOL/openra-ruby/compare/v2.2.0...v2.3.0)
13
56
 
14
57
  ## v2.2.0
15
58
 
data/README.md CHANGED
@@ -25,7 +25,7 @@ gem update openra
25
25
 
26
26
  ```sh
27
27
  openra replay-data /path/to/replay.orarep [--format json|pretty-json|yaml]
28
- openra metadata /path/to/replay.orarep [--format json|pretty-json|yaml]
28
+ openra replay-metadata /path/to/replay.orarep [--format json|pretty-json|yaml]
29
29
  ```
30
30
 
31
31
  ### Other tools
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openra/commands'
@@ -1,16 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
4
3
  require 'yaml'
5
4
  require 'json'
6
5
  require 'dry/cli'
7
6
  require 'openra/version'
8
- require 'openra/constants'
9
- require 'openra/replays'
10
- require 'openra/cli/utils'
7
+ require 'openra/commands'
11
8
  require 'openra/cli/formatters'
12
- require 'openra/cli/commands/metadata'
13
9
  require 'openra/cli/commands/replay_data'
10
+ require 'openra/cli/commands/replay_metadata'
14
11
  require 'openra/cli/commands/version'
15
12
  require 'openra/cli/command_registry'
16
13
 
@@ -5,8 +5,8 @@ module Openra
5
5
  class CommandRegistry
6
6
  extend Dry::CLI::Registry
7
7
 
8
- register 'metadata', Commands::Metadata
9
8
  register 'replay-data', Commands::ReplayData
9
+ register 'replay-metadata', Commands::ReplayMetadata
10
10
  register 'version', Commands::Version
11
11
  end
12
12
  end
@@ -4,155 +4,13 @@ module Openra
4
4
  class CLI
5
5
  module Commands
6
6
  class ReplayData < Dry::CLI::Command
7
- include CLI::Utils
8
-
9
7
  desc 'Output replay data to stdout'
10
8
 
11
9
  argument :replay, required: true, desc: 'Path of the replay file to read data from'
12
10
  option :format, default: 'json', values: %w(json pretty-json yaml), desc: 'Output format'
13
11
 
14
12
  def call(replay:, **options)
15
- replay = Openra::Replays::Replay.new(replay)
16
- support_powers = SUPPORT_POWERS.fetch(replay.metadata.mod, {})
17
-
18
- data = {
19
- mod: replay.metadata.mod,
20
- version: replay.metadata.version,
21
- server_name: nil,
22
- map: {
23
- name: utf8(replay.metadata.map_name),
24
- hash: replay.metadata.map_hash
25
- },
26
- game: {
27
- type: replay.players.each_with_object(Hash.new(0)) { |player, hash|
28
- if player.team.nil?
29
- hash[SecureRandom.uuid] += 1
30
- else
31
- hash[player.team] += 1
32
- end
33
- }.values.join('v'),
34
- start_time: replay.metadata.start_time.iso8601,
35
- end_time: replay.metadata.end_time.iso8601,
36
- duration: time((replay.metadata.end_time - replay.metadata.start_time) * 1000),
37
- options: nil
38
- },
39
- clients: [],
40
- chat: []
41
- }
42
-
43
- current_sync_clients = []
44
- current_sync_info = nil
45
-
46
- replay.each_order do |order|
47
- client = current_sync_clients.find do |candidate|
48
- candidate.index == order.client_index.to_s
49
- end
50
-
51
- case order.command
52
- when 'StartGame'
53
- data[:clients] = current_sync_info.clients.map do |client|
54
- player = replay.player(client.index)
55
- player_index = replay.players.index(player) + FIRST_PLAYER_INDEX if player
56
-
57
- {
58
- index: client.index,
59
- player_index: player_index,
60
- name: utf8(client.name),
61
- fingerprint: client.fingerprint,
62
- preferred_color: client.preferred_color,
63
- color: client.color,
64
- spawn: {
65
- random: player&.is_random_spawn,
66
- point: client.spawn_point
67
- },
68
- faction: {
69
- random: player&.is_random_faction,
70
- chosen: client.faction_name.downcase,
71
- actual: player&.faction_id
72
- },
73
- ip: client.ip,
74
- team: player&.team,
75
- is_bot: player&.is_bot || false,
76
- is_admin: client.is_admin,
77
- is_player: !player.nil?,
78
- is_winner: player&.outcome == 'Won',
79
- outcome_time: player.outcome_time.iso8601,
80
- build: [],
81
- support_powers: []
82
- }
83
- end
84
-
85
- data[:game][:options] = {
86
- explored_map: current_sync_info.global_settings.game_options.explored_map_enabled.value,
87
- speed: current_sync_info.global_settings.game_options.game_speed.value,
88
- starting_cash: current_sync_info.global_settings.game_options.starting_cash.value,
89
- starting_units: current_sync_info.global_settings.game_options.starting_units.value,
90
- fog_enabled: current_sync_info.global_settings.game_options.fog_enabled.value,
91
- cheats_enabled: current_sync_info.global_settings.game_options.cheats_enabled.value,
92
- kill_bounty_enabled: current_sync_info.global_settings.game_options.bounties_enabled.value,
93
- allow_undeploy: current_sync_info.global_settings.game_options.conyard_undeploy_enabled.value,
94
- crates_enabled: current_sync_info.global_settings.game_options.crates_enabled.value,
95
- build_off_allies: current_sync_info.global_settings.game_options.build_off_allies_enabled.value,
96
- restrict_build_radius: current_sync_info.global_settings.game_options.restricted_build_radius_enabled.value,
97
- short_game: current_sync_info.global_settings.game_options.short_game_enabled.value,
98
- techlevel: current_sync_info.global_settings.game_options.tech_level.value
99
- }
100
- when 'SyncInfo'
101
- current_sync_info = Openra::Struct::SyncInfo.new(
102
- Openra::MiniYAML.load(order.target)
103
- )
104
- current_sync_clients = current_sync_info.clients
105
- when 'SyncLobbyClients'
106
- current_sync_clients = Openra::Struct::SyncLobbyClients.new(
107
- Openra::MiniYAML.load(order.target)
108
- ).clients
109
- when *support_powers.keys
110
- key = support_powers.fetch(utf8(order.command))
111
- client_hash = data[:clients].find do |candidate|
112
- candidate[:index] == order.client_index.to_s
113
- end
114
-
115
- client_hash[:support_powers] << {
116
- type: key,
117
- game_time: time(order.frame.pred * current_sync_info.global_settings.frametime_multiplier),
118
- placement: cell(order.target_cell.to_i),
119
- extra_placement: cell(order.extra_cell.to_i),
120
- }
121
- when 'PlaceBuilding'
122
- # subject_id stores the player index here
123
- # as bot commands are issued by the host client
124
- client_hash = data[:clients].find do |candidate|
125
- candidate[:player_index] == order.subject_id
126
- end
127
-
128
- client_hash[:build] << {
129
- structure: utf8(order.target),
130
- game_time: time(order.frame.pred * current_sync_info.global_settings.frametime_multiplier),
131
- placement: cell(order.target_cell.to_i)
132
- }
133
- when 'Message'
134
- data[:chat] << {
135
- channel: 'server',
136
- name: nil,
137
- message: utf8(order.target)
138
- }
139
- when 'Chat'
140
- data[:chat] << {
141
- channel: 'global',
142
- name: utf8(client.name),
143
- message: utf8(order.target)
144
- }
145
- when 'TeamChat'
146
- data[:chat] << {
147
- channel: client.team,
148
- name: utf8(client.name),
149
- message: utf8(order.target)
150
- }
151
- end
152
- end
153
-
154
- data[:server_name] = utf8(current_sync_info.global_settings.server_name)
155
-
13
+ data = Openra::Commands::Replays::ExtractData.new.call(replay)
156
14
  puts FORMATTERS.fetch(options[:format]).call(data)
157
15
  end
158
16
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openra
4
+ class CLI
5
+ module Commands
6
+ class ReplayMetadata < Dry::CLI::Command
7
+ desc 'Output replay metadata to stdout'
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
+ data = Openra::Commands::Replays::ExtractMetadata.new.call(replay)
14
+ puts FORMATTERS.fetch(options[:format]).call(data)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ require 'securerandom'
2
+ require 'openra/constants'
3
+ require 'openra/replays'
4
+ require 'openra/commands/utils'
5
+ require 'openra/commands/replays/extract_metadata'
6
+ require 'openra/commands/replays/extract_data'
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openra
4
+ module Commands
5
+ module Replays
6
+ class ExtractData
7
+ include Utils
8
+
9
+ def call(filepath)
10
+ replay = Openra::Replays::Replay.new(filepath)
11
+ support_powers = SUPPORT_POWERS.fetch(replay.metadata.mod, {})
12
+
13
+ data = {
14
+ mod: replay.metadata.mod,
15
+ version: replay.metadata.version,
16
+ server_name: nil,
17
+ file: {
18
+ hash: replay.file_hash
19
+ },
20
+ map: {
21
+ name: utf8(replay.metadata.map_name),
22
+ hash: replay.metadata.map_hash
23
+ },
24
+ game: {
25
+ type: replay.players.each_with_object(Hash.new(0)) { |player, hash|
26
+ if player.team.nil?
27
+ hash[SecureRandom.uuid] += 1
28
+ else
29
+ hash[player.team] += 1
30
+ end
31
+ }.values.join('v'),
32
+ start_time: replay.metadata.start_time.iso8601,
33
+ end_time: replay.metadata.end_time.iso8601,
34
+ duration: time((replay.metadata.end_time - replay.metadata.start_time) * 1000),
35
+ options: nil
36
+ },
37
+ clients: [],
38
+ chat: []
39
+ }
40
+
41
+ current_sync_clients = []
42
+ current_sync_info = nil
43
+
44
+ replay.each_order do |order|
45
+ client = current_sync_clients.find do |candidate|
46
+ candidate.index == order.client_index.to_s
47
+ end
48
+
49
+ case order.command
50
+ when 'StartGame'
51
+ data[:clients] = current_sync_info.clients.map do |client|
52
+ player = replay.player(client.index)
53
+ player_index = replay.players.index(player) + FIRST_PLAYER_INDEX if player
54
+
55
+ {
56
+ index: client.index,
57
+ player_index: player_index,
58
+ name: utf8(client.name),
59
+ fingerprint: client.fingerprint,
60
+ preferred_color: client.preferred_color,
61
+ color: client.color,
62
+ spawn: {
63
+ random: player&.is_random_spawn,
64
+ point: client.spawn_point
65
+ },
66
+ faction: {
67
+ random: player&.is_random_faction,
68
+ chosen: client.faction_name.downcase,
69
+ actual: player&.faction_id
70
+ },
71
+ ip: client.ip,
72
+ team: player&.team,
73
+ is_bot: player&.is_bot || false,
74
+ is_admin: client.is_admin,
75
+ is_player: !player.nil?,
76
+ is_winner: player&.outcome == 'Won',
77
+ outcome_time: player&.outcome_time&.iso8601,
78
+ build: [],
79
+ support_powers: []
80
+ }
81
+ end
82
+
83
+ data[:game][:options] = {
84
+ explored_map: current_sync_info.global_settings.game_options.explored_map_enabled.value,
85
+ speed: current_sync_info.global_settings.game_options.game_speed.value,
86
+ starting_cash: current_sync_info.global_settings.game_options.starting_cash.value,
87
+ starting_units: current_sync_info.global_settings.game_options.starting_units.value,
88
+ fog_enabled: current_sync_info.global_settings.game_options.fog_enabled.value,
89
+ cheats_enabled: current_sync_info.global_settings.game_options.cheats_enabled.value,
90
+ kill_bounty_enabled: current_sync_info.global_settings.game_options.bounties_enabled.value,
91
+ allow_undeploy: current_sync_info.global_settings.game_options.conyard_undeploy_enabled.value,
92
+ crates_enabled: current_sync_info.global_settings.game_options.crates_enabled.value,
93
+ build_off_allies: current_sync_info.global_settings.game_options.build_off_allies_enabled.value,
94
+ restrict_build_radius: current_sync_info.global_settings.game_options.restricted_build_radius_enabled.value,
95
+ short_game: current_sync_info.global_settings.game_options.short_game_enabled.value,
96
+ techlevel: current_sync_info.global_settings.game_options.tech_level.value
97
+ }
98
+ when 'SyncInfo'
99
+ current_sync_info = Openra::Struct::SyncInfo.new(
100
+ Openra::MiniYAML.load(order.target)
101
+ )
102
+ current_sync_clients = current_sync_info.clients
103
+ when 'SyncLobbyClients'
104
+ next if order.target == "\n"
105
+
106
+ current_sync_clients = Openra::Struct::SyncLobbyClients.new(
107
+ Openra::MiniYAML.load(order.target)
108
+ ).clients
109
+ when *support_powers.keys
110
+ key = support_powers.fetch(utf8(order.command))
111
+ client_hash = data[:clients].find do |candidate|
112
+ candidate[:index] == order.client_index.to_s
113
+ end
114
+
115
+ client_hash[:support_powers] << {
116
+ type: key,
117
+ game_time: time(order.frame.pred * current_sync_info.global_settings.frametime_multiplier),
118
+ placement: cell(order.target_cell.to_i),
119
+ extra_placement: cell(order.extra_cell.to_i),
120
+ }
121
+ when 'PlaceBuilding'
122
+ # subject_id stores the player index here
123
+ # as bot commands are issued by the host client
124
+ client_hash = data[:clients].find do |candidate|
125
+ candidate[:player_index] == order.subject_id
126
+ end
127
+
128
+ client_hash[:build] << {
129
+ structure: utf8(order.target),
130
+ game_time: time(order.frame.pred * current_sync_info.global_settings.frametime_multiplier),
131
+ placement: cell(order.target_cell.to_i)
132
+ }
133
+ when 'Message'
134
+ data[:chat] << {
135
+ channel: 'server',
136
+ name: nil,
137
+ message: utf8(order.target)
138
+ }
139
+ when 'Chat'
140
+ data[:chat] << {
141
+ channel: 'global',
142
+ name: utf8(client.name),
143
+ message: utf8(order.target)
144
+ }
145
+ when 'TeamChat'
146
+ data[:chat] << {
147
+ channel: client.team,
148
+ name: utf8(client.name),
149
+ message: utf8(order.target)
150
+ }
151
+ end
152
+ end
153
+
154
+ data[:server_name] = utf8(current_sync_info.global_settings.server_name)
155
+
156
+ data
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -1,22 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Openra
4
- class CLI
5
- module Commands
6
- class Metadata < Dry::CLI::Command
7
- include CLI::Utils
4
+ module Commands
5
+ module Replays
6
+ class ExtractMetadata
7
+ include Utils
8
8
 
9
- desc 'Output replay metadata to stdout'
9
+ def call(filepath)
10
+ replay = Openra::Replays::Replay.new(filepath)
10
11
 
11
- argument :replay, required: true, desc: 'Path of the replay file to read data from'
12
- option :format, default: 'json', values: %w(json pretty-json yaml), desc: 'Output format'
13
-
14
- def call(replay:, **options)
15
- replay = Openra::Replays::Replay.new(replay)
16
-
17
- data = {
12
+ {
18
13
  mod: replay.metadata.mod,
19
14
  version: replay.metadata.version,
15
+ file: {
16
+ hash: replay.file_hash
17
+ },
20
18
  map: {
21
19
  name: utf8(replay.metadata.map_name),
22
20
  hash: replay.metadata.map_hash
@@ -55,8 +53,6 @@ module Openra
55
53
  }
56
54
  }
57
55
  }
58
-
59
- puts FORMATTERS.fetch(options[:format]).call(data)
60
56
  end
61
57
  end
62
58
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Openra
4
- class CLI
4
+ module Commands
5
5
  module Utils
6
6
  def utf8(string)
7
7
  string.force_encoding('UTF-8')
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'date'
4
+ require 'digest'
4
5
  require 'bindata'
5
6
  require 'bindata/big_integer'
6
7
  require 'bindata/pascal_string'
@@ -24,6 +24,10 @@ module Openra
24
24
  end
25
25
  end
26
26
 
27
+ def generate_hash
28
+ Digest::SHA256.hexdigest(file.read)
29
+ end
30
+
27
31
  def metadata
28
32
  # https://github.com/OpenRA/OpenRA/blob/23b3c237b7071fd308c4664b0b6c5d719c0f3c74/OpenRA.Game/FileFormats/ReplayMetadata.cs#L96
29
33
  @metadata ||= begin
@@ -6,19 +6,22 @@ module Openra
6
6
  class Order < BinData::Record
7
7
  HEX_FE = ?\xFE.dup.force_encoding('ASCII-8BIT').freeze
8
8
  HEX_FF = ?\xFF.dup.force_encoding('ASCII-8BIT').freeze
9
- # NEED TO LEARN HOW TO READ FLAGS AND TARGET TYPE
9
+
10
10
  IS_STANDARD_ORDER = -> { order_type == HEX_FF }
11
11
  IS_IMMEDIATE_ORDER = -> { order_type == HEX_FE }
12
- HAS_TARGET = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 1) == 1 }
12
+ HAS_TARGET = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 0x01) == 0x01 }
13
+ HAS_EXTRA_ACTORS = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 0x02) == 0x02 }
13
14
  TARGET_IS_ACTOR = -> { instance_exec(&HAS_TARGET) && target_type == 1 }
14
15
  TARGET_IS_TERRAIN = -> { instance_exec(&HAS_TARGET) && target_type == 2 }
15
16
  TARGET_IS_FROZEN_ACTOR = -> { instance_exec(&HAS_TARGET) && target_type == 3 }
16
- TARGET_IS_CELL = -> { instance_exec(&TARGET_IS_TERRAIN) && (flags & 64) == 64 }
17
- TARGET_NOT_CELL = -> { instance_exec(&TARGET_IS_TERRAIN) && (flags & 64) != 64 }
18
- HAS_SUBJECT = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 128) == 128 }
19
- HAS_TARGET_STRING = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 4) == 4 }
20
- HAS_EXTRA_LOCATION = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 16) == 16 }
21
- HAS_EXTRA_DATA = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 32) == 32 }
17
+ TARGET_IS_CELL = -> { instance_exec(&TARGET_IS_TERRAIN) && (flags & 0x40) == 0x40 }
18
+ TARGET_NOT_CELL = -> { instance_exec(&TARGET_IS_TERRAIN) && (flags & 0x40) != 0x40 }
19
+ HAS_SUBJECT = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 0x80) == 0x80 }
20
+ HAS_TARGET_STRING = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 0x04) == 0x04 }
21
+ HAS_EXTRA_LOCATION = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 0x10) == 0x10 }
22
+ HAS_EXTRA_DATA = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 0x20) == 0x20 }
23
+ # IS_QUEUED = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 0x08) == 0x08 }
24
+ IS_GROUPED = -> { instance_exec(&IS_STANDARD_ORDER) && (flags & 0x100) == 0x100 }
22
25
 
23
26
  endian :little
24
27
  # Common
@@ -27,7 +30,7 @@ module Openra
27
30
  # Immediate Order Data
28
31
  pascal_string :immediate_order_target, onlyif: IS_IMMEDIATE_ORDER
29
32
  # Standard Order Data
30
- uint8 :flags, onlyif: IS_STANDARD_ORDER
33
+ int16 :flags, onlyif: IS_STANDARD_ORDER
31
34
  uint32 :subject_id, onlyif: HAS_SUBJECT
32
35
  uint8 :target_type, onlyif: HAS_TARGET
33
36
  uint32 :actor_id, onlyif: TARGET_IS_ACTOR
@@ -39,8 +42,12 @@ module Openra
39
42
  int32 :pos_y, onlyif: TARGET_NOT_CELL
40
43
  int32 :pos_z, onlyif: TARGET_NOT_CELL
41
44
  pascal_string :standard_order_target, onlyif: HAS_TARGET_STRING
45
+ int32 :extra_actors_count, onlyif: HAS_EXTRA_ACTORS
46
+ array :extra_actor_ids, type: :uint32, initial_length: :extra_actors_count, onlyif: HAS_EXTRA_ACTORS
42
47
  int32 :extra_cell, onlyif: HAS_EXTRA_LOCATION
43
48
  uint32 :extra_data, onlyif: HAS_EXTRA_DATA
49
+ int32 :grouped_actors_count, onlyif: IS_GROUPED
50
+ array :grouped_actor_ids, type: :uint32, initial_length: :grouped_actors_count, onlyif: IS_GROUPED
44
51
 
45
52
  def target
46
53
  standard? ? standard_order_target : immediate_order_target
@@ -9,6 +9,10 @@ module Openra
9
9
  @file = Openra::Replays::File.new(filename)
10
10
  end
11
11
 
12
+ def file_hash
13
+ file.generate_hash
14
+ end
15
+
12
16
  def metadata
13
17
  @metadata ||= Openra::Struct::Metadata.new(
14
18
  Openra::MiniYAML.load(file.metadata.data)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'dry/transformer/all'
4
- require 'dry-struct'
5
4
  require 'openra/types'
5
+ require 'dry-struct'
6
6
  require 'openra/struct/functions'
7
7
  require 'openra/struct/pre_processor'
8
8
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Openra
4
- VERSION = '2.3.0'.freeze
4
+ VERSION = '4.0.1'.freeze
5
5
  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: 2.3.0
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Holland
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-07 00:00:00.000000000 Z
11
+ date: 2021-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -151,6 +151,7 @@ files:
151
151
  - lib/bindata/big_integer.rb
152
152
  - lib/bindata/pascal_string.rb
153
153
  - lib/openra-cli.rb
154
+ - lib/openra-commands.rb
154
155
  - lib/openra-mini_yaml.rb
155
156
  - lib/openra-replays.rb
156
157
  - lib/openra-struct.rb
@@ -158,11 +159,14 @@ files:
158
159
  - lib/openra.rb
159
160
  - lib/openra/cli.rb
160
161
  - lib/openra/cli/command_registry.rb
161
- - lib/openra/cli/commands/metadata.rb
162
162
  - lib/openra/cli/commands/replay_data.rb
163
+ - lib/openra/cli/commands/replay_metadata.rb
163
164
  - lib/openra/cli/commands/version.rb
164
165
  - lib/openra/cli/formatters.rb
165
- - lib/openra/cli/utils.rb
166
+ - lib/openra/commands.rb
167
+ - lib/openra/commands/replays/extract_data.rb
168
+ - lib/openra/commands/replays/extract_metadata.rb
169
+ - lib/openra/commands/utils.rb
166
170
  - lib/openra/constants.rb
167
171
  - lib/openra/mini_yaml.rb
168
172
  - lib/openra/replays.rb