openra 0.3.0 → 1.0.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: 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: