sc2ai 0.0.0.pre → 0.0.2
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 +4 -4
- data/data/data.json +1 -0
- data/data/data_readable.json +22842 -0
- data/data/sc2ai/protocol/common.proto +59 -0
- data/data/sc2ai/protocol/data.proto +120 -0
- data/data/sc2ai/protocol/debug.proto +127 -0
- data/data/sc2ai/protocol/error.proto +221 -0
- data/data/sc2ai/protocol/query.proto +55 -0
- data/data/sc2ai/protocol/raw.proto +202 -0
- data/data/sc2ai/protocol/sc2api.proto +718 -0
- data/data/sc2ai/protocol/score.proto +108 -0
- data/data/sc2ai/protocol/spatial.proto +115 -0
- data/data/sc2ai/protocol/ui.proto +145 -0
- data/data/setup/setup.SC2Map +0 -0
- data/data/setup/setup.SC2Replay +0 -0
- data/data/stableid.json +35730 -0
- data/data/versions.json +554 -0
- data/exe/sc2ai +35 -0
- data/lib/docker_build/Dockerfile.ruby +74 -0
- data/lib/docker_build/docker-compose-base-image.yml +10 -0
- data/lib/docker_build/docker-compose-ladderzip.yml +9 -0
- data/lib/sc2ai/api/ability_id.rb +1644 -0
- data/lib/sc2ai/api/buff_id.rb +306 -0
- data/lib/sc2ai/api/data.rb +101 -0
- data/lib/sc2ai/api/effect_id.rb +20 -0
- data/lib/sc2ai/api/tech_tree.rb +83 -0
- data/lib/sc2ai/api/tech_tree_data.rb +2338 -0
- data/lib/sc2ai/api/unit_type_id.rb +2022 -0
- data/lib/sc2ai/api/upgrade_id.rb +310 -0
- data/lib/sc2ai/cli/cli.rb +175 -0
- data/lib/sc2ai/cli/ladderzip.rb +154 -0
- data/lib/sc2ai/cli/new.rb +88 -0
- data/lib/sc2ai/configuration.rb +145 -0
- data/lib/sc2ai/connection/connection_listener.rb +30 -0
- data/lib/sc2ai/connection/requests.rb +417 -0
- data/lib/sc2ai/connection/status_listener.rb +15 -0
- data/lib/sc2ai/connection.rb +146 -0
- data/lib/sc2ai/local_play/client/configurable_options.rb +115 -0
- data/lib/sc2ai/local_play/client.rb +159 -0
- data/lib/sc2ai/local_play/client_manager.rb +70 -0
- data/lib/sc2ai/local_play/map_file.rb +48 -0
- data/lib/sc2ai/local_play/match.rb +184 -0
- data/lib/sc2ai/overrides/array.rb +14 -0
- data/lib/sc2ai/overrides/async/process/child.rb +31 -0
- data/lib/sc2ai/overrides/kernel.rb +33 -0
- data/lib/sc2ai/paths.rb +294 -0
- data/lib/sc2ai/player/actions.rb +386 -0
- data/lib/sc2ai/player/debug.rb +224 -0
- data/lib/sc2ai/player/game_state.rb +131 -0
- data/lib/sc2ai/player/geometry.rb +766 -0
- data/lib/sc2ai/player/previous_state.rb +49 -0
- data/lib/sc2ai/player/units.rb +337 -0
- data/lib/sc2ai/player.rb +661 -0
- data/lib/sc2ai/ports.rb +152 -0
- data/lib/sc2ai/protocol/_meta_documentation.rb +39 -0
- data/lib/sc2ai/protocol/common_pb.rb +43 -0
- data/lib/sc2ai/protocol/data_pb.rb +47 -0
- data/lib/sc2ai/protocol/debug_pb.rb +56 -0
- data/lib/sc2ai/protocol/error_pb.rb +36 -0
- data/lib/sc2ai/protocol/extensions/color.rb +20 -0
- data/lib/sc2ai/protocol/extensions/point.rb +23 -0
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +26 -0
- data/lib/sc2ai/protocol/extensions/position.rb +202 -0
- data/lib/sc2ai/protocol/extensions/power_source.rb +19 -0
- data/lib/sc2ai/protocol/extensions/unit.rb +489 -0
- data/lib/sc2ai/protocol/query_pb.rb +47 -0
- data/lib/sc2ai/protocol/raw_pb.rb +57 -0
- data/lib/sc2ai/protocol/sc2api_pb.rb +130 -0
- data/lib/sc2ai/protocol/score_pb.rb +40 -0
- data/lib/sc2ai/protocol/spatial_pb.rb +48 -0
- data/lib/sc2ai/protocol/ui_pb.rb +56 -0
- data/lib/sc2ai/unit_group/action_ext.rb +74 -0
- data/lib/sc2ai/unit_group/filter_ext.rb +379 -0
- data/lib/sc2ai/unit_group.rb +277 -0
- data/lib/sc2ai/version.rb +2 -1
- data/lib/sc2ai.rb +93 -2
- data/lib/templates/ladderzip/bin/ladder.tt +23 -0
- data/lib/templates/new/.ladderignore +20 -0
- data/lib/templates/new/Gemfile.tt +7 -0
- data/lib/templates/new/api/common.proto +59 -0
- data/lib/templates/new/api/data.proto +120 -0
- data/lib/templates/new/api/debug.proto +127 -0
- data/lib/templates/new/api/error.proto +221 -0
- data/lib/templates/new/api/query.proto +55 -0
- data/lib/templates/new/api/raw.proto +202 -0
- data/lib/templates/new/api/sc2api.proto +718 -0
- data/lib/templates/new/api/score.proto +108 -0
- data/lib/templates/new/api/spatial.proto +115 -0
- data/lib/templates/new/api/ui.proto +145 -0
- data/lib/templates/new/boot.rb.tt +6 -0
- data/lib/templates/new/my_bot.rb.tt +23 -0
- data/lib/templates/new/run_example_match.rb.tt +14 -0
- data/sc2ai.gemspec +80 -0
- metadata +344 -13
@@ -0,0 +1,379 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sc2ai/unit_group"
|
4
|
+
require "kdtree"
|
5
|
+
|
6
|
+
module Sc2
|
7
|
+
# Manage virtual control groups of units, similar to Hash or Array.
|
8
|
+
class UnitGroup
|
9
|
+
TYPE_WORKER = [Api::UnitTypeId::SCV, Api::UnitTypeId::MULE, Api::UnitTypeId::DRONE, Api::UnitTypeId::DRONEBURROWED, Api::UnitTypeId::PROBE].freeze
|
10
|
+
TYPE_GAS_STRUCTURE = [
|
11
|
+
Api::UnitTypeId::REFINERY,
|
12
|
+
Api::UnitTypeId::REFINERYRICH,
|
13
|
+
Api::UnitTypeId::ASSIMILATOR,
|
14
|
+
Api::UnitTypeId::ASSIMILATORRICH,
|
15
|
+
Api::UnitTypeId::EXTRACTOR,
|
16
|
+
Api::UnitTypeId::EXTRACTORRICH
|
17
|
+
].freeze
|
18
|
+
TYPE_MINERAL = [
|
19
|
+
Api::UnitTypeId::MINERALCRYSTAL,
|
20
|
+
Api::UnitTypeId::RICHMINERALFIELD,
|
21
|
+
Api::UnitTypeId::RICHMINERALFIELD750,
|
22
|
+
Api::UnitTypeId::MINERALFIELD,
|
23
|
+
Api::UnitTypeId::MINERALFIELD450,
|
24
|
+
Api::UnitTypeId::MINERALFIELD750,
|
25
|
+
Api::UnitTypeId::LABMINERALFIELD,
|
26
|
+
Api::UnitTypeId::LABMINERALFIELD750,
|
27
|
+
Api::UnitTypeId::PURIFIERRICHMINERALFIELD,
|
28
|
+
Api::UnitTypeId::PURIFIERRICHMINERALFIELD750,
|
29
|
+
Api::UnitTypeId::PURIFIERMINERALFIELD,
|
30
|
+
Api::UnitTypeId::PURIFIERMINERALFIELD750,
|
31
|
+
Api::UnitTypeId::BATTLESTATIONMINERALFIELD,
|
32
|
+
Api::UnitTypeId::BATTLESTATIONMINERALFIELD750,
|
33
|
+
Api::UnitTypeId::MINERALFIELDOPAQUE,
|
34
|
+
Api::UnitTypeId::MINERALFIELDOPAQUE900
|
35
|
+
].freeze
|
36
|
+
TYPE_GEYSER = [
|
37
|
+
Api::UnitTypeId::VESPENEGEYSER,
|
38
|
+
Api::UnitTypeId::SPACEPLATFORMGEYSER,
|
39
|
+
Api::UnitTypeId::RICHVESPENEGEYSER,
|
40
|
+
Api::UnitTypeId::PROTOSSVESPENEGEYSER,
|
41
|
+
Api::UnitTypeId::PURIFIERVESPENEGEYSER,
|
42
|
+
Api::UnitTypeId::SHAKURASVESPENEGEYSER
|
43
|
+
].freeze
|
44
|
+
TYPE_REJECT_DEBRIS = ((TYPE_MINERAL + TYPE_GEYSER) << Api::UnitTypeId::XELNAGATOWER).freeze
|
45
|
+
TYPE_TECHLAB = [
|
46
|
+
Api::UnitTypeId::TECHLAB,
|
47
|
+
Api::UnitTypeId::BARRACKSTECHLAB,
|
48
|
+
Api::UnitTypeId::FACTORYTECHLAB,
|
49
|
+
Api::UnitTypeId::STARPORTTECHLAB
|
50
|
+
].freeze
|
51
|
+
TYPE_REACTOR = [
|
52
|
+
Api::UnitTypeId::REACTOR,
|
53
|
+
Api::UnitTypeId::BARRACKSREACTOR,
|
54
|
+
Api::UnitTypeId::FACTORYREACTOR,
|
55
|
+
Api::UnitTypeId::STARPORTREACTOR
|
56
|
+
].freeze
|
57
|
+
TYPE_BASES = [
|
58
|
+
Api::UnitTypeId::COMMANDCENTER, Api::UnitTypeId::COMMANDCENTERFLYING,
|
59
|
+
Api::UnitTypeId::ORBITALCOMMAND, Api::UnitTypeId::ORBITALCOMMANDFLYING,
|
60
|
+
Api::UnitTypeId::PLANETARYFORTRESS,
|
61
|
+
Api::UnitTypeId::HATCHERY, Api::UnitTypeId::HIVE, Api::UnitTypeId::LAIR,
|
62
|
+
Api::UnitTypeId::NEXUS
|
63
|
+
].freeze
|
64
|
+
# Returns a new UnitGroup containing all units matching unit type id(s)
|
65
|
+
# Multiple values work as an "OR" filter
|
66
|
+
# @example
|
67
|
+
# # Single
|
68
|
+
# ug.select_type(Api::UnitTypeId::MARINE) #=> <UnitGroup: ...>
|
69
|
+
# # Multiple - select space-men
|
70
|
+
# ug.select_type([Api::UnitTypeId::MARINE, Api::UnitTypeId::REAPER]) #=> <UnitGroup: ...>
|
71
|
+
# @param unit_type_ids [Integer, Array<Integer>] one or an array of unit Api::UnitTypeId
|
72
|
+
# @return [UnitGroup]
|
73
|
+
def select_type(unit_type_ids)
|
74
|
+
cached("#{__method__}:#{unit_type_ids.hash}") do
|
75
|
+
if unit_type_ids.is_a? Array
|
76
|
+
select { |unit| unit_type_ids.include?(unit.unit_type) }
|
77
|
+
else
|
78
|
+
select { |unit| unit_type_ids == unit.unit_type }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns a new UnitGroup excluding all units matching unit type id(s)
|
84
|
+
# @example
|
85
|
+
# # Single
|
86
|
+
# ug.reject_type(Api::UnitTypeId::SCV) #=> <UnitGroup: ...>
|
87
|
+
# # Multiple - reject immovable army
|
88
|
+
# ug.reject_type([Api::UnitTypeId::SIEGETANKSIEGED, Api::UnitTypeId::WIDOWMINEBURROWED]) #=> <UnitGroup: ...>
|
89
|
+
# @param unit_type_ids [Integer, Array<Integer>] one or an array of unit Api::UnitTypeId
|
90
|
+
# @return [UnitGroup]
|
91
|
+
def reject_type(unit_type_ids)
|
92
|
+
cached("#{__method__}:#{unit_type_ids.hash}") do
|
93
|
+
if unit_type_ids.is_a? Array
|
94
|
+
reject { |unit| unit_type_ids.include?(unit.unit_type) }
|
95
|
+
else
|
96
|
+
reject { |unit| unit_type_ids == unit.unit_type }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Creates a negative selector, which will perform the opposite on the current scope
|
102
|
+
# for it's next select_type/reject_type call.
|
103
|
+
# @example
|
104
|
+
# structures.not.creep_tumors #=> all structures
|
105
|
+
# structures.not.pylons #=>
|
106
|
+
# units.not.workers # equivalent of units.army, but works too
|
107
|
+
# @return [Sc2::UnitGroupNotSelector]
|
108
|
+
def not
|
109
|
+
UnitGroupNotSelector.new(self)
|
110
|
+
end
|
111
|
+
|
112
|
+
# @private
|
113
|
+
# Negative selector allowing unit group "ug.not." filter
|
114
|
+
class UnitGroupNotSelector < UnitGroup
|
115
|
+
attr_accessor :parent
|
116
|
+
|
117
|
+
def initialize(unit_group)
|
118
|
+
@parent = unit_group
|
119
|
+
super
|
120
|
+
end
|
121
|
+
|
122
|
+
# @private
|
123
|
+
# Does the opposite of selector and returns those values for parent
|
124
|
+
def select_type(*)
|
125
|
+
@parent.reject_type(*)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Does the opposite of selector and returns those values for parent
|
129
|
+
def reject_type(*)
|
130
|
+
@parent.select_type(*)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns a new UnitGroup containing all units matching attribute id(s)
|
135
|
+
# Multiple values work as an "AND" filter
|
136
|
+
# @example
|
137
|
+
# # Single
|
138
|
+
# ug.select_attribute(Api::Attribute::Structure) #=> <UnitGroup: ...>
|
139
|
+
# ug.select_attribute(:Structure) #=> <UnitGroup: ...>
|
140
|
+
# # Multiple - select mechanical flying units
|
141
|
+
# ug.select_attribute([:Mechanical, :Flying]) #=> <UnitGroup: ...>
|
142
|
+
# @param attributes [Integer, Array<Integer>] one or an array of unit Api::UnitTypeId
|
143
|
+
# @return [UnitGroup]
|
144
|
+
def select_attribute(attributes)
|
145
|
+
cached("#{__method__}:#{attributes.hash}") do
|
146
|
+
attributes = [attributes] unless attributes.is_a? Array
|
147
|
+
attributes = attributes.map { |a| a.is_a?(Symbol) ? a : Api::Attribute.lookup(a) }
|
148
|
+
select do |unit|
|
149
|
+
attributes & unit.attributes == attributes
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns a new UnitGroup containing all units excluding attribute id(s)
|
155
|
+
# Multiple values work as an "AND" filter
|
156
|
+
# @example
|
157
|
+
# # Single
|
158
|
+
# ug.reject_attribute(Api::Attribute::Structure) #=> <UnitGroup: ...>
|
159
|
+
# ug.reject_attribute(:Structure) #=> <UnitGroup: ...>
|
160
|
+
# # Multiple - reject mechanical flying units
|
161
|
+
# ug.reject_attribute(:Mechanical, :Flying) #=> <UnitGroup: ...>
|
162
|
+
# @param attributes [Integer, Array<Integer>] one or an array of unit Api::UnitTypeId
|
163
|
+
# @return [UnitGroup]
|
164
|
+
def reject_attribute(attributes)
|
165
|
+
cached("#{__method__}:#{attributes.hash}") do
|
166
|
+
attributes = [attributes] unless attributes.is_a? Array
|
167
|
+
attributes = attributes.map { |a| a.is_a?(Symbol) ? a : Api::Attribute.lookup(a) }
|
168
|
+
reject do |unit|
|
169
|
+
unit.attributes & attributes == attributes
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# GENERICS ---
|
175
|
+
|
176
|
+
# Selects worker units
|
177
|
+
# @return [Sc2::UnitGroup] workers
|
178
|
+
def workers
|
179
|
+
select_type(TYPE_WORKER)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Selects non army units workers. Generally run on Sc2::Player#units
|
183
|
+
# @example in the Player context
|
184
|
+
# fighters = units.army
|
185
|
+
# enemy_fighters = units.army
|
186
|
+
# @return [Sc2::UnitGroup] army
|
187
|
+
# @see #non_army_unit_type_ids to modify filters
|
188
|
+
def army
|
189
|
+
reject_type(non_army_unit_type_ids)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Selects units with attribute Structure
|
193
|
+
# @return [Sc2::UnitGroup] structures
|
194
|
+
def structures
|
195
|
+
select_attribute(Api::Attribute::Structure)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Contains an array non-army types
|
199
|
+
# Override to remove or add units you want units.army to exclude
|
200
|
+
# @example
|
201
|
+
# # i.e. to have units.army to exclude Overseers
|
202
|
+
# @non_army_unit_types.push(Api::UnitTypeId::OVERSEER)
|
203
|
+
# # i.e. to have units.army to include Queens
|
204
|
+
# @non_army_unit_types.delete(Api::UnitTypeId::QUEEN)
|
205
|
+
# @non_army_unit_types.delete(Api::UnitTypeId::QUEENBURROWED)
|
206
|
+
def non_army_unit_type_ids
|
207
|
+
@non_army_unit_type_ids ||= TYPE_WORKER + [
|
208
|
+
Api::UnitTypeId::QUEEN, Api::UnitTypeId::QUEENBURROWED,
|
209
|
+
Api::UnitTypeId::OVERLORD, Api::UnitTypeId::OVERLORDCOCOON,
|
210
|
+
Api::UnitTypeId::LARVA
|
211
|
+
]
|
212
|
+
end
|
213
|
+
|
214
|
+
# Selects command posts (CC, OC, PF, Nexus, Hatch, Hive, Lair)
|
215
|
+
# Aliases are #hq and #townhalls
|
216
|
+
# @return [Sc2::UnitGroup] unit group of workers
|
217
|
+
def bases
|
218
|
+
select_type(TYPE_BASES)
|
219
|
+
end
|
220
|
+
alias_method :hq, :bases # short name
|
221
|
+
alias_method :townhalls, :bases # bad name
|
222
|
+
|
223
|
+
# Selects gas structures (refinery/extractor/assimilator)
|
224
|
+
# @return [UnitGroup] gas structures
|
225
|
+
def gas
|
226
|
+
select_type(TYPE_GAS_STRUCTURE)
|
227
|
+
end
|
228
|
+
alias_method :refineries, :gas
|
229
|
+
alias_method :extractors, :gas
|
230
|
+
alias_method :assimilators, :gas
|
231
|
+
|
232
|
+
# NEUTRAL ------------------------------------------
|
233
|
+
|
234
|
+
# Selects mineral fields
|
235
|
+
# @return [Sc2::UnitGroup] mineral fields
|
236
|
+
# @example
|
237
|
+
# # Typically a Player selects via group @neutral
|
238
|
+
# @neutral.minerals
|
239
|
+
def minerals
|
240
|
+
select_type(TYPE_MINERAL)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Selects gas geysers
|
244
|
+
# @return [Sc2::UnitGroup] gas geysers
|
245
|
+
# @example
|
246
|
+
# # Typically a Player selects via group @neutral
|
247
|
+
# @neutral.geysers
|
248
|
+
def geysers
|
249
|
+
select_type(TYPE_GEYSER)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Selects xel'naga watchtowers
|
253
|
+
# @return [Sc2::UnitGroup] watchtowers
|
254
|
+
# @example
|
255
|
+
# # Typically a Player selects via group @neutral
|
256
|
+
# @neutral.watchtowers
|
257
|
+
def watchtowers
|
258
|
+
select_type(Api::UnitTypeId::XELNAGATOWER)
|
259
|
+
end
|
260
|
+
|
261
|
+
# Reverse filters our minerals, geysers and towers to get what is hopefully debris
|
262
|
+
# @return [Sc2::UnitGroup] debris
|
263
|
+
# @example
|
264
|
+
# # Typically a Player selects via group @neutral
|
265
|
+
# @neutral.debris
|
266
|
+
def debris
|
267
|
+
reject_type(TYPE_REJECT_DEBRIS)
|
268
|
+
end
|
269
|
+
|
270
|
+
# ZERG ------------------------------------------
|
271
|
+
# Selects larva units
|
272
|
+
# @return [Sc2::UnitGroup] larva
|
273
|
+
def larva
|
274
|
+
select_type(Api::UnitTypeId::LARVA)
|
275
|
+
end
|
276
|
+
alias_method :larvae, :larva
|
277
|
+
|
278
|
+
# Selects queens
|
279
|
+
# @return [Sc2::UnitGroup] queens
|
280
|
+
def queens
|
281
|
+
select_type([Api::UnitTypeId::QUEEN, Api::UnitTypeId::QUEENBURROWED])
|
282
|
+
end
|
283
|
+
|
284
|
+
# Selects overlords
|
285
|
+
# @return [Sc2::UnitGroup]
|
286
|
+
def overlords
|
287
|
+
select_type([Api::UnitTypeId::OVERLORD, Api::UnitTypeId::OVERLORDCOCOON])
|
288
|
+
end
|
289
|
+
|
290
|
+
# Selects creep tumors (all)
|
291
|
+
# CREEPTUMORQUEEN is still building & burrowing
|
292
|
+
# while CREEPTUMOR was spread from another tumor still building & burrowing
|
293
|
+
# and CREEPTUMORBURROWED are burrowed tumors which have already spread or can still spread more
|
294
|
+
# @see #creep_tumors_burrowed for those ready to be spread
|
295
|
+
# @return [Sc2::UnitGroup] all tumors
|
296
|
+
def creep_tumors
|
297
|
+
select_type([Api::UnitTypeId::CREEPTUMORQUEEN, Api::UnitTypeId::CREEPTUMOR, Api::UnitTypeId::CREEPTUMORBURROWED])
|
298
|
+
end
|
299
|
+
alias_method :tumors, :creep_tumors
|
300
|
+
|
301
|
+
# Selects creep tumors which are burrowed.
|
302
|
+
# Burrowed tumors have already been spread or are spread-ready.
|
303
|
+
# No way to distinguish spreadable tumors without manual tracking.
|
304
|
+
# @return [Sc2::UnitGroup] burrowed tumors (with and without spread ability)
|
305
|
+
def creep_tumors_burrowed
|
306
|
+
select_type(Api::UnitTypeId::CREEPTUMORBURROWED)
|
307
|
+
end
|
308
|
+
|
309
|
+
# Protoss ---
|
310
|
+
|
311
|
+
# Selects pylons
|
312
|
+
# @return [Sc2::UnitGroup] pylons
|
313
|
+
def pylons
|
314
|
+
select_type(Api::UnitTypeId::PYLON)
|
315
|
+
end
|
316
|
+
|
317
|
+
# Selects warp gates (not gateways)
|
318
|
+
# @return [Sc2::UnitGroup] warp gates
|
319
|
+
def warpgates
|
320
|
+
select_type(Api::UnitTypeId::WARPGATE)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Selects pylons and warp prisms in phasing mode
|
324
|
+
# @return [Sc2::UnitGroup] pylons annd warp prisms phasing
|
325
|
+
def warpables
|
326
|
+
select_type([Api::UnitTypeId::PYLON, Api::UnitTypeId::WARPPRISMPHASING])
|
327
|
+
end
|
328
|
+
|
329
|
+
# Geometric/Map ---
|
330
|
+
|
331
|
+
# Whether we should be building a kdtree
|
332
|
+
# Added to allow the disabling of this property
|
333
|
+
# i.e. allows optimization of not to build if group is too big:
|
334
|
+
# return @units.size > 200
|
335
|
+
# If you don't do a lot of repeat filtering and don't get gains from repeated searches
|
336
|
+
# then override the attribute and set this to: @units.size > 120
|
337
|
+
# @return [Boolean]
|
338
|
+
attr_accessor :use_kdtree
|
339
|
+
|
340
|
+
# Builds a kdtree if not already built and returns it
|
341
|
+
# @return [Kdtree]
|
342
|
+
def kdtree
|
343
|
+
return @kdtree unless @kdtree.nil?
|
344
|
+
@kdtree = Kdtree.new(@units.values.each_with_index.map { |unit, index| [unit.pos.x, unit.pos.y, index] })
|
345
|
+
end
|
346
|
+
|
347
|
+
# Returns an
|
348
|
+
# @param pos [Sc2::Position] unit.pos or a point of any kind
|
349
|
+
# @return [Sc2::UnitGroup, Api::Unit, nil] return group or single unit if amount is not supplied
|
350
|
+
def nearest_to(pos:, amount: nil)
|
351
|
+
return UnitGroup.new if !amount.nil? && amount.to_i <= 0
|
352
|
+
|
353
|
+
if use_kdtree
|
354
|
+
if amount.nil?
|
355
|
+
index = kdtree.nearest(pos.x, pos.y)
|
356
|
+
return @units.values[index]
|
357
|
+
else
|
358
|
+
result_indexes = kdtree.nearestk(pos.x, pos.y, amount)
|
359
|
+
return UnitGroup.new(@units.values.values_at(*result_indexes))
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Traditional array min_by with distance calcs on the fly
|
364
|
+
if amount.nil?
|
365
|
+
# noinspection RubyMismatchedReturnType
|
366
|
+
@units.values.min_by { |unit| unit.distance_to(pos) }
|
367
|
+
else
|
368
|
+
UnitGroup.new(@units.values.min_by(amount) { |unit| unit.distance_to(pos) })
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
# Selects units which are in a particular circle
|
373
|
+
# @param point [Api::Point2D, Api::Point] center of circle
|
374
|
+
# @param radius [Float]
|
375
|
+
def select_in_circle(point:, radius:)
|
376
|
+
select { |unit| unit.in_circle?(point:, radius:) }
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
@@ -0,0 +1,277 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sc2
|
4
|
+
# A collection of Api::Unit, which acts as a hash of unit.tag => unit.
|
5
|
+
# Consider a UnitGroup a virtual Control Group.
|
6
|
+
# Most Hash operations like select/filter, reject, etc. are implemented to return a UnitGroup to to allow fluent group control
|
7
|
+
class UnitGroup
|
8
|
+
extend Forwardable
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
# @!attribute units
|
12
|
+
# A hash of units by tag.
|
13
|
+
# @return [Hash<Integer, Api::Unit>] Api::Unit.tag => Api::Unit
|
14
|
+
attr_accessor :units
|
15
|
+
|
16
|
+
# @param units [Api::Unit, Hash<Integer, Api::Unit>, Array<Api::Unit>, Sc2::UnitGroup, nil] default to be added.
|
17
|
+
# @return Sc2::UnitGroup new unit group
|
18
|
+
def initialize(units = nil)
|
19
|
+
@units = {}
|
20
|
+
case units
|
21
|
+
when Array, Google::Protobuf::RepeatedField
|
22
|
+
units.each do |unit|
|
23
|
+
@units[unit.tag] = unit
|
24
|
+
end
|
25
|
+
when Hash
|
26
|
+
@units = units
|
27
|
+
when UnitGroup
|
28
|
+
@units = units.units
|
29
|
+
else
|
30
|
+
# noop
|
31
|
+
end
|
32
|
+
|
33
|
+
@_cache_hash = {}
|
34
|
+
@_cache = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!macro [attach] def_delegators
|
38
|
+
# @!method $2
|
39
|
+
# Forwards to hash of #units.
|
40
|
+
# @see Hash#$2
|
41
|
+
def_delegator :@units, :empty? # Returns whether there are no entries.
|
42
|
+
def_delegator :@units, :eql? # Returns whether a given object is equal to #units.
|
43
|
+
def_delegator :@units, :length # Returns the count of entries.
|
44
|
+
def_delegator :@units, :size # Returns the count of entries.
|
45
|
+
|
46
|
+
# @!macro [attach] def_delegators
|
47
|
+
# @!method $2
|
48
|
+
# Forwards to hash of #units.
|
49
|
+
# @see Hash#$2
|
50
|
+
def_delegator :@units, :< # Returns whether #units is a proper subset of a given object.
|
51
|
+
def_delegator :@units, :<= # Returns whether #units is a subset of a given object.
|
52
|
+
def_delegator :@units, :== # Returns whether a given object is equal to #units.
|
53
|
+
def_delegator :@units, :> # Returns whether #units is a proper superset of a given object
|
54
|
+
def_delegator :@units, :>= # Returns whether #units is a proper superset of a given object.
|
55
|
+
|
56
|
+
# @!macro [attach] def_delegators
|
57
|
+
# @!method $2
|
58
|
+
# Forwards to hash of #units.
|
59
|
+
# @see Hash#$2
|
60
|
+
def_delegator :@units, :[] # Returns the value associated with the given key, if found
|
61
|
+
def_delegator :@units, :keys # Returns an array containing all keys in #units.
|
62
|
+
def_delegator :@units, :values # Returns an array containing all values in #units.
|
63
|
+
def_delegator :@units, :values_at # Returns an array containing values for given tags in #units.
|
64
|
+
|
65
|
+
# @!macro [attach] def_delegators
|
66
|
+
# @!method $2
|
67
|
+
# Forwards to hash of #units.
|
68
|
+
# @see Hash#$2
|
69
|
+
def_delegator :@units, :clear # Removes all entries from #units.
|
70
|
+
def_delegator :@units, :delete_if # Removes entries selected by a given block.
|
71
|
+
def_delegator :@units, :select! # Keep only those entries selected by a given block
|
72
|
+
def_delegator :@units, :filter! # Keep only those entries selected by a given block
|
73
|
+
def_delegator :@units, :keep_if # Keep only those entries selected by a given block
|
74
|
+
# def_delegator :@units, :compact! # Removes all +nil+-valued entries from #units.
|
75
|
+
|
76
|
+
# find_all, #filter, #select: Returns elements selected by the block.
|
77
|
+
|
78
|
+
def_delegator :@units, :reject! # Removes entries selected by a given block.
|
79
|
+
def_delegator :@units, :shift # Removes and returns the first entry.
|
80
|
+
|
81
|
+
def_delegator :@units, :to_a # Returns a new array of 2-element arrays; each nested array contains a key-value pair from #units
|
82
|
+
def_delegator :@units, :to_h # Returns {UnitGroup#units}
|
83
|
+
def_delegator :@units, :to_hash # Returns {UnitGroup#units}
|
84
|
+
def_delegator :@units, :to_proc # Returns a proc that maps a given key to its value.Returns a proc that maps a given key to its value.
|
85
|
+
|
86
|
+
# Gets the Unit at an index
|
87
|
+
# @return [Api::Unit]
|
88
|
+
def at(index)
|
89
|
+
@units[tags.at(index)]
|
90
|
+
end
|
91
|
+
|
92
|
+
# Calls the given block with each Api::Unit value
|
93
|
+
# @example
|
94
|
+
# unit_group.each {|unit| puts unit.tag } #=> 1234 ...
|
95
|
+
#
|
96
|
+
# unit_group.each do |unit|
|
97
|
+
# puts unit.tag #=> 1234 ...
|
98
|
+
# end
|
99
|
+
def each(&block)
|
100
|
+
@units.each_value(&block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Calls the given block with each key-value pair
|
104
|
+
# @return [self] a new unit group with items merged
|
105
|
+
# @example
|
106
|
+
# unit_group.each {|tag, unit| puts "#{tag}: #{unit}"} #=> "12345: #<Api::Unit ...>"
|
107
|
+
#
|
108
|
+
# unit_group.each do |tag, unit|
|
109
|
+
# puts "#{tag}: #{unit}"} #=> "12345: #<Api::Unit ...>"
|
110
|
+
# end
|
111
|
+
def each_with_tag(&block)
|
112
|
+
@units.each(&block)
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
# Checks whether this group contains a unit.
|
117
|
+
# @param unit [Api::Unit, Integer] a unit or a tag.
|
118
|
+
# @return [Boolean] A boolean indicating if the #units include? unit.
|
119
|
+
def contains?(unit)
|
120
|
+
tag = unit.is_a?(Api::Unit) ? unit.tag : unit
|
121
|
+
@units.include?(tag)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Associates a given unit tag with a given unit.
|
125
|
+
# @see UnitGroup#add #add, which is easier to just pass an Api::Unit
|
126
|
+
def []=(unit_tag, unit)
|
127
|
+
return if unit_tag.nil? || unit.nil?
|
128
|
+
@units[unit_tag] = unit
|
129
|
+
end
|
130
|
+
|
131
|
+
# Adds a unit or units to the group.
|
132
|
+
# @param units [Api::Unit, Array<Api::Unit>, Sc2::UnitGroup]
|
133
|
+
# @return [self]
|
134
|
+
def add(units)
|
135
|
+
case units
|
136
|
+
when Api::Unit
|
137
|
+
@units[units.tag] = units
|
138
|
+
when Array
|
139
|
+
units.each do |unit|
|
140
|
+
@units[unit.tag] = unit
|
141
|
+
end
|
142
|
+
when Hash
|
143
|
+
@units.merge(units.units)
|
144
|
+
when UnitGroup
|
145
|
+
@units.merge(units)
|
146
|
+
else
|
147
|
+
# noop
|
148
|
+
end
|
149
|
+
@units
|
150
|
+
end
|
151
|
+
|
152
|
+
alias_method :push, :add
|
153
|
+
|
154
|
+
# Remove a another UnitGroup's units from ours or a singular Api::Unit either by object or Api::Unit#tag
|
155
|
+
# @param unit_group_unit_or_tag [Sc2::UnitGroup, Api::Unit, Integer, nil]
|
156
|
+
# @return [Hash<Integer, Api::Unit>, Api::Unit, nil] the removed item(s) or nil
|
157
|
+
def remove(unit_group_unit_or_tag)
|
158
|
+
case unit_group_unit_or_tag
|
159
|
+
when Sc2::UnitGroup
|
160
|
+
@units.reject! { |tag, _unit| unit_group_unit_or_tag.units.has_key?(tag) }
|
161
|
+
when Api::Unit
|
162
|
+
@units.delete(unit_group_unit_or_tag.tag)
|
163
|
+
when Integer
|
164
|
+
# noinspection RubyMismatchedArgumentType
|
165
|
+
@units.delete(unit_group_unit_or_tag)
|
166
|
+
else
|
167
|
+
# noop
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
alias_method :delete, :remove
|
172
|
+
|
173
|
+
# Creates a new unit group which is the result of the two being subtracted
|
174
|
+
# @param other_unit_group [UnitGroup]
|
175
|
+
# @return [UnitGroup] new unit group
|
176
|
+
def subtract(other_unit_group)
|
177
|
+
UnitGroup.new(@units.reject { |tag, _unit| other_unit_group.units.has_key?(tag) })
|
178
|
+
end
|
179
|
+
|
180
|
+
# Merges unit_group with our units and returns a new unit group
|
181
|
+
# @return [Sc2::UnitGroup] a new unit group with items merged
|
182
|
+
def merge(unit_group)
|
183
|
+
UnitGroup.new(@units.merge(unit_group))
|
184
|
+
end
|
185
|
+
alias_method :+, :merge
|
186
|
+
|
187
|
+
# Merges unit_group.units into self.units and returns self
|
188
|
+
# @return [self]
|
189
|
+
def merge!(unit_group)
|
190
|
+
@units.merge!(unit_group.units)
|
191
|
+
self
|
192
|
+
end
|
193
|
+
|
194
|
+
# Replaces the entire contents of #units with the contents of a given unit_group.
|
195
|
+
# Synonymous with self.units = unit_group.units
|
196
|
+
# @return [void]
|
197
|
+
def replace(unit_group)
|
198
|
+
@units = unit_group.units
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns a new UnitGroup object whose #units entries are those for which the block returns a truthy value
|
202
|
+
# @return [Sc2::UnitGroup] new unit group
|
203
|
+
# noinspection RubyMismatchedReturnType # UnitGroup acts as an array, so sig is ok.
|
204
|
+
def select
|
205
|
+
result = @units.select { |_tag, unit| yield unit }
|
206
|
+
UnitGroup.new(result)
|
207
|
+
end
|
208
|
+
|
209
|
+
alias_method :filter, :select
|
210
|
+
|
211
|
+
# Returns a new UnitGroup object whose entries are all those from #units for which the block returns false or nil
|
212
|
+
# @return [Sc2::UnitGroup] new unit group
|
213
|
+
# noinspection RubyMismatchedReturnType # UnitGroup acts as an array, so sig is ok.
|
214
|
+
def reject(&block)
|
215
|
+
result = @units.reject { |_tag, unit| yield unit }
|
216
|
+
UnitGroup.new(result)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Returns a copy of self with units removed for specified tags.
|
220
|
+
# @return [Sc2::UnitGroup] new unit group
|
221
|
+
def except(...)
|
222
|
+
UnitGroup.new(@units.except(...))
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns a hash containing the entries for given tag(s).
|
226
|
+
# @return [Sc2::UnitGroup] new unit group
|
227
|
+
def slice(...)
|
228
|
+
UnitGroup.new(@units.slice(...))
|
229
|
+
end
|
230
|
+
|
231
|
+
# Selects a single random Unit without a parameter or an array of Units with a param, i.e. self.random(2)
|
232
|
+
# @return []
|
233
|
+
def sample(...)
|
234
|
+
@units.values.sample(...)
|
235
|
+
end
|
236
|
+
alias_method :random, :sample
|
237
|
+
|
238
|
+
# def select_or(*procs)
|
239
|
+
# result = UnitGroup.new
|
240
|
+
# procs.each do |proc|
|
241
|
+
# selected = select(&proc)
|
242
|
+
# result.merge!(selected) unless selected.nil?
|
243
|
+
# end
|
244
|
+
# result
|
245
|
+
# end
|
246
|
+
|
247
|
+
# Returns an array of unit tags
|
248
|
+
# @return [Array<Integer>] array of unit#tag
|
249
|
+
def tags
|
250
|
+
keys
|
251
|
+
end
|
252
|
+
|
253
|
+
class << self
|
254
|
+
end
|
255
|
+
|
256
|
+
private
|
257
|
+
|
258
|
+
# @private
|
259
|
+
# Caches a block based on key and current UnitGroup#units.hash
|
260
|
+
# If the hash changes (units are modified) the cache expires
|
261
|
+
# This allows lazy lookups which only fires if the units change
|
262
|
+
# Allows, i.e. Player#units.workers to fire only the first time it's called per frame
|
263
|
+
def cached(key)
|
264
|
+
if @_cache_hash[key] != @units.hash
|
265
|
+
@_cache_hash[key] = @units.hash
|
266
|
+
@_cache[key] = yield
|
267
|
+
end
|
268
|
+
@_cache[key]
|
269
|
+
end
|
270
|
+
|
271
|
+
attr_accessor :_cache_hash
|
272
|
+
attr_accessor :_cache
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
require_relative "unit_group/action_ext"
|
277
|
+
require_relative "unit_group/filter_ext"
|