sc2ai 0.0.5 → 0.0.7
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/stableid.json +424 -2562
- data/data/versions.json +8 -0
- data/lib/docker_build/Dockerfile.ruby +1 -1
- data/lib/sc2ai/api/ability_id.rb +12 -314
- data/lib/sc2ai/api/buff_id.rb +9 -16
- data/lib/sc2ai/api/data.rb +1 -4
- data/lib/sc2ai/api/tech_tree.rb +35 -0
- data/lib/sc2ai/api/tech_tree_data.rb +14 -9
- data/lib/sc2ai/api/unit_type_id.rb +6 -58
- data/lib/sc2ai/api/upgrade_id.rb +9 -9
- data/lib/sc2ai/connection/requests.rb +34 -16
- data/lib/sc2ai/data.rb +101 -0
- data/lib/sc2ai/local_play/match.rb +1 -3
- data/lib/sc2ai/player/actions.rb +8 -4
- data/lib/sc2ai/player/debug.rb +2 -3
- data/lib/sc2ai/player/game_state.rb +36 -5
- data/lib/sc2ai/player/geometry.rb +138 -55
- data/lib/sc2ai/player/previous_state.rb +2 -1
- data/lib/sc2ai/player/units.rb +66 -3
- data/lib/sc2ai/player.rb +5 -3
- data/lib/sc2ai/protocol/_meta_documentation.rb +18 -0
- data/lib/sc2ai/protocol/extensions/ability_remapable.rb +22 -0
- data/lib/sc2ai/protocol/extensions/point.rb +3 -1
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +1 -1
- data/lib/sc2ai/protocol/extensions/point_distance.rb +11 -0
- data/lib/sc2ai/protocol/extensions/position.rb +48 -13
- data/lib/sc2ai/protocol/extensions/unit.rb +54 -3
- data/lib/sc2ai/protocol/extensions/unit_type.rb +9 -0
- data/lib/sc2ai/unit_group/action_ext.rb +3 -3
- data/lib/sc2ai/unit_group/filter_ext.rb +33 -7
- data/lib/sc2ai/unit_group/geo_ext.rb +28 -0
- data/lib/sc2ai/unit_group.rb +3 -0
- data/lib/sc2ai/version.rb +1 -1
- data/lib/templates/new/run_example_match.rb.tt +1 -1
- data/sig/sc2ai.rbs +653 -567
- metadata +22 -31
@@ -20,7 +20,7 @@ module Sc2
|
|
20
20
|
# @return [Integer]
|
21
21
|
def map_width
|
22
22
|
# bot.bot.game_info
|
23
|
-
bot.game_info.start_raw.map_size.x
|
23
|
+
@map_width ||= bot.game_info.start_raw.map_size.x
|
24
24
|
end
|
25
25
|
|
26
26
|
# Gets the map tile height. Range is 1-255.
|
@@ -28,7 +28,13 @@ module Sc2
|
|
28
28
|
# @return [Integer]
|
29
29
|
def map_height
|
30
30
|
# bot.bot.game_info
|
31
|
-
bot.game_info.start_raw.map_size.y
|
31
|
+
@map_height ||= bot.game_info.start_raw.map_size.y
|
32
|
+
end
|
33
|
+
|
34
|
+
# Center of the map
|
35
|
+
# @return [Api::Point2D]
|
36
|
+
def map_center
|
37
|
+
@map_center ||= Api::Point2D[map_width / 2, map_height / 2]
|
32
38
|
end
|
33
39
|
|
34
40
|
# Returns zero to map_width as range
|
@@ -83,6 +89,15 @@ module Sc2
|
|
83
89
|
@parsed_placement_grid
|
84
90
|
end
|
85
91
|
|
92
|
+
# Whether this tile is where an expansion is supposed to be placed.
|
93
|
+
# To see if a unit/structure is blocking an expansion, pass their coordinates to this method.
|
94
|
+
# @param x [Float, Integer]
|
95
|
+
# @param y [Float, Integer]
|
96
|
+
# @return [Boolean] true if location has creep on it
|
97
|
+
def expo_placement?(x:, y:)
|
98
|
+
expo_placement_grid[y.to_i, x.to_i] == 1
|
99
|
+
end
|
100
|
+
|
86
101
|
# Returns a grid where ony the expo locations are marked
|
87
102
|
# @return [Numo::Bit]
|
88
103
|
def expo_placement_grid
|
@@ -91,8 +106,16 @@ module Sc2
|
|
91
106
|
expansion_points.each do |point|
|
92
107
|
x = point.x.floor
|
93
108
|
y = point.y.floor
|
94
|
-
|
95
|
-
|
109
|
+
|
110
|
+
# For zerg, reserve a layer at the bottom for larva->egg
|
111
|
+
if bot.race == Api::Race::Zerg
|
112
|
+
# Reserve one row lower, meaning (y-3) instead of (y-2)
|
113
|
+
@expo_placement_grid[(y - 3).clamp(map_tile_range_y)..(y + 2).clamp(map_tile_range_y),
|
114
|
+
(x - 2).clamp(map_tile_range_y)..(x + 2).clamp(map_tile_range_y)] = 1
|
115
|
+
else
|
116
|
+
@expo_placement_grid[(y - 2).clamp(map_tile_range_y)..(y + 2).clamp(map_tile_range_y),
|
117
|
+
(x - 2).clamp(map_tile_range_y)..(x + 2).clamp(map_tile_range_y)] = 1
|
118
|
+
end
|
96
119
|
end
|
97
120
|
end
|
98
121
|
@expo_placement_grid
|
@@ -112,40 +135,11 @@ module Sc2
|
|
112
135
|
return result
|
113
136
|
end
|
114
137
|
|
115
|
-
radius = power_source.radius
|
116
|
-
radius_tile = radius.ceil
|
117
|
-
|
118
|
-
# Keep this code-block, should we need to make power sources dynamic again:
|
119
|
-
# START: Dynamic blueprint
|
120
|
-
# # Build a blueprint and mark it everywhere we need to
|
121
|
-
# # Lets mark everything as powered with 1 and then disable non-powered with a 0
|
122
|
-
# blueprint = Numo::Bit.ones(radius.ceil * 2, radius.ceil * 2)
|
123
|
-
# #blueprint[radius_tile, radius_tile] = 0
|
124
|
-
# blueprint[(radius_tile - 1)..radius_tile, (radius_tile - 1)..radius_tile] = 0
|
125
|
-
# # Loop over top-right quadrant of a circle, so we don't have to +/- for distance calcs.
|
126
|
-
# # Additionally, we only measure if in the upper triangle, since the inner is all inside the circle.
|
127
|
-
# # Then apply to all four quadrants.
|
128
|
-
# quadrant_size = radius_tile - 1
|
129
|
-
# point_search_offsets = (0..quadrant_size).to_a.product((0..quadrant_size).to_a)
|
130
|
-
# point_search_offsets.each do |y, x|
|
131
|
-
# next if x < quadrant_size - y # Only upper Triangle
|
132
|
-
#
|
133
|
-
# dist = Math.hypot(x, y)
|
134
|
-
# if dist >= radius
|
135
|
-
# # Mark as outside x4
|
136
|
-
# blueprint[radius_tile + y, radius_tile + x] = 0
|
137
|
-
# blueprint[radius_tile + y, radius_tile - 1 - x] = 0
|
138
|
-
# blueprint[radius_tile - 1 - y, radius_tile + x] = 0
|
139
|
-
# blueprint[radius_tile - 1 - y, radius_tile - 1 - x] = 0
|
140
|
-
# end
|
141
|
-
# end
|
142
|
-
# END: Dynamic blueprint ---
|
143
|
-
|
144
138
|
# Hard-coding this shape for pylon power
|
145
139
|
# 00001111110000
|
140
|
+
# 00011111111000
|
146
141
|
# 00111111111100
|
147
142
|
# 01111111111110
|
148
|
-
# 01111111111110
|
149
143
|
# 11111111111111
|
150
144
|
# 11111111111111
|
151
145
|
# 11111100111111
|
@@ -153,15 +147,35 @@ module Sc2
|
|
153
147
|
# 11111111111111
|
154
148
|
# 11111111111111
|
155
149
|
# 01111111111110
|
156
|
-
# 01111111111110
|
157
150
|
# 00111111111100
|
151
|
+
# 00011111111000
|
158
152
|
# 00001111110000
|
159
|
-
|
160
153
|
# perf: Saving pre-created shape for speed (0.5ms saved) by using hardcode from .to_binary.unpack("C*")
|
161
|
-
blueprint_data = [
|
162
|
-
|
163
|
-
|
154
|
+
blueprint_data = [0, 0, 254, 193, 255, 248, 127, 254, 159, 255, 231, 243, 249, 124, 254, 159, 255, 231, 255, 241, 63, 248, 7, 0, 0].pack("C*")
|
155
|
+
blueprint_pylon = ::Numo::Bit.from_binary(blueprint_data, [14, 14])
|
156
|
+
|
157
|
+
# Warp Prism
|
158
|
+
# 00011000
|
159
|
+
# 01111110
|
160
|
+
# 01111110
|
161
|
+
# 11111111
|
162
|
+
# 11111111
|
163
|
+
# 01111110
|
164
|
+
# 01111110
|
165
|
+
# 00011000
|
166
|
+
blueprint_data = [24, 126, 126, 255, 255, 126, 126, 24].pack("C*")
|
167
|
+
blueprint_prism = ::Numo::Bit.from_binary(blueprint_data, [8, 8])
|
168
|
+
|
169
|
+
# Print each power-source on map using shape above
|
164
170
|
bot.power_sources.each do |ps|
|
171
|
+
radius_tile = ps.radius.ceil
|
172
|
+
# Select blueprint for 7-tile radius (Pylon) or 4-tile radius (Prism)
|
173
|
+
blueprint = if radius_tile == 4
|
174
|
+
blueprint_prism
|
175
|
+
else
|
176
|
+
blueprint_pylon
|
177
|
+
end
|
178
|
+
|
165
179
|
x_tile = ps.pos.x.floor
|
166
180
|
y_tile = ps.pos.y.floor
|
167
181
|
replace_start_x = (x_tile - radius_tile)
|
@@ -260,6 +274,7 @@ module Sc2
|
|
260
274
|
end
|
261
275
|
|
262
276
|
# Returns the terrain height (z) at position x and y for a point
|
277
|
+
# @param position [Sc2::Position]
|
263
278
|
# @return [Float] z axis position between -16 and 16
|
264
279
|
def terrain_height_for_pos(position)
|
265
280
|
terrain_height(x: position.x, y: position.y)
|
@@ -340,15 +355,17 @@ module Sc2
|
|
340
355
|
end
|
341
356
|
|
342
357
|
# Provides parsed minimap representation of creep spread
|
358
|
+
# Caches for 4 frames
|
343
359
|
# @return [Numo::Bit] Numo array
|
344
360
|
def parsed_creep
|
345
|
-
if @parsed_creep.nil?
|
361
|
+
if @parsed_creep.nil? || @parsed_creep[1] + 4 < bot.game_loop
|
346
362
|
image_data = bot.observation.raw_data.map_state.creep
|
347
363
|
# Fix endian for Numo bit parser
|
348
364
|
data = image_data.data.unpack("b*").pack("B*")
|
349
|
-
|
365
|
+
result = ::Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
|
366
|
+
@parsed_creep = [result, bot.game_loop]
|
350
367
|
end
|
351
|
-
@parsed_creep
|
368
|
+
@parsed_creep[0]
|
352
369
|
end
|
353
370
|
|
354
371
|
# TODO: Removing. Better name or more features for this? Maybe check nearest units.
|
@@ -412,7 +429,7 @@ module Sc2
|
|
412
429
|
# This differs from position of first base structure
|
413
430
|
# @return [Api::Point2D]
|
414
431
|
def start_position
|
415
|
-
@start_position ||= bot.observation.raw_data.player.camera
|
432
|
+
@start_position ||= bot.observation.raw_data.player.camera.to_p2d
|
416
433
|
end
|
417
434
|
|
418
435
|
# Returns the enemy 2d start position
|
@@ -543,16 +560,75 @@ module Sc2
|
|
543
560
|
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
544
561
|
# @return [Sc2::UnitGroup] UnitGroup of minerals for the base
|
545
562
|
def minerals_for_base(base)
|
546
|
-
|
547
|
-
|
548
|
-
bot.neutral.minerals.
|
563
|
+
base_resources = resources_for_base(base)
|
564
|
+
cached_tags = base_resources.minerals.tags
|
565
|
+
observed_tags = bot.neutral.minerals.tags
|
566
|
+
|
567
|
+
# BACK-STORY: Mineral id's are fixed when in vision.
|
568
|
+
# Snapshots get random id's every time an object leaves vision.
|
569
|
+
# At game launch when we calculate and save minerals, which are mostly snapshot.
|
570
|
+
|
571
|
+
# Currently, we might have moved vision over minerals, so that their id's have changed.
|
572
|
+
# The alive object share a Position with our cached one, so we can get the correct id and update our cache.
|
573
|
+
|
574
|
+
# PERF: Fix takes 0.70ms, cache takes 0.10ms - we mostly call cached. This is the way.
|
575
|
+
# PERF: In contrast, repeated calls to neutral.minerals.units_in_circle? always costs 0.22ms
|
576
|
+
|
577
|
+
missing_tags = cached_tags - observed_tags
|
578
|
+
unless missing_tags.empty?
|
579
|
+
other_alive_minerals = bot.neutral.minerals.slice(*(observed_tags - cached_tags))
|
580
|
+
# For each missing calculated mineral patch...
|
581
|
+
missing_tags.each do |tag|
|
582
|
+
missing_resource = base_resources.delete(tag)
|
583
|
+
|
584
|
+
# Find an alive mineral at that position
|
585
|
+
new_resource = other_alive_minerals.find { |live_mineral| live_mineral.pos == missing_resource.pos }
|
586
|
+
base_resources.add(new_resource) unless new_resource.nil?
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
base_resources.minerals
|
549
591
|
end
|
550
592
|
|
551
593
|
# Gets geysers for a base or base position
|
552
594
|
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
553
595
|
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
554
596
|
def geysers_for_base(base)
|
555
|
-
|
597
|
+
# @see #minerals_for_base for backstory on these fixes
|
598
|
+
base_resources = resources_for_base(base)
|
599
|
+
cached_tags = base_resources.geysers.tags
|
600
|
+
observed_tags = bot.neutral.geysers.tags
|
601
|
+
|
602
|
+
missing_tags = cached_tags - observed_tags
|
603
|
+
unless missing_tags.empty?
|
604
|
+
other_alive_geysers = bot.neutral.geysers.slice(*(observed_tags - cached_tags))
|
605
|
+
# For each missing calculated geyser patch...
|
606
|
+
missing_tags.each do |tag|
|
607
|
+
missing_resource = base_resources.delete(tag)
|
608
|
+
|
609
|
+
# Find an alive geyser at that position
|
610
|
+
new_resource = other_alive_geysers.find { |live_geyser| live_geyser.pos == missing_resource.pos }
|
611
|
+
base_resources.add(new_resource) unless new_resource.nil?
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
base_resources.geysers
|
616
|
+
end
|
617
|
+
|
618
|
+
# Gets geysers which have not been taken for a base or base position
|
619
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
620
|
+
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
621
|
+
def geysers_open_for_base(base)
|
622
|
+
geysers = geysers_for_base(base)
|
623
|
+
|
624
|
+
# Mineral-only base, return nothing
|
625
|
+
return UnitGroup.new if geysers.size == 0
|
626
|
+
|
627
|
+
# Reject all which have a gas structure on-top
|
628
|
+
gas_positions = bot.structures.gas.map { |gas| gas.pos }
|
629
|
+
geysers.reject do |geyser|
|
630
|
+
gas_positions.include?(geyser.pos)
|
631
|
+
end
|
556
632
|
end
|
557
633
|
|
558
634
|
# @private
|
@@ -560,6 +636,7 @@ module Sc2
|
|
560
636
|
# @return [Sc2::UnitGroup] UnitGroup of resources (minerals+geysers)
|
561
637
|
private def resources_for_base(base)
|
562
638
|
pos = base.is_a?(Api::Unit) ? base.pos : base
|
639
|
+
pos = pos.to_p2d if base.is_a?(Api::Point)
|
563
640
|
|
564
641
|
# If we have a base setup for this exact position, use it
|
565
642
|
if expansions.has_key?(pos)
|
@@ -599,12 +676,17 @@ module Sc2
|
|
599
676
|
def build_coordinates(length:, on_creep: false, in_power: false)
|
600
677
|
length = 1 if length < 1
|
601
678
|
@_build_coordinates ||= {}
|
602
|
-
cache_key = [length, on_creep].hash
|
679
|
+
cache_key = [length, on_creep, in_power].hash
|
603
680
|
return @_build_coordinates[cache_key] if !@_build_coordinates[cache_key].nil? && !bot.game_info_stale?
|
604
681
|
|
605
682
|
result = []
|
606
683
|
input_grid = parsed_pathing_grid & parsed_placement_grid & ~expo_placement_grid
|
607
|
-
input_grid =
|
684
|
+
input_grid = if on_creep
|
685
|
+
parsed_creep & input_grid
|
686
|
+
else
|
687
|
+
~parsed_creep & input_grid
|
688
|
+
end
|
689
|
+
|
608
690
|
input_grid = parsed_power_grid & input_grid if in_power
|
609
691
|
|
610
692
|
# Dimensions
|
@@ -657,23 +739,24 @@ module Sc2
|
|
657
739
|
# @param length [Integer] length of the building, 2 for depot/pylon, 3 for rax/gate
|
658
740
|
# @param target [Api::Unit, Sc2::Position] near where to find a placement
|
659
741
|
# @param random [Integer] number of nearest points to randomly choose from. 1 for nearest point.
|
742
|
+
# @param in_power [Boolean] whether this must be on a power field
|
660
743
|
# @return [Api::Point2D, nil] buildable location, nil if no buildable location found
|
661
|
-
def build_placement_near(length:, target:, random: 1)
|
744
|
+
def build_placement_near(length:, target:, random: 1, in_power: false)
|
662
745
|
target = target.pos if target.is_a? Api::Unit
|
663
746
|
random = 1 if random.to_i.negative?
|
664
747
|
length = 1 if length < 1
|
665
748
|
on_creep = bot.race == Api::Race::Zerg
|
666
749
|
|
667
|
-
coordinates = build_coordinates(length:, on_creep:)
|
750
|
+
coordinates = build_coordinates(length:, on_creep:, in_power:)
|
751
|
+
cache_key = coordinates.hash
|
668
752
|
@_build_coordinate_tree ||= {}
|
669
|
-
cache_key = [length, on_creep].hash
|
670
753
|
if @_build_coordinate_tree[cache_key].nil?
|
671
754
|
@_build_coordinate_tree[cache_key] = Kdtree.new(
|
672
755
|
coordinates.each_with_index.map { |coords, index| coords + [index] }
|
673
756
|
)
|
674
757
|
end
|
675
758
|
nearest = @_build_coordinate_tree[cache_key].nearestk(target.x, target.y, random)
|
676
|
-
return nil if nearest.nil?
|
759
|
+
return nil if nearest.nil? || nearest.empty?
|
677
760
|
|
678
761
|
coordinates[nearest.sample].to_p2d
|
679
762
|
end
|
@@ -819,14 +902,14 @@ module Sc2
|
|
819
902
|
# @example
|
820
903
|
# Randomly randomly adjust both x and y by a range of -3.5 or +3.5
|
821
904
|
# geo.point_random_near(point: structures.hq.first, offset: 3.5)
|
822
|
-
# @param pos [Sc2::
|
905
|
+
# @param pos [Sc2::Position]
|
823
906
|
# @param offset [Float]
|
824
907
|
# @return [Api::Point2D]
|
825
908
|
def point_random_near(pos:, offset: 1.0)
|
826
909
|
pos.random_offset(offset)
|
827
910
|
end
|
828
911
|
|
829
|
-
# @param pos [Sc2::
|
912
|
+
# @param pos [Sc2::Position]
|
830
913
|
# @param radius [Float]
|
831
914
|
# @return [Api::Point2D]
|
832
915
|
def point_random_on_circle(pos:, radius: 1.0)
|
@@ -16,6 +16,7 @@ module Sc2
|
|
16
16
|
@all_units = bot.all_units
|
17
17
|
@units = bot.units
|
18
18
|
@structures = bot.structures
|
19
|
+
@placeholders = bot.placeholders
|
19
20
|
@neutral = bot.neutral
|
20
21
|
@effects = bot.effects
|
21
22
|
@power_sources = bot.power_sources
|
@@ -24,7 +25,7 @@ module Sc2
|
|
24
25
|
@status = bot.status
|
25
26
|
@game_info = bot.game_info
|
26
27
|
@observation = bot.observation
|
27
|
-
|
28
|
+
@game_loop = bot.observation.game_loop
|
28
29
|
@spent_minerals = bot.spent_minerals
|
29
30
|
@spent_vespene = bot.spent_vespene
|
30
31
|
@spent_supply = bot.spent_supply
|
data/lib/sc2ai/player/units.rb
CHANGED
@@ -15,9 +15,14 @@ module Sc2
|
|
15
15
|
|
16
16
|
# A full list of all your structures (non-units)
|
17
17
|
# @!attribute units
|
18
|
-
# @return [Sc2::UnitGroup] a group of
|
18
|
+
# @return [Sc2::UnitGroup] a group of structures
|
19
19
|
attr_accessor :structures
|
20
20
|
|
21
|
+
# A list of structures which haven't started
|
22
|
+
# @!attribute units
|
23
|
+
# @return [Sc2::UnitGroup] a group of placeholder structures
|
24
|
+
attr_accessor :placeholders
|
25
|
+
|
21
26
|
# All units with alliance :Neutral
|
22
27
|
# @!attribute neutral
|
23
28
|
# @return [Sc2::UnitGroup] a group of neutral units
|
@@ -77,6 +82,47 @@ module Sc2
|
|
77
82
|
false
|
78
83
|
end
|
79
84
|
|
85
|
+
# For this unit type, tells you how many are in progress by checking orders for all it's sources.
|
86
|
+
# @return [Integer]
|
87
|
+
def units_in_progress(unit_type_id)
|
88
|
+
source_unit_types = Api::TechTree.unit_created_from(unit_type_id: unit_type_id)
|
89
|
+
|
90
|
+
# When building from LARVA, check the intermediate models
|
91
|
+
if source_unit_types.include?(Api::UnitTypeId::LARVA)
|
92
|
+
source_unit_types << Api::UnitTypeId::EGG
|
93
|
+
elsif source_unit_types.include?(Api::UnitTypeId::BANELING)
|
94
|
+
# For certain Zerg types, return the count of specific intermediate egg/cocoon
|
95
|
+
return units.select_type(Api::UnitTypeId::BANELINGCOCOON).size
|
96
|
+
elsif source_unit_types.include?(Api::UnitTypeId::RAVAGER)
|
97
|
+
return units.select_type(Api::UnitTypeId::RAVAGERCOCOON).size
|
98
|
+
elsif source_unit_types.include?(Api::UnitTypeId::OVERSEER)
|
99
|
+
return units.select_type(Api::UnitTypeId::OVERLORDCOCOON).size
|
100
|
+
elsif source_unit_types.include?(Api::UnitTypeId::LURKERMP)
|
101
|
+
return units.select_type(Api::UnitTypeId::LURKERMPEGG).size
|
102
|
+
elsif source_unit_types.include?(Api::UnitTypeId::BROODLORD)
|
103
|
+
return units.select_type(Api::UnitTypeId::BROODLORDCOCOON).size
|
104
|
+
end
|
105
|
+
|
106
|
+
unit_create_ability = Api::TechTree.unit_type_creation_ability_id(
|
107
|
+
source: source_unit_types.first,
|
108
|
+
target: unit_type_id
|
109
|
+
)
|
110
|
+
|
111
|
+
origin = if unit_data(source_unit_types.first).attributes.include?(:Structure)
|
112
|
+
structures
|
113
|
+
else
|
114
|
+
units
|
115
|
+
end
|
116
|
+
total_in_progress = origin.select_type(source_unit_types).sum do |source|
|
117
|
+
source.orders.count do |order|
|
118
|
+
true if order.ability_id == unit_create_ability
|
119
|
+
end
|
120
|
+
end
|
121
|
+
total_in_progress *= 2 if unit_type_id == Api::UnitTypeId::ZERGLING
|
122
|
+
|
123
|
+
total_in_progress
|
124
|
+
end
|
125
|
+
|
80
126
|
# An array of Protoss power sources, which have a point, radius and unit tag
|
81
127
|
# @!attribute power_sources
|
82
128
|
# @return [Array<Api::PowerSource>] an array of power sources
|
@@ -84,13 +130,14 @@ module Sc2
|
|
84
130
|
|
85
131
|
# An array of Sensor tower rings as per minimap. It has a `pos` and a `radius`
|
86
132
|
# @!attribute power_sources
|
87
|
-
# @return [Array<Api::RadarRing>] an array of
|
133
|
+
# @return [Array<Api::RadarRing>] an array of radar rings sources
|
88
134
|
attr_accessor :radar_rings # not a unit but has a tag
|
89
135
|
|
90
136
|
# @private
|
91
137
|
# @!attribute all_seen_unit_tags
|
92
138
|
# Privately keep track of all seen Unit tags (excl structures) in order to detect new created units
|
93
139
|
attr_accessor :_all_seen_unit_tags
|
140
|
+
private :_all_seen_unit_tags
|
94
141
|
|
95
142
|
# Event-driven unit groups ---
|
96
143
|
|
@@ -247,6 +294,17 @@ module Sc2
|
|
247
294
|
true
|
248
295
|
end
|
249
296
|
|
297
|
+
# Micro/Unit-Specific ------
|
298
|
+
|
299
|
+
# Returns whether Query Available Ability is true for unit and tag
|
300
|
+
# Queries API if necessary. Uses batching in the background.
|
301
|
+
# @param [Integer] unit_tag
|
302
|
+
# @param [Integer] ability_id
|
303
|
+
# @return [Boolean]
|
304
|
+
def unit_ability_available?(unit_tag:, ability_id:)
|
305
|
+
!!available_abilities[unit_tag]&.include?(ability_id)
|
306
|
+
end
|
307
|
+
|
250
308
|
private
|
251
309
|
|
252
310
|
# @private
|
@@ -258,8 +316,10 @@ module Sc2
|
|
258
316
|
# Clear previous units and prep for categorization
|
259
317
|
@units = UnitGroup.new
|
260
318
|
@structures = UnitGroup.new
|
319
|
+
@placeholders = UnitGroup.new
|
261
320
|
@enemy.units = UnitGroup.new
|
262
321
|
@enemy.structures = UnitGroup.new
|
322
|
+
@enemy.all_units = UnitGroup.new
|
263
323
|
@neutral = UnitGroup.new
|
264
324
|
@effects = observation.raw_data.effects # not a unit
|
265
325
|
@power_sources = observation.raw_data.player.power_sources # not a unit
|
@@ -294,6 +354,8 @@ module Sc2
|
|
294
354
|
# Categorize own units/structures, enemy units/structures, neutral
|
295
355
|
if unit.is_blip
|
296
356
|
@blips[tag] = unit
|
357
|
+
elsif unit.display_type == :Placeholder
|
358
|
+
@placeholders[tag] = unit
|
297
359
|
elsif unit.alliance == own_alliance || unit.alliance == enemy_alliance
|
298
360
|
if unit.alliance == own_alliance
|
299
361
|
structure_collection = @structures
|
@@ -301,6 +363,7 @@ module Sc2
|
|
301
363
|
else
|
302
364
|
structure_collection = @enemy.structures
|
303
365
|
unit_collection = @enemy.units
|
366
|
+
@enemy.all_units[tag] = unit
|
304
367
|
end
|
305
368
|
|
306
369
|
unit_data = unit_data(unit.unit_type)
|
@@ -314,7 +377,7 @@ module Sc2
|
|
314
377
|
end
|
315
378
|
|
316
379
|
# Dont parse callbacks on first loop or for neutral units
|
317
|
-
if
|
380
|
+
if !@previous.all_units.nil? &&
|
318
381
|
unit.alliance != :Neutral &&
|
319
382
|
unit.display_type != :Placeholder &&
|
320
383
|
unit.is_blip == false
|
data/lib/sc2ai/player.rb
CHANGED
@@ -226,13 +226,14 @@ module Sc2
|
|
226
226
|
# Callback for step 0
|
227
227
|
on_step
|
228
228
|
|
229
|
+
puts ""
|
229
230
|
# Step 1 to n
|
230
231
|
loop do
|
231
232
|
r = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
232
233
|
perform_actions
|
233
234
|
perform_debug_commands # TODO: Detect IS_LADDER? -> unless IS_LADDER?
|
234
235
|
step_forward
|
235
|
-
|
236
|
+
print "\e[2K#{game_loop - @previous.game_loop} Steps Took (ms): #{(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - r) * 1000}\n\e[1A\r"
|
236
237
|
return @result unless @result.nil?
|
237
238
|
break if @status != :in_game
|
238
239
|
end
|
@@ -525,6 +526,7 @@ module Sc2
|
|
525
526
|
@previous.reset(self)
|
526
527
|
# Reset
|
527
528
|
self.observation = response_observation.observation
|
529
|
+
self.game_loop = observation.game_loop
|
528
530
|
self.chats_received = response_observation.chat
|
529
531
|
self.spent_minerals = 0
|
530
532
|
self.spent_vespene = 0
|
@@ -547,7 +549,7 @@ module Sc2
|
|
547
549
|
# Having loaded all the necessities for the current state...
|
548
550
|
# If we're on the first frame of the game, say previous state and current are the same
|
549
551
|
# This is better than having a bunch of random zero and nil values
|
550
|
-
@previous.reset(self) if
|
552
|
+
@previous.reset(self) if @previous.all_units.nil?
|
551
553
|
|
552
554
|
# TODO: remove @events attributes if we don't use them for performance gains
|
553
555
|
# Actions performed and errors (only if implemented)
|
@@ -584,7 +586,7 @@ module Sc2
|
|
584
586
|
self.game_info = @api.game_info
|
585
587
|
end
|
586
588
|
|
587
|
-
#
|
589
|
+
# Enemy -----------------------
|
588
590
|
|
589
591
|
# If you're random, best to set #race to match after launched
|
590
592
|
def set_race_for_random
|
@@ -23,8 +23,26 @@
|
|
23
23
|
# class Unit < Google::Protobuf::AbstractMessage; end;
|
24
24
|
# # Protobuf virtual class.
|
25
25
|
# class UnitTypeData < Google::Protobuf::AbstractMessage; end;
|
26
|
+
# # Protobuf virtual class.
|
27
|
+
# class AvailableAbility < Google::Protobuf::AbstractMessage; end;
|
28
|
+
# # Protobuf virtual class.
|
29
|
+
# class UnitOrder < Google::Protobuf::AbstractMessage; end;
|
30
|
+
# # Protobuf virtual class.
|
31
|
+
# class ActionRawUnitCommand < Google::Protobuf::AbstractMessage; end;
|
32
|
+
# # Protobuf virtual class.
|
33
|
+
# class ActionRawToggleAutocast < Google::Protobuf::AbstractMessage; end;
|
34
|
+
# # Protobuf virtual class.
|
35
|
+
# class ActionError < Google::Protobuf::AbstractMessage; end;
|
36
|
+
# # Protobuf virtual class.
|
37
|
+
# class ActionSpatialUnitCommand < Google::Protobuf::AbstractMessage; end;
|
38
|
+
# # Protobuf virtual class.
|
39
|
+
# class BuildItem < Google::Protobuf::AbstractMessage; end;
|
40
|
+
# # Protobuf virtual class.
|
41
|
+
# class ActionToggleAutocast < Google::Protobuf::AbstractMessage; end;
|
26
42
|
# end
|
27
43
|
|
44
|
+
|
45
|
+
|
28
46
|
# Protobuf enums ---
|
29
47
|
# Protobuf classes
|
30
48
|
# @!parse
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Api
|
2
|
+
# This module make sure that a read from method ability_id always returns the proper source id
|
3
|
+
module AbilityRemapable
|
4
|
+
# Ability Id. The generic id or "remapid".
|
5
|
+
# i.e. Api::AbilityId::ATTACK_BATTLECRUISER returns generic Api::AbilityId::ATTACK
|
6
|
+
# @return [Integer]
|
7
|
+
def ability_id
|
8
|
+
@ability_id ||= Api::AbilityId.generic_id(send(:method_missing, :ability_id))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# AbilityData should not include, since it holds exact info and contains the remap id as attr
|
14
|
+
# Similarly Request* methods only do requests and ew supply correct id's.
|
15
|
+
Api::AvailableAbility.include Api::AbilityRemapable
|
16
|
+
Api::UnitOrder.include Api::AbilityRemapable
|
17
|
+
Api::ActionRawUnitCommand.include Api::AbilityRemapable
|
18
|
+
Api::ActionRawToggleAutocast.include Api::AbilityRemapable
|
19
|
+
Api::ActionError.include Api::AbilityRemapable
|
20
|
+
Api::ActionSpatialUnitCommand.include Api::AbilityRemapable
|
21
|
+
Api::BuildItem.include Api::AbilityRemapable
|
22
|
+
Api::ActionToggleAutocast.include Api::AbilityRemapable
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# This class was partially generated with the help of AI.
|
2
|
+
|
3
|
+
module Api
|
4
|
+
# Adds additional functionality to message object Api::Unit
|
5
|
+
module PointDistanceExtension
|
6
|
+
end
|
7
|
+
end
|
8
|
+
Api::Point.include Api::PointDistanceExtension
|
9
|
+
Api::Point2D.include Api::PointDistanceExtension
|
10
|
+
Api::PointI.include Api::PointDistanceExtension
|
11
|
+
Api::Size2DI.include Api::PointDistanceExtension
|