sc2ai 0.0.5 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|