openra 0.3.0 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d4b0cd4fc230f79bfb083fb98c4adba801016c57
4
- data.tar.gz: 6e69e56820305fd0868b959b9787e0b15e52b50b
3
+ metadata.gz: 18eaa1cb9a89b397783943143d5038abf81eaa2a
4
+ data.tar.gz: 8d0656389b17e73063eb24973326921a68e3e845
5
5
  SHA512:
6
- metadata.gz: 8a5b42b78584c3b2a5ca4afbbd53e793c0bf0e357df0e250ff0e1801c731928eb97cabb8f84e04662128a770b6c44a0af759e06a073f15606fec1087a33b21aa
7
- data.tar.gz: 440ce52d27cc21c42b18d399b550b5ae22580c5e1fbd50f70256333426dfaac5af0bbb34239cc99f0d22597513cc07a4baef739568c8095af46f0477a68a657a
6
+ metadata.gz: a3889a2bdbb19f65e091e18bfc6168de0442c9c6c790c57024cb9a2e442b40754ee6234d139f83376e3e90168551da58a289e6fadbcd3e06b4312c393de9b4be
7
+ data.tar.gz: a99959ea027d0e554871bf05485df7794ba7dec96d235b778efea5fb13703764b6e7d4add86dc07225afb35315781a00ea9aaccbfbe56c5b98793eaa677e26d5
@@ -1,11 +1,25 @@
1
1
  ## Unreleased
2
2
 
