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 +4 -4
- data/CHANGELOG.md +15 -1
- data/lib/{big_integer.rb → bindata/big_integer.rb} +0 -0
- data/lib/{pascal_string.rb → bindata/pascal_string.rb} +0 -0
- data/lib/openra-replays.rb +1 -0
- data/lib/openra-struct.rb +1 -0
- data/lib/openra-types.rb +1 -0
- data/lib/openra-yaml.rb +1 -0
- data/lib/openra.rb +1 -1
- data/lib/openra/cli.rb +8 -1
- data/lib/openra/cli/command_registry.rb +0 -3
- data/lib/openra/cli/commands/replay_data.rb +86 -136
- data/lib/openra/cli/commands/replay_data/formatters.rb +29 -0
- data/lib/openra/replays.rb +3 -2
- data/lib/openra/replays/replay.rb +44 -21
- data/lib/openra/struct.rb +49 -0
- data/lib/openra/struct/client.rb +26 -0
- data/lib/openra/struct/functions.rb +33 -0
- data/lib/openra/struct/game_options.rb +42 -0
- data/lib/openra/struct/game_options/boolean_option.rb +14 -0
- data/lib/openra/struct/game_options/integer_option.rb +14 -0
- data/lib/openra/struct/game_options/string_option.rb +14 -0
- data/lib/openra/struct/global_settings.rb +22 -0
- data/lib/openra/struct/metadata.rb +17 -0
- data/lib/openra/struct/player.rb +25 -0
- data/lib/openra/struct/pre_processor.rb +67 -0
- data/lib/openra/struct/sync_info.rb +12 -0
- data/lib/openra/types.rb +11 -0
- data/lib/openra/version.rb +1 -1
- data/openra.gemspec +3 -0
- metadata +64 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18eaa1cb9a89b397783943143d5038abf81eaa2a
|
4
|
+
data.tar.gz: 8d0656389b17e73063eb24973326921a68e3e845
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3889a2bdbb19f65e091e18bfc6168de0442c9c6c790c57024cb9a2e442b40754ee6234d139f83376e3e90168551da58a289e6fadbcd3e06b4312c393de9b4be
|
7
|
+
data.tar.gz: a99959ea027d0e554871bf05485df7794ba7dec96d235b778efea5fb13703764b6e7d4add86dc07225afb35315781a00ea9aaccbfbe56c5b98793eaa677e26d5
|
data/CHANGELOG.md
CHANGED
@@ -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...
|
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
|
|
File without changes
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'openra/replays'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'openra/struct'
|
data/lib/openra-types.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'openra/types'
|
data/lib/openra-yaml.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'openra/yaml
|
data/lib/openra.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
require 'openra/version'
|
2
|
-
require 'openra/replays'
|
2
|
+
require 'openra/replays' # Replays requires everything else
|
data/lib/openra/cli.rb
CHANGED
@@ -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,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
|
-
|
9
|
-
'
|
10
|
-
'
|
11
|
-
'
|
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:
|
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.
|
48
|
-
hash: replay.
|
24
|
+
name: utf8(replay.metadata.map_name),
|
25
|
+
hash: replay.metadata.map_hash
|
49
26
|
},
|
50
27
|
game: {
|
51
|
-
type:
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
87
|
+
client_hash = replay_data[:clients].find do |candidate|
|
128
88
|
candidate[:index] == order.client_index.to_s
|
129
89
|
end
|
130
90
|
|
131
|
-
|
132
|
-
|
133
|
-
|
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:
|
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:
|
160
|
-
name: client
|
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
|
170
|
-
name: client
|
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
|
-
|
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
|
data/lib/openra/replays.rb
CHANGED
@@ -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::
|
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
|
19
|
-
|
20
|
+
def players
|
21
|
+
metadata.players
|
20
22
|
end
|
21
23
|
|
22
|
-
def
|
23
|
-
|
24
|
+
def player(index)
|
25
|
+
players.find do |candidate|
|
26
|
+
candidate.index == index
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
|
-
def
|
27
|
-
|
30
|
+
def clients
|
31
|
+
sync_info.clients
|
28
32
|
end
|
29
33
|
|
30
|
-
def
|
31
|
-
|
34
|
+
def client(index)
|
35
|
+
clients.find do |candidate|
|
36
|
+
candidate.index == index
|
37
|
+
end
|
32
38
|
end
|
33
39
|
|
34
|
-
def
|
35
|
-
|
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
|
42
|
-
|
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
|
49
|
-
|
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
|
data/lib/openra/types.rb
ADDED
data/lib/openra/version.rb
CHANGED
data/openra.gemspec
CHANGED
@@ -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.
|
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-
|
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:
|