reinforce 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +56 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +35 -0
- data/Rakefile +12 -0
- data/generated/abilities.json +1 -0
- data/generated/ebps.json +1 -0
- data/generated/sbps.json +1 -0
- data/generated/upgrade.json +1 -0
- data/lib/reinforce/attributes/ability.rb +137 -0
- data/lib/reinforce/attributes/base.rb +63 -0
- data/lib/reinforce/attributes/collection.rb +155 -0
- data/lib/reinforce/attributes/entity.rb +85 -0
- data/lib/reinforce/attributes/squad.rb +52 -0
- data/lib/reinforce/attributes/upgrade.rb +41 -0
- data/lib/reinforce/command.rb +58 -0
- data/lib/reinforce/factory.rb +94 -0
- data/lib/reinforce/version.rb +5 -0
- data/lib/reinforce.rb +38 -0
- metadata +84 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reinforce
|
4
|
+
module Attributes
|
5
|
+
class Ability < Base
|
6
|
+
AUTOBUILDS = %w[
|
7
|
+
abilities/races/american/battlegroups/infantry/infantry_left_2a_medical_tent
|
8
|
+
].freeze
|
9
|
+
|
10
|
+
SPAWNERS = %w[
|
11
|
+
armored_support_flame_p3_ak
|
12
|
+
armored_support_command_p4_ak
|
13
|
+
italian_combined_arms_bersaglieri_ak
|
14
|
+
italian_combined_arms_semovente_ak
|
15
|
+
italian_combined_arms_m13_40_ak
|
16
|
+
italian_infantry_double_l640_ak
|
17
|
+
italian_infantry_guastatori_ak
|
18
|
+
italian_infantry_cannone_da_105_ak
|
19
|
+
infiltration_left_1_vampire_ht_goliath_ak
|
20
|
+
british_air_and_sea_left_2a_centaur_cs_uk
|
21
|
+
british_air_and_sea_right_1_commandos_uk
|
22
|
+
british_air_and_sea_right_2a_pack_howitzer_team_uk
|
23
|
+
british_air_and_sea_right_2b_commando_lmg_team_uk
|
24
|
+
british_armored_right_2_crusader_aa_uk
|
25
|
+
british_armored_left_2_churchill
|
26
|
+
british_armored_left_3b_churchill_black_prince_uk
|
27
|
+
artillery_gurkhas_uk
|
28
|
+
artillery_4_2_inch_heavy_mortar_uk
|
29
|
+
artillery_bl_5_5_heavy_artillery_uk
|
30
|
+
australian_defense_archer_tank_destroyer_call_in_uk
|
31
|
+
australian_defense_australian_light_infantry_uk
|
32
|
+
australian_defense_2pdr_at_gun_uk
|
33
|
+
airborne_right_1a_pathfinders_us
|
34
|
+
airborne_right_1b_paradrop_hmg_us
|
35
|
+
airborne_right_2_paratrooper_us
|
36
|
+
airborne_right_3_paradrop_at_gun_us
|
37
|
+
armored_left_2b_recovery_vehicle_us
|
38
|
+
armored_right_2a_scott_us
|
39
|
+
armored_right_3_easy_8_task_force_us
|
40
|
+
special_operations_left_1a_m29_weasal_us
|
41
|
+
special_operations_left_1b_m29_weasal_with_pack_howitzer
|
42
|
+
special_operations_left_3_whizbang_us
|
43
|
+
special_operations_right_2_devils_brigade_us
|
44
|
+
infantry_left_1_rifleman_convert_to_ranger_us
|
45
|
+
infantry_right_1a_artillery_observers_us
|
46
|
+
infantry_right_2_105mm_howitzer_us
|
47
|
+
breakthrough_right_3a_assault_group_ger
|
48
|
+
breakthrough_left_1b_truck_2_5_ger
|
49
|
+
breakthrough_left_2b_panzer_iv_cmd_ger
|
50
|
+
breakthrough_left_3_tiger_ger
|
51
|
+
luftwaffe_right_2_fallschirmjagers_ger
|
52
|
+
luftwaffe_left_1b_fallschirmpioneers_ger
|
53
|
+
luftwaffe_left_2b_combat_group_ger
|
54
|
+
luftwaffe_left_2a_weapon_drop_ger
|
55
|
+
luftwaffe_left_3_88mm_at_gun_ger
|
56
|
+
mechanized_right_2a_stug_assault_group_ger
|
57
|
+
mechanized_left_2b_8_rad_ger
|
58
|
+
mechanized_right_3_panther_ger
|
59
|
+
mechanized_left_3a_wespe_ger
|
60
|
+
coastal_left_1_coastal_reserve_ger
|
61
|
+
coastal_artillery_officer_ger
|
62
|
+
coastal_obice_ger
|
63
|
+
halftrack_deployment_panzerjager_inf_1_ak
|
64
|
+
halftrack_deployment_assault_grenadier_1_ak
|
65
|
+
halftrack_deployment_at_gun_1_ak
|
66
|
+
halftrack_deployment_leig_1_ak
|
67
|
+
halftrack_deployment_piv_tank_hunter_group_ak
|
68
|
+
halftrack_deployment_stug_assault_group_ak
|
69
|
+
halftrack_deployment_panzer_iii_assault_group_ak
|
70
|
+
halftrack_deployment_tiger_ak
|
71
|
+
].to_set
|
72
|
+
|
73
|
+
FILENAME = 'abilities.json'
|
74
|
+
|
75
|
+
attr_reader :builds
|
76
|
+
|
77
|
+
def initialize(path:, pbgid:, locstring:, icon_name:, builds:)
|
78
|
+
super(path:, pbgid:, locstring:, icon_name:)
|
79
|
+
@builds = builds
|
80
|
+
end
|
81
|
+
|
82
|
+
class << self
|
83
|
+
private
|
84
|
+
|
85
|
+
def parse(data)
|
86
|
+
parse_subtree(data, %w[abilities])
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_subtree(data, key)
|
90
|
+
data.flat_map do |k, tree|
|
91
|
+
new_key = key + [k]
|
92
|
+
if tree.key?('ability_bag')
|
93
|
+
parse_ability_bag(tree, new_key)
|
94
|
+
else
|
95
|
+
parse_subtree(tree, new_key)
|
96
|
+
end
|
97
|
+
end.compact
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse_ability_bag(data, path)
|
101
|
+
locstring = data.dig('ability_bag', 'ui_info', 'screen_name', 'locstring', 'value')
|
102
|
+
|
103
|
+
return if locstring.nil? || locstring == '0'
|
104
|
+
|
105
|
+
icon_name = data.dig('ability_bag', 'ui_info', 'icon_name')
|
106
|
+
builds = data.dig('ability_bag', 'cursor_ghost_ebp', 'instance_reference')
|
107
|
+
|
108
|
+
new(locstring:,
|
109
|
+
icon_name:,
|
110
|
+
builds: builds == '' ? nil : builds,
|
111
|
+
path:,
|
112
|
+
pbgid: data['pbgid'])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def autobuild?
|
117
|
+
@path.include?('auto_build') || @path.include?('autobuild') || AUTOBUILDS.include?(path)
|
118
|
+
end
|
119
|
+
|
120
|
+
def production_building?
|
121
|
+
%r{ebps/races/.+/buildings/production/.+}.match?(@builds)
|
122
|
+
end
|
123
|
+
|
124
|
+
def spawner?
|
125
|
+
SPAWNERS.include?(@path.last)
|
126
|
+
end
|
127
|
+
|
128
|
+
def ==(other)
|
129
|
+
super && @builds == other.builds
|
130
|
+
end
|
131
|
+
|
132
|
+
def as_json(_options)
|
133
|
+
super.merge(builds:)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Reinforce
|
6
|
+
module Attributes
|
7
|
+
class Base
|
8
|
+
attr_reader :locstring, :icon_name
|
9
|
+
|
10
|
+
def initialize(path:, pbgid:, locstring:, icon_name:)
|
11
|
+
@path = path
|
12
|
+
@pbgid = pbgid
|
13
|
+
@locstring = locstring
|
14
|
+
@icon_name = icon_name
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def load_from_file(path)
|
19
|
+
File.open(path) do |file|
|
20
|
+
data = JSON.parse(file.read)
|
21
|
+
parse(data)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse(_data)
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def path
|
33
|
+
@path.join('/')
|
34
|
+
end
|
35
|
+
|
36
|
+
def pbgid
|
37
|
+
@pbgid.to_i
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(other)
|
41
|
+
return false unless other.is_a?(self.class)
|
42
|
+
|
43
|
+
path == other.path &&
|
44
|
+
pbgid == other.pbgid &&
|
45
|
+
locstring == other.locstring &&
|
46
|
+
icon_name == other.icon_name
|
47
|
+
end
|
48
|
+
|
49
|
+
def as_json(_options)
|
50
|
+
{
|
51
|
+
path: @path,
|
52
|
+
pbgid:,
|
53
|
+
locstring:,
|
54
|
+
icon_name:
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_json(*options)
|
59
|
+
as_json(*options).to_json(*options)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reinforce
|
4
|
+
module Attributes
|
5
|
+
class Collection
|
6
|
+
LATEST_BUILD = -1
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@pbgid_keyed = {}
|
10
|
+
@path_keyed = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def generate_for(klass, pretty: false)
|
15
|
+
collection = load_from_data(klass)
|
16
|
+
|
17
|
+
collection.rehash_all
|
18
|
+
|
19
|
+
generation_path = File.join(Reinforce.root, 'generated', klass::FILENAME)
|
20
|
+
json = pretty ? JSON.pretty_generate(collection) : JSON.generate(collection)
|
21
|
+
File.write(generation_path, json)
|
22
|
+
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def instance
|
27
|
+
return @instance if defined?(@instance)
|
28
|
+
|
29
|
+
@instance = new
|
30
|
+
|
31
|
+
[Ability, Entity, Squad, Upgrade].each do |klass|
|
32
|
+
generated_path = File.join(Reinforce.root, 'generated', klass::FILENAME)
|
33
|
+
File.open(generated_path) do |file|
|
34
|
+
data = JSON.parse(file.read)
|
35
|
+
data.each do |build, attributes|
|
36
|
+
@instance.populate(build, attributes.map { |a| klass.new(**a.transform_keys(&:to_sym)) }, rehash: false)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@instance.rehash_all
|
42
|
+
@instance
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_for(klass)
|
46
|
+
collection = new
|
47
|
+
generated_path = File.join(Reinforce.root, 'generated', klass::FILENAME)
|
48
|
+
File.open(generated_path) do |file|
|
49
|
+
data = JSON.parse(file.read)
|
50
|
+
data.each do |build, attributes|
|
51
|
+
collection.populate(build, attributes.map { |a| klass.new(**a.transform_keys(&:to_sym)) }, rehash: false)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
collection.rehash_all
|
56
|
+
collection
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def load_from_data(klass)
|
62
|
+
collection = new
|
63
|
+
data_path = File.join(Reinforce.root, 'data')
|
64
|
+
|
65
|
+
Dir.chdir(data_path) do
|
66
|
+
Dir.glob('*').select { |f| File.directory?(f) }.each do |dir|
|
67
|
+
file_path = File.join(dir, klass::FILENAME)
|
68
|
+
next unless File.exist?(file_path)
|
69
|
+
|
70
|
+
data = klass.load_from_file(file_path)
|
71
|
+
collection.populate(dir, data, rehash: false)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
collection
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_by_pbgid(pbgid, build: LATEST_BUILD)
|
80
|
+
return nil if build.nil?
|
81
|
+
|
82
|
+
build = last_build if build == LATEST_BUILD
|
83
|
+
@pbgid_keyed.dig(build, pbgid) || get_by_pbgid(pbgid, build: previous_build_for(build))
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_by_path(path, build: LATEST_BUILD)
|
87
|
+
return nil if build.nil?
|
88
|
+
|
89
|
+
build = last_build if build == LATEST_BUILD
|
90
|
+
@path_keyed.dig(build, path) || get_by_path(path, build: previous_build_for(build))
|
91
|
+
end
|
92
|
+
|
93
|
+
def populate(build, data, rehash: true)
|
94
|
+
populate_pbgid_hash(build, data)
|
95
|
+
populate_path_hash(build, data)
|
96
|
+
|
97
|
+
rehash_all if rehash
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
def rehash_all
|
102
|
+
rehash(@pbgid_keyed)
|
103
|
+
rehash(@path_keyed)
|
104
|
+
end
|
105
|
+
|
106
|
+
def as_json(_options)
|
107
|
+
@pbgid_keyed.transform_values(&:values)
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_json(*options)
|
111
|
+
as_json(*options).to_json(*options)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def populate_pbgid_hash(build, data)
|
117
|
+
@pbgid_keyed[build.to_i] ||= {}
|
118
|
+
@pbgid_keyed[build.to_i] = @pbgid_keyed[build.to_i].merge(data.to_h { |o| [o.pbgid, o] })
|
119
|
+
end
|
120
|
+
|
121
|
+
def populate_path_hash(build, data)
|
122
|
+
@path_keyed[build.to_i] ||= {}
|
123
|
+
@path_keyed[build.to_i] = @path_keyed[build.to_i].merge(data.to_h { |o| [o.path, o] })
|
124
|
+
end
|
125
|
+
|
126
|
+
def first_build
|
127
|
+
@pbgid_keyed.keys.min
|
128
|
+
end
|
129
|
+
|
130
|
+
def last_build
|
131
|
+
@pbgid_keyed.keys.max
|
132
|
+
end
|
133
|
+
|
134
|
+
def previous_build_for(build)
|
135
|
+
@pbgid_keyed.keys.sort.reverse.find { |b| b < build }
|
136
|
+
end
|
137
|
+
|
138
|
+
def rehash(hash)
|
139
|
+
previous = nil
|
140
|
+
|
141
|
+
hash.keys.sort.each do |build|
|
142
|
+
next previous = build if previous.nil?
|
143
|
+
|
144
|
+
hash[build].each do |key, value|
|
145
|
+
hash[build].delete(key) if value == hash[previous][key]
|
146
|
+
end
|
147
|
+
|
148
|
+
hash[build] = hash[previous].merge(hash[build])
|
149
|
+
|
150
|
+
previous = build
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reinforce
|
4
|
+
module Attributes
|
5
|
+
class Entity < Base
|
6
|
+
FILENAME = 'ebps.json'
|
7
|
+
|
8
|
+
attr_reader :spawns, :upgrades
|
9
|
+
|
10
|
+
def initialize(path:, pbgid:, locstring:, icon_name:, spawns:, upgrades:)
|
11
|
+
super(path:, pbgid:, locstring:, icon_name:)
|
12
|
+
@spawns = spawns
|
13
|
+
@upgrades = upgrades
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
private
|
18
|
+
|
19
|
+
def parse(data)
|
20
|
+
parse_subtree(data, %w[ebps])
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_subtree(data, key)
|
24
|
+
data.flat_map do |k, tree|
|
25
|
+
new_key = key + [k]
|
26
|
+
if tree.key?('extensions')
|
27
|
+
parse_extensions(tree, new_key)
|
28
|
+
else
|
29
|
+
parse_subtree(tree, new_key)
|
30
|
+
end
|
31
|
+
end.compact
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_extensions(data, path)
|
35
|
+
locstring = parse_locstring(data)
|
36
|
+
|
37
|
+
return if locstring.nil? || locstring == '0'
|
38
|
+
|
39
|
+
new(locstring:,
|
40
|
+
icon_name: parse_icon_name(data),
|
41
|
+
path:,
|
42
|
+
spawns: parse_spawns(data),
|
43
|
+
upgrades: parse_upgrades(data),
|
44
|
+
pbgid: data['pbgid'])
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_locstring(data)
|
48
|
+
ext_data = data['extensions'].find { |ext| ext['exts'].key?('screen_name') }
|
49
|
+
ext_data&.dig('exts', 'screen_name', 'locstring', 'value')
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_icon_name(data)
|
53
|
+
ext_data = data['extensions'].find { |ext| ext['exts'].key?('screen_name') }
|
54
|
+
ext_data.dig('exts', 'icon_name')
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_spawns(data)
|
58
|
+
ext_spawns = data['extensions'].find { |ext| ext['exts'].key?('spawn_items') }
|
59
|
+
(ext_spawns&.dig('exts', 'spawn_items') || []).map do |item|
|
60
|
+
item.dig('spawn_item', 'squad', 'instance_reference')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_upgrades(data)
|
65
|
+
ext_upgrades = data['extensions'].find { |ext| ext['exts'].key?('standard_upgrades') }
|
66
|
+
(ext_upgrades&.dig('exts', 'standard_upgrades') || []).map do |item|
|
67
|
+
item.dig('upgrade', 'instance_reference')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def produces?(path)
|
73
|
+
@spawns.include?(path) || @upgrades.include?(path)
|
74
|
+
end
|
75
|
+
|
76
|
+
def ==(other)
|
77
|
+
super && @spawns == other.spawns && @upgrades == other.upgrades
|
78
|
+
end
|
79
|
+
|
80
|
+
def as_json(_options)
|
81
|
+
super.merge(spawns:, upgrades:)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reinforce
|
4
|
+
module Attributes
|
5
|
+
class Squad < Base
|
6
|
+
FILENAME = 'sbps.json'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
private
|
10
|
+
|
11
|
+
def parse(data)
|
12
|
+
parse_subtree(data, %w[sbps])
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_subtree(data, key)
|
16
|
+
data.flat_map do |k, tree|
|
17
|
+
new_key = key + [k]
|
18
|
+
if tree.key?('extensions')
|
19
|
+
parse_extensions(tree, new_key)
|
20
|
+
else
|
21
|
+
parse_subtree(tree, new_key)
|
22
|
+
end
|
23
|
+
end.compact
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_extensions(data, path)
|
27
|
+
squad_data = squad_data_from(data)
|
28
|
+
locstring = squad_data&.dig('race_data', 'info', 'screen_name', 'locstring', 'value')
|
29
|
+
|
30
|
+
return if locstring.nil? || locstring == '0'
|
31
|
+
|
32
|
+
icon_name = squad_data.dig('race_data', 'info', 'icon_name')
|
33
|
+
|
34
|
+
new(locstring:,
|
35
|
+
icon_name:,
|
36
|
+
path:,
|
37
|
+
pbgid: data['pbgid'])
|
38
|
+
end
|
39
|
+
|
40
|
+
def squad_data_from(data)
|
41
|
+
race_ext = data['extensions'].find do |ext|
|
42
|
+
ext['squadexts'].key?('race_list')
|
43
|
+
end
|
44
|
+
|
45
|
+
race_ext&.dig('squadexts', 'race_list')&.find do |entry|
|
46
|
+
!entry.dig('race_data', 'info', 'screen_name').nil?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reinforce
|
4
|
+
module Attributes
|
5
|
+
class Upgrade < Base
|
6
|
+
FILENAME = 'upgrade.json'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
private
|
10
|
+
|
11
|
+
def parse(data)
|
12
|
+
parse_subtree(data, %w[upgrade])
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_subtree(data, key)
|
16
|
+
data.flat_map do |k, tree|
|
17
|
+
new_key = key + [k]
|
18
|
+
if tree.key?('upgrade_bag')
|
19
|
+
parse_upgrade_bag(tree, new_key)
|
20
|
+
else
|
21
|
+
parse_subtree(tree, new_key)
|
22
|
+
end
|
23
|
+
end.compact
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_upgrade_bag(data, path)
|
27
|
+
locstring = data.dig('upgrade_bag', 'ui_info', 'screen_name', 'locstring', 'value')
|
28
|
+
|
29
|
+
return if locstring.nil? || locstring == '0'
|
30
|
+
|
31
|
+
icon_name = data.dig('upgrade_bag', 'ui_info', 'icon_name')
|
32
|
+
|
33
|
+
new(locstring:,
|
34
|
+
icon_name:,
|
35
|
+
path:,
|
36
|
+
pbgid: data['pbgid'])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reinforce
|
4
|
+
class Command
|
5
|
+
attr_reader :action_type, :tick, :pbgid, :source, :index, :details
|
6
|
+
|
7
|
+
# rubocop:disable Metrics/AbcSize
|
8
|
+
def initialize(command_hash, build_number)
|
9
|
+
@action_type = command_hash.keys.first
|
10
|
+
@tick = command_hash.values.first[:tick]
|
11
|
+
@pbgid = command_hash.values.first[:pbgid]
|
12
|
+
@source = command_hash.values.first[:source_identifier]
|
13
|
+
@index = command_hash.values.first[:queue_index]
|
14
|
+
@details = Attributes::Collection.instance.get_by_pbgid(@pbgid, build: build_number)
|
15
|
+
@cancelled = false
|
16
|
+
@suspect_from_tick = nil
|
17
|
+
end
|
18
|
+
# rubocop:enable Metrics/AbcSize
|
19
|
+
|
20
|
+
def cancelled?
|
21
|
+
@cancelled
|
22
|
+
end
|
23
|
+
|
24
|
+
def cancel
|
25
|
+
@cancelled = true
|
26
|
+
end
|
27
|
+
|
28
|
+
def suspect?
|
29
|
+
!@suspect_from_tick.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
def mark_suspect(tick)
|
33
|
+
@suspect_from_tick = tick
|
34
|
+
end
|
35
|
+
|
36
|
+
def mark_legit
|
37
|
+
@suspect_from_tick = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def suspect_since
|
41
|
+
@suspect_from_tick
|
42
|
+
end
|
43
|
+
|
44
|
+
def as_json(_options)
|
45
|
+
{
|
46
|
+
action: action_type,
|
47
|
+
tick:,
|
48
|
+
pbgid:,
|
49
|
+
locstring: @details.locstring,
|
50
|
+
icon_name: @details.icon_name
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_json(*options)
|
55
|
+
as_json(*options).to_json(*options)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reinforce
|
4
|
+
class Factory
|
5
|
+
BUILDING_COMMANDS = %w[UseAbility].freeze
|
6
|
+
PRODUCTION_COMMANDS = %w[BuildSquad BuildGlobalUpgrade].freeze
|
7
|
+
BATTLEGROUP_COMMANDS = %w[SelectBattlegroup SelectBattlegroupAbility UseBattlegroupAbility].freeze
|
8
|
+
CANCEL_COMMANDS = %w[CancelConstruction CancelProduction].freeze
|
9
|
+
ALL_COMMANDS = BUILDING_COMMANDS + PRODUCTION_COMMANDS + BATTLEGROUP_COMMANDS + CANCEL_COMMANDS
|
10
|
+
|
11
|
+
def initialize(player, build_number)
|
12
|
+
@player = player.to_h
|
13
|
+
@build_number = build_number
|
14
|
+
@buildings = []
|
15
|
+
@productions = {}
|
16
|
+
@battlegroup = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def build(with_cancellations: false)
|
20
|
+
commands.each { |c| classify_command(c) }
|
21
|
+
result = consolidate
|
22
|
+
result = rectify_suspects(result)
|
23
|
+
result = result.reject(&:cancelled?) unless with_cancellations
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def commands
|
30
|
+
@commands ||= @player[:commands].filter { |c| ALL_COMMANDS.include?(c.keys.first) }
|
31
|
+
.map { |c| Command.new(c, @build_number) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def classify_command(command)
|
35
|
+
if BUILDING_COMMANDS.include?(command.action_type)
|
36
|
+
classify_building_command(command)
|
37
|
+
elsif PRODUCTION_COMMANDS.include?(command.action_type)
|
38
|
+
classify_production_command(command)
|
39
|
+
elsif BATTLEGROUP_COMMANDS.include?(command.action_type)
|
40
|
+
classify_battlegroup_command(command)
|
41
|
+
elsif CANCEL_COMMANDS.include?(command.action_type)
|
42
|
+
process_cancellation(command)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def classify_building_command(command)
|
47
|
+
@buildings << command if command.details.autobuild?
|
48
|
+
end
|
49
|
+
|
50
|
+
def classify_production_command(command)
|
51
|
+
@productions[command.source] ||= []
|
52
|
+
@productions[command.source] << command
|
53
|
+
end
|
54
|
+
|
55
|
+
def classify_battlegroup_command(command)
|
56
|
+
if command.details.respond_to?(:autobuild?) && command.details.autobuild?
|
57
|
+
@buildings << command
|
58
|
+
else
|
59
|
+
@battlegroup << command
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def process_cancellation(command)
|
64
|
+
if command.action_type == 'CancelConstruction'
|
65
|
+
@buildings.reject(&:suspect?).each { |building| building.mark_suspect(command.tick) }
|
66
|
+
else
|
67
|
+
@productions[command.source][command.index - 1].cancel
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def consolidate
|
72
|
+
build = @buildings + @battlegroup + @productions.values.flatten
|
73
|
+
build.sort_by(&:tick)
|
74
|
+
end
|
75
|
+
|
76
|
+
# rubocop:disable Metrics/AbcSize
|
77
|
+
def rectify_suspects(commands)
|
78
|
+
commands.each_with_index do |command, idx|
|
79
|
+
next unless command.suspect?
|
80
|
+
|
81
|
+
building_details = Attributes::Collection.instance.get_by_path(command.details.builds, build: @build_number)
|
82
|
+
remaining = commands[(idx + 1)..]
|
83
|
+
relevant = remaining.take_while { |c| c.pbgid != command.pbgid }
|
84
|
+
|
85
|
+
used = relevant.any? do |c|
|
86
|
+
building_details.produces?(c.details.path)
|
87
|
+
end
|
88
|
+
|
89
|
+
command.mark_legit if used
|
90
|
+
end
|
91
|
+
end
|
92
|
+
# rubocop:enable Metrics/AbcSize
|
93
|
+
end
|
94
|
+
end
|