3
+ ## Added
4
+
5
+ * [core] Clean up internals with structs and better abstractions ([AMHOL](https://github.com/AMHOL))
6
+ * [replay-data] Added duration struct to game output (formatted/msec) ([AMHOL](https://github.com/AMHOL))
7
+ * [replay-data] Added support for YAML output format ([AMHOL](https://github.com/AMHOL))
8
+
9
+ ## Removed
10
+
11
+ * [replay-data] Removed duration (seconds) field from game output ([AMHOL](https://github.com/AMHOL))
12
+
13
+ [Compare v0.3.0...HEAD](https://github.com/AMHOL/openra-ruby/compare/v0.3.0...HEAD)
14
+
15
+ ## v0.3.0
16
+
3
17
  ## Fixed
4
18
 
5
19
  * [replay-data] Fix "type" output when no teams set ([AMHOL](https://github.com/AMHOL))
6
20
  * [replay-data] Fix error uninitialized constant Openra::CLI::Commands::ReplayData::SecureRandom ([AMHOL](https://github.com/AMHOL))
7
21
 
8
- [Compare v0.2.0...HEAD](https://github.com/AMHOL/openra-ruby/compare/v0.2.0...HEAD)
22
+ [Compare v0.2.0...v0.3.0](https://github.com/AMHOL/openra-ruby/compare/v0.2.0...v0.3.0)
9
23
 
10
24
  ## v0.2.0
11
25
 
@@ -0,0 +1 @@
1
+ require 'openra/replays'
@@ -0,0 +1 @@
1
+ require 'openra/struct'
@@ -0,0 +1 @@
1
+ require 'openra/types'
@@ -0,0 +1 @@
1
+ require 'openra/yaml
@@ -1,2 +1,2 @@
1
1
  require 'openra/version'
2
- require 'openra/replays'
2
+ require 'openra/replays' # Replays requires everything else
@@ -1,5 +1,12 @@
1
+ require 'securerandom'
2
+ require 'yaml'
3
+ require 'json'
1
4
  require 'hanami/cli'
2
- require 'openra'
5
+ require 'openra/version'
6
+ require 'openra/replays'
7
+ require 'openra/cli/commands/replay_data/formatters'
8
+ require 'openra/cli/commands/replay_data'
9
+ require 'openra/cli/commands/version'
3
10
  require 'openra/cli/command_registry'
4
11
 
5
12
  module Openra
@@ -1,6 +1,3 @@
1
- require 'openra/cli/commands/replay_data'
2
- require 'openra/cli/commands/version'
3
-
4
1
  module Openra
5
2
  class CLI
6
3
  class CommandRegistry
@@ -1,190 +1,140 @@
1
- require 'json'
2
- require 'securerandom'
3
-
4
1
  module Openra
5
2
  class CLI
6
3
  module Commands
7
4
  class ReplayData < Hanami::CLI::Command
8
- ORDER_LATENCY_MAPPING = {
9
- 'slowest' => 2,
10
- 'slower' => 3,
11
- 'default' => 3,
12
- 'fast' => 4,
13
- 'faster' => 4,
14
- 'fastest' => 6
5
+ FORMATTERS = {
6
+ 'json' => Formatters::JSON.new,
7
+ 'pretty-json' => Formatters::PrettyJSON.new,
8
+ 'yaml' => Formatters::YAML.new,
15
9
  }.freeze
16
10
 
17
11
  desc 'Output replay data to stdout'
18
12
 
19
13
  argument :replay, required: true, desc: 'Path of the replay file to read data from'
20
- option :format, default: 'json', values: %w(json pretty-json), desc: 'Output format'
14
+ option :format, default: 'json', values: %w(json pretty-json yaml), desc: 'Output format'
21
15
 
22
16
  def call(replay:, **options)
23
17
  replay = Openra::Replays::Replay.new(replay)
24
18
 
25
- players = replay.metadata.each_with_object([]) do |(key, value), arr|
26
- next unless key.start_with?('Player')
27
- arr << value
28
- end
29
- player_mapping = players.each_with_object({}) do |player, mapping|
30
- mapping[player['ClientIndex']] = player
31
- end
32
- player_teams = players.map { |player| player['Team'] }
33
- team_alignment = player_teams.each_with_object({}) do |team, hash|
34
- if team.to_s == '0'
35
- hash[SecureRandom.uuid] = 1
36
- else
37
- hash[team] ||= 0
38
- hash[team] += 1
39
- end
40
- end
41
-
42
19
  replay_data = {
43
- mod: replay.mod,
44
- version: replay.version,
45
- server_name: nil,
20
+ mod: replay.metadata.mod,
21
+ version: replay.metadata.version,
22
+ server_name: utf8(replay.global_settings.server_name),
46
23
  map: {
47
- name: utf8(replay.map_title),
48
- hash: replay.map_id
24
+ name: utf8(replay.metadata.map_name),
25
+ hash: replay.metadata.map_hash
49
26
  },
50
27
  game: {
51
- type: team_alignment.values.join('v'),
52
- start_time: replay.start_time,
53
- end_time: replay.end_time,
54
- duration: replay.duration,
55
- options: {}
28
+ type: replay.clients.each_with_object(Hash.new(0)) { |client, hash|
29
+ if client.team.nil?
30
+ hash[SecureRandom.uuid] += 1
31
+ else
32
+ hash[client.team] += 1
33
+ end
34
+ }.values.join('v'),
35
+ start_time: replay.metadata.start_time,
36
+ end_time: replay.metadata.end_time,
37
+ duration: time((replay.metadata.end_time - replay.metadata.start_time) * 1000),
38
+ options: {
39
+ explored_map: replay.game_options.explored_map_enabled.value,
40
+ speed: replay.game_options.game_speed.value,
41
+ starting_cash: replay.game_options.starting_cash.value,
42
+ starting_units: replay.game_options.starting_units.value,
43
+ fog_enabled: replay.game_options.fog_enabled.value,
44
+ cheats_enabled: replay.game_options.cheats_enabled.value,
45
+ kill_bounty_enabled: replay.game_options.bounties_enabled.value,
46
+ allow_undeploy: replay.game_options.conyard_undeploy_enabled.value,
47
+ crates_enabled: replay.game_options.crates_enabled.value,
48
+ build_off_allies: replay.game_options.build_off_allies_enabled.value,
49
+ restrict_build_radius: replay.game_options.restricted_build_radius_enabled.value,
50
+ short_game: replay.game_options.short_game_enabled.value,
51
+ techlevel: replay.game_options.tech_level.value
52
+ }
53
+ },
54
+ clients: replay.clients.map { |client|
55
+ player = replay.player(client.index)
56
+
57
+ {
58
+ index: client.index,
59
+ name: utf8(client.name),
60
+ preferred_color: client.preferred_color,
61
+ color: client.preferred_color,
62
+ spawn: {
63
+ random: player&.is_random_spawn,
64
+ point: client.spawn_point
65
+ },
66
+ faction: {
67
+ chosen: client.faction_name.downcase,
68
+ actual: player&.faction_id
69
+ },
70
+ ip: client.ip,
71
+ team: player&.team,
72
+ is_bot: player&.is_bot || false,
73
+ is_admin: client.is_admin,
74
+ is_player: !player.nil?,
75
+ is_winner: player&.outcome == 'Won',
76
+ build: []
77
+ }
56
78
  },
57
- clients: [],
58
79
  chat: []
59
80
  }
60
81
 
61
- timestep = nil
62
- sync_info_orders = replay.orders.select do |order|
63
- order.command == 'SyncInfo'
64
- end
65
-
66
- sync_info_orders.reverse.each.with_index do |sync_info_order, index|
67
- sync_info = Openra::YAML.load(sync_info_order.target)
68
-
69
- # Get all clients
70
- sync_info.each_pair do |key, data|
71
- case key
72
- when /^Client@/
73
- player = player_mapping.fetch(data['Index'], {})
74
-
75
- replay_data[:clients] << {
76
- index: data['Index'],
77
- name: utf8(data['Name']),
78
- preferred_color: data['PreferredColor'],
79
- color: data['Color'],
80
- spawn: {
81
- random: player.fetch('IsRandomSpawnPoint', 'False') == 'True',
82
- point: player.fetch('SpawnPoint', nil)
83
- },
84
- faction: {
85
- chosen: data['Faction'].downcase,
86
- actual: player_mapping.fetch(data['Index'], {}).fetch('FactionId', nil)
87
- },
88
- ip: data['IpAddress'],
89
- team: player.fetch('Team', '0').to_s == '0' ? nil : data['Team'],
90
- is_bot: data['Bot'].nil? ? false : true,
91
- is_admin: data['IsAdmin'] == 'True',
92
- is_player: !player.empty?,
93
- is_winner: player.fetch('Outcome', nil) == 'Won',
94
- build: []
95
- } unless replay_data[:clients].any? { |client| client[:index] == data['Index'] }
96
- when 'GlobalSettings'
97
- next unless index.zero?
98
-
99
- timestep = Integer(data['Timestep']) * ORDER_LATENCY_MAPPING.fetch(
100
- data['Options']['gamespeed']['Value'],
101
- ORDER_LATENCY_MAPPING['default']
102
- )
103
-
104
- replay_data[:server_name] = data['ServerName']
105
- replay_data[:game][:options] = {
106
- explored_map: data['Options']['explored']['Value'] == 'True',
107
- speed: data['Options']['gamespeed']['Value'],
108
- starting_cash: data['Options']['startingcash']['Value'],
109
- starting_units: data['Options']['startingunits']['Value'],
110
- fog_enabled: data['Options']['fog']['Value'] == 'True',
111
- cheats_enabled: data['Options']['cheats']['Value'] == 'True',
112
- kill_bounty_enabled: data['Options']['bounty']['Value'] == 'True',
113
- allow_undeploy: data['Options']['factundeploy']['Value'] == 'True',
114
- crates_enabled: data['Options']['crates']['Value'] == 'True',
115
- build_off_allies: data['Options']['allybuild']['Value'] == 'True',
116
- restrict_build_radius: data['Options']['buildradius']['Value'] == 'True',
117
- short_game: data['Options']['shortgame']['Value'] == 'True',
118
- techlevel: data['Options']['techlevel']['Value']
119
- }
120
- end
121
- end
122
- end
123
-
124
82
  replay.orders.each do |order|
83
+ client = replay.client(order.client_index.to_s)
84
+
125
85
  case order.command
126
86
  when 'PlaceBuilding'
127
- client = replay_data[:clients].find do |candidate|
87
+ client_hash = replay_data[:clients].find do |candidate|
128
88
  candidate[:index] == order.client_index.to_s
129
89
  end
130
90
 
131
- current_time = order.frame * timestep
132
- current_time_seconds = current_time / 1000
133
- mm, ss = current_time_seconds.divmod(60)
134
- hh, mm = mm.divmod(60)
135
-
136
- client[:build] << {
137
- structure: order.target_string,
138
- game_time: {
139
- formatted: '%02d:%02d:%02d' % [hh, mm, ss],
140
- msec: current_time
141
- },
91
+ client_hash[:build] << {
92
+ structure: utf8(order.target_string),
93
+ game_time: time(order.frame * replay.frametime_multiplier),
142
94
  placement: {
143
- x: order.target_x,
144
- y: order.target_y
95
+ x: order.target_x.to_i,
96
+ y: order.target_y.to_i
145
97
  }
146
98
  }
147
99
  when 'Message'
148
100
  replay_data[:chat] << {
149
- channel: :server,
101
+ channel: 'server',
150
102
  name: nil,
151
103
  message: utf8(order.target)
152
104
  }
153
105
  when 'Chat'
154
- client = replay_data[:clients].find do |candidate|
155
- candidate[:index] == order.client_index.to_s
156
- end
157
-
158
106
  replay_data[:chat] << {
159
- channel: :global,
160
- name: client[:name],
107
+ channel: 'global',
108
+ name: client.name,
161
109
  message: utf8(order.target)
162
110
  }
163
111
  when 'TeamChat'
164
- client = replay_data[:clients].find do |candidate|
165
- candidate[:index] == order.client_index.to_s
166
- end
167
-
168
112
  replay_data[:chat] << {
169
- channel: client[:team],
170
- name: client[:name],
113
+ channel: client.team,
114
+ name: client.name,
171
115
  message: utf8(order.target)
172
116
  }
173
117
  end
174
118
  end
175
119
 
176
- case options[:format]
177
- when 'json'
178
- puts JSON.dump(replay_data)
179
- when 'pretty-json'
180
- puts JSON.pretty_generate(replay_data)
181
- end
120
+ puts FORMATTERS.fetch(options[:format]).call(replay_data)
182
121
  end
183
122
 
184
123
  private
185
124
 
186
125
  def utf8(string)
187
- string.force_encoding('UTF-8')
126
+ string.force_encoding('UTF-8').to_s
127
+ end
128
+
129
+ def time(msec)
130
+ sec = msec / 1000
131
+ mm, ss = sec.divmod(60)
132
+ hh, mm = mm.divmod(60)
133
+
134
+ {
135
+ formatted: '%02d:%02d:%02d' % [hh, mm, ss],
136
+ msec: msec.to_i
137
+ }
188
138
  end
189
139
  end
190
140
  end
@@ -0,0 +1,29 @@
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
@@ -1,8 +1,9 @@
1
1
  require 'date'
2
2
  require 'bindata'
3
- require 'big_integer'
4
- require 'pascal_string'
3
+ require 'bindata/big_integer'
4
+ require 'bindata/pascal_string'
5
5
  require 'openra/yaml'
6
+ require 'openra/struct'
6
7
  require 'openra/replays/order_decorator'
7
8
  require 'openra/replays/order'
8
9
  require 'openra/replays/order_list'
@@ -8,45 +8,68 @@ module Openra
8
8
  end
9
9
 
10
10
  def metadata
11
- @metadata ||= Openra::YAML.load(file.metadata.data)
11
+ @metadata ||= Openra::Struct::Metadata.new(
12
+ Openra::YAML.load(file.metadata.data)
13
+ )
12
14
  end
13
15
 
14
16
  def orders
15
17
  @orders ||= file.orders
16
18
  end
17
19
 
18
- def mod
19
- @mod ||= metadata['Root']['Mod']
20
+ def players
21
+ metadata.players
20
22
  end
21
23
 
22
- def version
23
- @version ||= metadata['Root']['Version']
24
+ def player(index)
25
+ players.find do |candidate|
26
+ candidate.index == index
27
+ end
24
28
  end
25
29
 
26
- def map_id
27
- @map_id ||= metadata['Root']['MapUid']
30
+ def clients
31
+ sync_info.clients
28
32
  end
29
33
 
30
- def map_title
31
- @map_title ||= metadata['Root']['MapTitle']
34
+ def client(index)
35
+ clients.find do |candidate|
36
+ candidate.index == index
37
+ end
32
38
  end
33
39
 
34
- def start_time
35
- @start_time ||= ::DateTime.strptime(
36
- metadata['Root']['StartTimeUtc'],
37
- '%Y-%m-%d %H-%M-%S'
38
- ).to_time
40
+ def global_settings
41
+ sync_info.global_settings
39
42
  end
40
43
 
41
- def end_time
42
- @end_time ||= ::DateTime.strptime(
43
- metadata['Root']['EndTimeUtc'],
44
- '%Y-%m-%d %H-%M-%S'
45
- ).to_time
44
+ def frametime_multiplier
45
+ global_settings.frametime_multiplier
46
46
  end
47
47
 
48
- def duration
49
- @duration ||= (end_time - start_time).to_i
48
+ def game_options
49
+ global_settings.game_options
50
+ end
51
+
52
+ private
53
+
54
+ def sync_info
55
+ return @sync_info if @sync_info
56
+
57
+ syncs = orders.reverse.each_with_object([]) do |order, arr|
58
+ next unless order.command == 'SyncInfo'
59
+
60
+ arr << Openra::Struct::SyncInfo.new(
61
+ Openra::YAML.load(order.target)
62
+ )
63
+ end
64
+
65
+ @sync_info = syncs.inject(syncs.shift) do |next_sync, sync|
66
+ next_sync.clients.each do |client|
67
+ next if sync.clients.map(&:index).include?(client.index)
68
+ sync.clients << client
69
+ end
70
+
71
+ sync
72
+ end
50
73
  end
51
74
  end
52
75
  end
@@ -0,0 +1,49 @@
1
+ require 'transproc'
2
+ require 'dry-struct'
3
+ require 'openra/types'
4
+ require 'openra/struct/functions'
5
+ require 'openra/struct/pre_processor'
6
+
7
+ module Openra
8
+ class Struct < Dry::Struct
9
+ UNDEFINED = Object.new.freeze
10
+ DEFAULT_PROCESSOR = PreProcessor.new
11
+
12
+ class << self
13
+ def preprocessor(processor = UNDEFINED)
14
+ if processor === UNDEFINED
15
+ @__preprocessor || DEFAULT_PROCESSOR
16
+ else
17
+ @__preprocessor = processor
18
+ end
19
+ end
20
+
21
+ def inherited(subclass)
22
+ super
23
+ subclass.preprocessor(preprocessor)
24
+ end
25
+
26
+ def define(&block)
27
+ instance_eval(&block)
28
+
29
+ preprocessor(preprocessor.with(schema: schema))
30
+
31
+ preprocessor.finalize!
32
+ end
33
+
34
+ def new(attributes)
35
+ super(preprocessor.call(attributes))
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ require 'openra/struct/client'
42
+ require 'openra/struct/player'
43
+ require 'openra/struct/game_options/boolean_option'
44
+ require 'openra/struct/game_options/integer_option'
45
+ require 'openra/struct/game_options/string_option'
46
+ require 'openra/struct/game_options'
47
+ require 'openra/struct/global_settings'
48
+ require 'openra/struct/sync_info'
49
+ require 'openra/struct/metadata'
@@ -0,0 +1,26 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ class Client < Openra::Struct
4
+ define do
5
+ attribute :index, Types::Strict::String.meta(from: 'Index')
6
+ attribute :preferred_color, Types::Strict::String.meta(from: 'PreferredColor')
7
+ attribute :color, Types::Strict::String.meta(from: 'Color')
8
+ attribute :faction_name, Types::Strict::String.meta(from: 'Faction')
9
+ attribute :spawn_point, Types::Strict::String.meta(from: 'SpawnPoint')
10
+ attribute :name, Types::Strict::String.meta(from: 'Name')
11
+ attribute :ip, Types::Strict::String.meta(from: 'IpAddress')
12
+ attribute :state, Types::Strict::String.meta(from: 'State')
13
+ attribute :team, Types::Strict::String.meta(from: 'Team')
14
+ attribute :slot, Types::Strict::String.meta(from: 'Slot', omittable: true)
15
+ attribute :bot_controller_index, Types::Strict::String.meta(
16
+ from: 'BotControllerClientIndex'
17
+ )
18
+ attribute :is_admin, Types::Params::Bool.meta(from: 'IsAdmin')
19
+ end
20
+
21
+ def team
22
+ attributes[:team] == '0' ? nil : attributes[:team]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ module Functions
4
+ extend Transproc::Registry
5
+
6
+ import Transproc::HashTransformations
7
+
8
+ def self.sequence(hash, prefix, into)
9
+ keys = hash.keys.select { |key| key.start_with?(prefix + '@') }
10
+
11
+ hash[into] = keys.map { |key| hash[key] }
12
+
13
+ hash
14
+ end
15
+
16
+ def self.deep_stringify_keys(hash)
17
+ hash.each_with_object({}) do |(key, value), output|
18
+ output[key.to_s] =
19
+ case value
20
+ when Hash
21
+ deep_stringify_keys(value)
22
+ when Array
23
+ value.map { |item|
24
+ item.is_a?(Hash) ? deep_stringify_keys(item) : item
25
+ }
26
+ else
27
+ value
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ class GameOptions < Openra::Struct
4
+ define do
5
+ attribute :starting_cash, IntegerOption.meta(from: 'startingcash')
6
+ attribute :cheats_enabled, BooleanOption.meta(from: 'cheats')
7
+ attribute :explored_map_enabled, BooleanOption.meta(from: 'explored')
8
+ attribute :fog_enabled, BooleanOption.meta(from: 'fog')
9
+ attribute :bounties_enabled, BooleanOption.meta(from: 'bounty', omittable: true)
10
+ attribute :conyard_undeploy_enabled, BooleanOption.meta(from: 'factundeploy', omittable: true)
11
+ attribute :crates_enabled, BooleanOption.meta(from: 'crates')
12
+ attribute :build_off_allies_enabled, BooleanOption.meta(from: 'allybuild')
13
+ attribute :restricted_build_radius_enabled, BooleanOption.meta(from: 'buildradius', omittable: true)
14
+ attribute :short_game_enabled, BooleanOption.meta(from: 'shortgame')
15
+ attribute :tech_level, StringOption.meta(from: 'techlevel')
16
+ attribute :game_speed, StringOption.meta(from: 'gamespeed')
17
+ attribute :starting_units, StringOption.meta(from: 'startingunits')
18
+ end
19
+
20
+ def bounties_enabled
21
+ attributes.fetch(:bounties_enabled, BooleanOption.new(
22
+ value: true,
23
+ preferred_value: true
24
+ ))
25
+ end
26
+
27
+ def conyard_undeploy_enabled
28
+ attributes.fetch(:conyard_undeploy_enabled, BooleanOption.new(
29
+ value: true,
30
+ preferred_value: true
31
+ ))
32
+ end
33
+
34
+ def restricted_build_radius_enabled
35
+ attributes.fetch(:restricted_build_radius_enabled, BooleanOption.new(
36
+ value: true,
37
+ preferred_value: true
38
+ ))
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ class GameOptions < Openra::Struct
4
+ class BooleanOption < Openra::Struct
5
+ define do
6
+ attribute :value, Types::Params::Bool.meta(from: 'Value')
7
+ attribute :preferred_value, Types::Params::Bool.meta(
8
+ from: 'PreferredValue'
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ class GameOptions < Openra::Struct
4
+ class IntegerOption < Openra::Struct
5
+ define do
6
+ attribute :value, Types::Params::Integer.meta(from: 'Value')
7
+ attribute :preferred_value, Types::Params::Integer.meta(
8
+ from: 'PreferredValue'
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ class GameOptions < Openra::Struct
4
+ class StringOption < Openra::Struct
5
+ define do
6
+ attribute :value, Types::Strict::String.meta(from: 'Value')
7
+ attribute :preferred_value, Types::Strict::String.meta(
8
+ from: 'PreferredValue'
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ class GlobalSettings < Openra::Struct
4
+ define do
5
+ attribute :server_name, Types::Strict::String.meta(from: 'ServerName')
6
+ attribute :map_hash, Types::Strict::String.meta(from: 'Map')
7
+ attribute :timestep, Types::Params::Integer.meta(from: 'Timestep')
8
+ attribute :order_latency, Types::Params::Integer.meta(from: 'OrderLatency')
9
+ attribute :random_seed, Types::Strict::String.meta(from: 'RandomSeed')
10
+ attribute :allow_spectators, Types::Params::Bool.meta(from: 'AllowSpectators')
11
+ attribute :allow_version_mismatch, Types::Params::Bool.meta(from: 'AllowVersionMismatch')
12
+ attribute :allow_singleplayer, Types::Params::Bool.meta(from: 'EnableSingleplayer')
13
+ attribute :game_id, Types::Strict::String.meta(from: 'GameUid')
14
+ attribute :game_options, GameOptions.meta(from: 'Options')
15
+ end
16
+
17
+ def frametime_multiplier
18
+ timestep * order_latency
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ class Metadata < Openra::Struct
4
+ preprocessor(preprocessor.with(root: 'Root'))
5
+
6
+ define do
7
+ attribute :mod, Types::Strict::String.meta(from: 'Mod')
8
+ attribute :version, Types::Strict::String.meta(from: 'Version')
9
+ attribute :map_hash, Types::Strict::String.meta(from: 'MapUid')
10
+ attribute :map_name, Types::Strict::String.meta(from: 'MapTitle')
11
+ attribute :start_time, Types::Timestamp.meta(from: 'StartTimeUtc')
12
+ attribute :end_time, Types::Timestamp.meta(from: 'EndTimeUtc')
13
+ attribute :players, Types::Strict::Array.of(Player).meta(sequence: 'Player')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ class Player < Openra::Struct
4
+ define do
5
+ attribute :index, Types::Strict::String.meta(from: 'ClientIndex')
6
+ attribute :name, Types::Strict::String.meta(from: 'Name')
7
+ attribute :is_human, Types::Params::Bool.meta(from: 'IsHuman')
8
+ attribute :is_bot, Types::Params::Bool.meta(from: 'IsBot')
9
+ attribute :faction_name, Types::Strict::String.meta(from: 'FactionName')
10
+ attribute :faction_id, Types::Strict::String.meta(from: 'FactionId')
11
+ attribute :color, Types::Strict::String.meta(from: 'Color')
12
+ attribute :team, Types::Strict::String.meta(from: 'Team')
13
+ attribute :spawn_point, Types::Strict::String.meta(from: 'SpawnPoint')
14
+ attribute :is_random_faction, Types::Params::Bool.meta(from: 'IsRandomFaction')
15
+ attribute :is_random_spawn, Types::Params::Bool.meta(from: 'IsRandomSpawnPoint')
16
+ attribute :outcome, Types::Strict::String.meta(from: 'Outcome')
17
+ attribute :outcome_time, Types::Timestamp.meta(from: 'OutcomeTimestampUtc')
18
+ end
19
+
20
+ def team
21
+ attributes[:team] == '0' ? nil : attributes[:team]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,67 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ class PreProcessor
4
+ AlreadyFinalizedError = Class.new(StandardError)
5
+ NotFinalizedError = Class.new(StandardError)
6
+
7
+ attr_reader :config
8
+
9
+ def initialize(**config)
10
+ @config = config
11
+ end
12
+
13
+ def call(input)
14
+ transformer.call(input)
15
+ end
16
+
17
+ def with(**new_config)
18
+ raise AlreadyFinalizedError, 'transformer already finalized' if finalized?
19
+ self.class.new(config.merge(new_config))
20
+ end
21
+
22
+ def finalize!
23
+ config = self.config
24
+
25
+ transformer_klass = Class.new(Transproc::Transformer[Functions])
26
+ transformer_klass.instance_eval do
27
+ unwrap(config[:root]) if config[:root]
28
+
29
+ if (schema = config[:schema])
30
+ schema.each_pair do |key, type|
31
+ if (sequence = type.meta[:sequence])
32
+ sequence(sequence, sequence)
33
+ rename_keys(sequence => key)
34
+ end
35
+ end
36
+
37
+ mapping = schema.each_with_object({}) do |(key, type), mapping|
38
+ mapping[type.meta[:from]] = key if type.meta[:from]
39
+ end
40
+
41
+ rename_keys(mapping)
42
+ end
43
+ end
44
+
45
+ @transformer = transformer_klass.new
46
+ @finalized = true
47
+ end
48
+
49
+ private
50
+
51
+ def transformer
52
+ if finalized?
53
+ @transformer
54
+ else
55
+ raise(
56
+ NotFinalizedError,
57
+ 'preprocessor must be finalized before being used'
58
+ )
59
+ end
60
+ end
61
+
62
+ def finalized?
63
+ @finalized == true
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,12 @@
1
+ module Openra
2
+ class Struct < Dry::Struct
3
+ class SyncInfo < Openra::Struct
4
+ define do
5
+ attribute :clients, Types::Strict::Array.of(Client).meta(sequence: 'Client')
6
+ attribute :global_settings, GlobalSettings.meta(
7
+ from: 'GlobalSettings'
8
+ )
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require 'dry-types'
2
+
3
+ module Openra
4
+ module Types
5
+ include Dry::Types.module
6
+
7
+ Timestamp = Constructor(Time) do |input|
8
+ ::DateTime.strptime(input, '%Y-%m-%d %H-%M-%S').to_time
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Openra
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -16,6 +16,9 @@ Gem::Specification.new do |spec|
16
16
  spec.require_paths = ['lib']
17
17
 
18
18
  spec.add_dependency 'bindata', '2.4.3'
19
+ spec.add_dependency 'transproc', '1.0.2'
20
+ spec.add_dependency 'dry-types', '0.13.2'
21
+ spec.add_dependency 'dry-struct', '0.5.0'
19
22
  spec.add_dependency 'hanami-cli', '0.2.0'
20
23
 
21
24
  spec.add_development_dependency 'bundler'
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: 0.3.0
4
+ version: 1.0.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-06-30 00:00:00.000000000 Z
11
+ date: 2018-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bindata
@@ -24,6 +24,48 @@ dependencies:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.4.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: transproc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-types
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.13.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.13.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: dry-struct
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.5.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.5.0
27
69
  - !ruby/object:Gem::Dependency
28
70
  name: hanami-cli
29
71
  requirement: !ruby/object:Gem::Requirement
@@ -81,12 +123,18 @@ files:
81
123
  - README.md
82
124
  - Rakefile
83
125
  - bin/openra
84
- - lib/big_integer.rb
126
+ - lib/bindata/big_integer.rb
127
+ - lib/bindata/pascal_string.rb
85
128
  - lib/openra-cli.rb
129
+ - lib/openra-replays.rb
130
+ - lib/openra-struct.rb
131
+ - lib/openra-types.rb
132
+ - lib/openra-yaml.rb
86
133
  - lib/openra.rb
87
134
  - lib/openra/cli.rb
88
135
  - lib/openra/cli/command_registry.rb
89
136
  - lib/openra/cli/commands/replay_data.rb
137
+ - lib/openra/cli/commands/replay_data/formatters.rb
90
138
  - lib/openra/cli/commands/version.rb
91
139
  - lib/openra/replays.rb
92
140
  - lib/openra/replays/file.rb
@@ -98,9 +146,21 @@ files:
98
146
  - lib/openra/replays/packet.rb
99
147
  - lib/openra/replays/packet_list.rb
100
148
  - lib/openra/replays/replay.rb
149
+ - lib/openra/struct.rb
150
+ - lib/openra/struct/client.rb
151
+ - lib/openra/struct/functions.rb
152
+ - lib/openra/struct/game_options.rb
153
+ - lib/openra/struct/game_options/boolean_option.rb
154
+ - lib/openra/struct/game_options/integer_option.rb
155
+ - lib/openra/struct/game_options/string_option.rb
156
+ - lib/openra/struct/global_settings.rb
157
+ - lib/openra/struct/metadata.rb
158
+ - lib/openra/struct/player.rb
159
+ - lib/openra/struct/pre_processor.rb
160
+ - lib/openra/struct/sync_info.rb
161
+ - lib/openra/types.rb
101
162
  - lib/openra/version.rb
102
163
  - lib/openra/yaml.rb
103
- - lib/pascal_string.rb
104
164
  - openra.gemspec
105
165
  homepage: https://github.com/AMHOL/openra-ruby
106
166
  licenses: