sc2ai 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/docker_build/Dockerfile.ruby +1 -1
- data/lib/sc2ai/api/data.rb +0 -3
- data/lib/sc2ai/connection/requests.rb +26 -11
- data/lib/sc2ai/player/game_state.rb +8 -5
- data/lib/sc2ai/player/geometry.rb +94 -16
- data/lib/sc2ai/player/previous_state.rb +1 -1
- data/lib/sc2ai/player/units.rb +41 -0
- data/lib/sc2ai/player.rb +3 -1
- data/lib/sc2ai/protocol/extensions/ability_remapable.rb +22 -0
- data/lib/sc2ai/protocol/extensions/point.rb +3 -1
- data/lib/sc2ai/protocol/extensions/position.rb +41 -4
- data/lib/sc2ai/protocol/extensions/unit.rb +25 -0
- data/lib/sc2ai/unit_group/filter_ext.rb +12 -1
- 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/sig/sc2ai.rbs +274 -62
- metadata +9 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 862f9bd2e2dac28e0ffdb6eba214d106598dea2906e315747617c02cb3fa5943
|
4
|
+
data.tar.gz: b86e9e3447dd98b8b2ba41fbdbc400d64d1f26f20a7ee6b6abe01807610c67ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b2d60204bd3d066d76e91db725e182ec2a0c21700c5f1eae5b8818ae8fc3294ad6805e264535a6429054aad7e938dec72763a1e680503334ddcbe5b047c549a
|
7
|
+
data.tar.gz: adc9fadf404d42070faaf3d28d7c90b27487f290bd0d17a60e1ea6390662566707511fc2b9ff5f7836bc3950422e01095e03b7168f23f41794449841088796eb
|
data/lib/sc2ai/api/data.rb
CHANGED
@@ -148,9 +148,6 @@ module Sc2
|
|
148
148
|
unit_data.vespene_cost = 75
|
149
149
|
when Api::UnitTypeId::OVERSEER
|
150
150
|
unit_data.mineral_cost = 50
|
151
|
-
when Api::UnitTypeId::QUEENMP
|
152
|
-
unit_data.mineral_cost = 150
|
153
|
-
unit_data.food_required = 2
|
154
151
|
when Api::UnitTypeId::RAVAGER
|
155
152
|
unit_data.mineral_cost = 25
|
156
153
|
unit_data.vespene_cost = 75
|
@@ -301,21 +301,21 @@ module Sc2
|
|
301
301
|
end
|
302
302
|
|
303
303
|
# Queries one or more pathing queries
|
304
|
-
# @param queries [Array<Api::RequestQueryPathing
|
305
|
-
# @return [Array<Api::ResponseQueryPathing
|
304
|
+
# @param queries [Array<Api::RequestQueryPathing>] one or more pathing queries
|
305
|
+
# @return [Array<Api::ResponseQueryPathing>] one or more results depending on input size
|
306
306
|
def query_pathings(queries)
|
307
307
|
arr_queries = queries.is_a?(Array) ? queries : [queries]
|
308
308
|
|
309
309
|
response = send_request_for query: Api::RequestQuery.new(
|
310
310
|
pathing: arr_queries
|
311
311
|
)
|
312
|
-
|
312
|
+
response.pathing
|
313
313
|
end
|
314
314
|
|
315
315
|
# Queries one or more ability-available checks
|
316
|
-
# @param queries [Array<Api::RequestQueryAvailableAbilities
|
316
|
+
# @param queries [Array<Api::RequestQueryAvailableAbilities>] one or more pathing queries
|
317
317
|
# @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
|
318
|
-
# @return [Array<Api::ResponseQueryAvailableAbilities
|
318
|
+
# @return [Array<Api::ResponseQueryAvailableAbilities>] one or more results depending on input size
|
319
319
|
def query_abilities(queries, ignore_resource_requirements: true)
|
320
320
|
arr_queries = queries.is_a?(Array) ? queries : [queries]
|
321
321
|
|
@@ -323,13 +323,13 @@ module Sc2
|
|
323
323
|
abilities: arr_queries,
|
324
324
|
ignore_resource_requirements:
|
325
325
|
)
|
326
|
-
|
326
|
+
response.abilities
|
327
327
|
end
|
328
328
|
|
329
329
|
# Queries available abilities for units
|
330
|
-
# @param unit_tags [Array<Integer
|
330
|
+
# @param unit_tags [Array<Integer>] an array of unit tags or a single tag
|
331
331
|
# @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
|
332
|
-
# @return [Array<Api::ResponseQueryAvailableAbilities
|
332
|
+
# @return [Array<Api::ResponseQueryAvailableAbilities>] one or more results depending on input size
|
333
333
|
def query_abilities_for_unit_tags(unit_tags, ignore_resource_requirements: true)
|
334
334
|
queries = []
|
335
335
|
unit_tags = [unit_tags] unless unit_tags.is_a? Array
|
@@ -340,15 +340,30 @@ module Sc2
|
|
340
340
|
query_abilities(queries, ignore_resource_requirements:)
|
341
341
|
end
|
342
342
|
|
343
|
+
# Queries available ability ids for one unit
|
344
|
+
# Shortened response over #query_abilities_for_unit_tags, since we know the tag already
|
345
|
+
# and can just return an array of ability ids.
|
346
|
+
# Note: Querying single units are expensive and should be batched with #query_abilities_for_unit_tags
|
347
|
+
# @param unit [Api::Unit, Integer] a unit or a tag.
|
348
|
+
def query_ability_ids_for_unit(unit, ignore_resource_requirements: true)
|
349
|
+
tag = unit.is_a?(Api::Unit) ? unit.tag : unit
|
350
|
+
result = query_abilities_for_unit_tags([tag], ignore_resource_requirements:)
|
351
|
+
if result.nil?
|
352
|
+
[]
|
353
|
+
else
|
354
|
+
result.first.abilities
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
343
358
|
# Queries one or more pathing queries
|
344
|
-
# @param queries [Array<Api::RequestQueryBuildingPlacement
|
345
|
-
# @return [Array<Api::ResponseQueryBuildingPlacement
|
359
|
+
# @param queries [Array<Api::RequestQueryBuildingPlacement>] one or more placement queries
|
360
|
+
# @return [Array<Api::ResponseQueryBuildingPlacement>] one or more results depending on input size
|
346
361
|
def query_placements(queries)
|
347
362
|
arr_queries = queries.is_a?(Array) ? queries : [queries]
|
348
363
|
|
349
364
|
response = query(placements: arr_queries)
|
350
365
|
|
351
|
-
|
366
|
+
response.placements
|
352
367
|
end
|
353
368
|
|
354
369
|
# Generates a replay.
|
@@ -16,9 +16,12 @@ module Sc2
|
|
16
16
|
|
17
17
|
extend Forwardable
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
attr_writer :game_loop
|
20
|
+
|
21
|
+
# @return [Integer] current game loop
|
22
|
+
def game_loop
|
23
|
+
@game_loop || 0
|
24
|
+
end
|
22
25
|
|
23
26
|
# @!attribute game_info [rw]
|
24
27
|
# Access useful game information. Used in parsed pathing grid, terrain height, placement grid.
|
@@ -38,7 +41,7 @@ module Sc2
|
|
38
41
|
attr_accessor :game_info_loop
|
39
42
|
|
40
43
|
# Determines if your game_info will be refreshed at this moment
|
41
|
-
# Has a hard-capped refresh of only ever
|
44
|
+
# Has a hard-capped refresh of only ever 4 steps
|
42
45
|
# In general game_info is only refreshed Player::Bot reads from pathing_grid or placement_grid
|
43
46
|
# @return [Boolean]
|
44
47
|
def game_info_stale?
|
@@ -47,7 +50,7 @@ module Sc2
|
|
47
50
|
|
48
51
|
# Note: No minimum step count set anymore
|
49
52
|
# We can do something like, only updating every 2+ frames:
|
50
|
-
game_info_loop +
|
53
|
+
game_info_loop + 4 <= game_loop
|
51
54
|
end
|
52
55
|
|
53
56
|
# @!attribute data
|
@@ -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,7 @@ 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
32
|
end
|
33
33
|
|
34
34
|
# Returns zero to map_width as range
|
@@ -83,6 +83,15 @@ module Sc2
|
|
83
83
|
@parsed_placement_grid
|
84
84
|
end
|
85
85
|
|
86
|
+
# Whether this tile is where an expansion is supposed to be placed.
|
87
|
+
# To see if a unit/structure is blocking an expansion, pass their coordinates to this method.
|
88
|
+
# @param x [Float, Integer]
|
89
|
+
# @param y [Float, Integer]
|
90
|
+
# @return [Boolean] true if location has creep on it
|
91
|
+
def expo_placement?(x:, y:)
|
92
|
+
expo_placement_grid[y.to_i, x.to_i] == 1
|
93
|
+
end
|
94
|
+
|
86
95
|
# Returns a grid where ony the expo locations are marked
|
87
96
|
# @return [Numo::Bit]
|
88
97
|
def expo_placement_grid
|
@@ -260,6 +269,7 @@ module Sc2
|
|
260
269
|
end
|
261
270
|
|
262
271
|
# Returns the terrain height (z) at position x and y for a point
|
272
|
+
# @param position [Sc2::Position]
|
263
273
|
# @return [Float] z axis position between -16 and 16
|
264
274
|
def terrain_height_for_pos(position)
|
265
275
|
terrain_height(x: position.x, y: position.y)
|
@@ -340,15 +350,17 @@ module Sc2
|
|
340
350
|
end
|
341
351
|
|
342
352
|
# Provides parsed minimap representation of creep spread
|
353
|
+
# Caches for 4 frames
|
343
354
|
# @return [Numo::Bit] Numo array
|
344
355
|
def parsed_creep
|
345
|
-
if @parsed_creep.nil?
|
356
|
+
if @parsed_creep.nil? || @parsed_creep[1] + 4 < bot.game_loop
|
346
357
|
image_data = bot.observation.raw_data.map_state.creep
|
347
358
|
# Fix endian for Numo bit parser
|
348
359
|
data = image_data.data.unpack("b*").pack("B*")
|
349
|
-
|
360
|
+
result = ::Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
|
361
|
+
@parsed_creep = [result, bot.game_loop]
|
350
362
|
end
|
351
|
-
@parsed_creep
|
363
|
+
@parsed_creep[0]
|
352
364
|
end
|
353
365
|
|
354
366
|
# TODO: Removing. Better name or more features for this? Maybe check nearest units.
|
@@ -543,16 +555,75 @@ module Sc2
|
|
543
555
|
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
544
556
|
# @return [Sc2::UnitGroup] UnitGroup of minerals for the base
|
545
557
|
def minerals_for_base(base)
|
546
|
-
|
547
|
-
|
548
|
-
bot.neutral.minerals.
|
558
|
+
base_resources = resources_for_base(base)
|
559
|
+
cached_tags = base_resources.minerals.tags
|
560
|
+
observed_tags = bot.neutral.minerals.tags
|
561
|
+
|
562
|
+
# BACK-STORY: Mineral id's are fixed when in vision.
|
563
|
+
# Snapshots get random id's every time an object leaves vision.
|
564
|
+
# At game launch when we calculate and save minerals, which are mostly snapshot.
|
565
|
+
|
566
|
+
# Currently, we might have moved vision over minerals, so that their id's have changed.
|
567
|
+
# The alive object share a Position with our cached one, so we can get the correct id and update our cache.
|
568
|
+
|
569
|
+
# PERF: Fix takes 0.70ms, cache takes 0.10ms - we mostly call cached. This is the way.
|
570
|
+
# PERF: In contrast, repeated calls to neutral.minerals.units_in_circle? always costs 0.22ms
|
571
|
+
|
572
|
+
missing_tags = cached_tags - observed_tags
|
573
|
+
unless missing_tags.empty?
|
574
|
+
other_alive_minerals = bot.neutral.minerals.slice(*(observed_tags - cached_tags))
|
575
|
+
# For each missing calculated mineral patch...
|
576
|
+
missing_tags.each do |tag|
|
577
|
+
missing_resource = base_resources.delete(tag)
|
578
|
+
|
579
|
+
# Find an alive mineral at that position
|
580
|
+
new_resource = other_alive_minerals.find { |live_mineral| live_mineral.pos == missing_resource.pos }
|
581
|
+
base_resources.add(new_resource) unless new_resource.nil?
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
base_resources.minerals
|
549
586
|
end
|
550
587
|
|
551
588
|
# Gets geysers for a base or base position
|
552
589
|
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
553
590
|
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
554
591
|
def geysers_for_base(base)
|
555
|
-
|
592
|
+
# @see #minerals_for_base for backstory on these fixes
|
593
|
+
base_resources = resources_for_base(base)
|
594
|
+
cached_tags = base_resources.geysers.tags
|
595
|
+
observed_tags = bot.neutral.geysers.tags
|
596
|
+
|
597
|
+
missing_tags = cached_tags - observed_tags
|
598
|
+
unless missing_tags.empty?
|
599
|
+
other_alive_geysers = bot.neutral.geysers.slice(*(observed_tags - cached_tags))
|
600
|
+
# For each missing calculated geyser patch...
|
601
|
+
missing_tags.each do |tag|
|
602
|
+
missing_resource = base_resources.delete(tag)
|
603
|
+
|
604
|
+
# Find an alive geyser at that position
|
605
|
+
new_resource = other_alive_geysers.find { |live_geyser| live_geyser.pos == missing_resource.pos }
|
606
|
+
base_resources.add(new_resource) unless new_resource.nil?
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
base_resources.geysers
|
611
|
+
end
|
612
|
+
|
613
|
+
# Gets geysers which have not been taken for a base or base position
|
614
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
615
|
+
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
616
|
+
def geysers_open_for_base(base)
|
617
|
+
geysers = geysers_for_base(base)
|
618
|
+
|
619
|
+
# Mineral-only base, return nothing
|
620
|
+
return UnitGroup.new if geysers.size == 0
|
621
|
+
|
622
|
+
# Reject all which have a gas structure on-top
|
623
|
+
gas_positions = bot.structures.gas.map { |gas| gas.pos }
|
624
|
+
geysers.reject do |geyser|
|
625
|
+
gas_positions.include?(geyser.pos)
|
626
|
+
end
|
556
627
|
end
|
557
628
|
|
558
629
|
# @private
|
@@ -560,6 +631,7 @@ module Sc2
|
|
560
631
|
# @return [Sc2::UnitGroup] UnitGroup of resources (minerals+geysers)
|
561
632
|
private def resources_for_base(base)
|
562
633
|
pos = base.is_a?(Api::Unit) ? base.pos : base
|
634
|
+
pos = pos.to_p2d if base.is_a?(Api::Point)
|
563
635
|
|
564
636
|
# If we have a base setup for this exact position, use it
|
565
637
|
if expansions.has_key?(pos)
|
@@ -599,12 +671,17 @@ module Sc2
|
|
599
671
|
def build_coordinates(length:, on_creep: false, in_power: false)
|
600
672
|
length = 1 if length < 1
|
601
673
|
@_build_coordinates ||= {}
|
602
|
-
cache_key = [length, on_creep].hash
|
674
|
+
cache_key = [length, on_creep, in_power].hash
|
603
675
|
return @_build_coordinates[cache_key] if !@_build_coordinates[cache_key].nil? && !bot.game_info_stale?
|
604
676
|
|
605
677
|
result = []
|
606
678
|
input_grid = parsed_pathing_grid & parsed_placement_grid & ~expo_placement_grid
|
607
|
-
input_grid =
|
679
|
+
input_grid = if on_creep
|
680
|
+
parsed_creep & input_grid
|
681
|
+
else
|
682
|
+
~parsed_creep & input_grid
|
683
|
+
end
|
684
|
+
|
608
685
|
input_grid = parsed_power_grid & input_grid if in_power
|
609
686
|
|
610
687
|
# Dimensions
|
@@ -657,16 +734,17 @@ module Sc2
|
|
657
734
|
# @param length [Integer] length of the building, 2 for depot/pylon, 3 for rax/gate
|
658
735
|
# @param target [Api::Unit, Sc2::Position] near where to find a placement
|
659
736
|
# @param random [Integer] number of nearest points to randomly choose from. 1 for nearest point.
|
737
|
+
# @param in_power [Boolean] whether this must be on a power field
|
660
738
|
# @return [Api::Point2D, nil] buildable location, nil if no buildable location found
|
661
|
-
def build_placement_near(length:, target:, random: 1)
|
739
|
+
def build_placement_near(length:, target:, random: 1, in_power: false)
|
662
740
|
target = target.pos if target.is_a? Api::Unit
|
663
741
|
random = 1 if random.to_i.negative?
|
664
742
|
length = 1 if length < 1
|
665
743
|
on_creep = bot.race == Api::Race::Zerg
|
666
744
|
|
667
|
-
coordinates = build_coordinates(length:, on_creep:)
|
745
|
+
coordinates = build_coordinates(length:, on_creep:, in_power:)
|
746
|
+
cache_key = coordinates.hash
|
668
747
|
@_build_coordinate_tree ||= {}
|
669
|
-
cache_key = [length, on_creep].hash
|
670
748
|
if @_build_coordinate_tree[cache_key].nil?
|
671
749
|
@_build_coordinate_tree[cache_key] = Kdtree.new(
|
672
750
|
coordinates.each_with_index.map { |coords, index| coords + [index] }
|
@@ -819,14 +897,14 @@ module Sc2
|
|
819
897
|
# @example
|
820
898
|
# Randomly randomly adjust both x and y by a range of -3.5 or +3.5
|
821
899
|
# geo.point_random_near(point: structures.hq.first, offset: 3.5)
|
822
|
-
# @param pos [Sc2::
|
900
|
+
# @param pos [Sc2::Position]
|
823
901
|
# @param offset [Float]
|
824
902
|
# @return [Api::Point2D]
|
825
903
|
def point_random_near(pos:, offset: 1.0)
|
826
904
|
pos.random_offset(offset)
|
827
905
|
end
|
828
906
|
|
829
|
-
# @param pos [Sc2::
|
907
|
+
# @param pos [Sc2::Position]
|
830
908
|
# @param radius [Float]
|
831
909
|
# @return [Api::Point2D]
|
832
910
|
def point_random_on_circle(pos:, radius: 1.0)
|
data/lib/sc2ai/player/units.rb
CHANGED
@@ -77,6 +77,47 @@ module Sc2
|
|
77
77
|
false
|
78
78
|
end
|
79
79
|
|
80
|
+
# For this unit type, tells you how many are in progress by checking orders for all it's sources.
|
81
|
+
# @return [Integer]
|
82
|
+
def units_in_progress(unit_type_id)
|
83
|
+
source_unit_types = Api::TechTree.unit_created_from(unit_type_id: unit_type_id)
|
84
|
+
|
85
|
+
# When building from LARVA, check the intermediate models
|
86
|
+
if source_unit_types.include?(Api::UnitTypeId::LARVA)
|
87
|
+
source_unit_types << Api::UnitTypeId::EGG
|
88
|
+
elsif source_unit_types.include?(Api::UnitTypeId::BANELING)
|
89
|
+
# For certain Zerg types, return the count of specific intermediate egg/cocoon
|
90
|
+
return units.select_type(Api::UnitTypeId::BANELINGCOCOON).size
|
91
|
+
elsif source_unit_types.include?(Api::UnitTypeId::RAVAGER)
|
92
|
+
return units.select_type(Api::UnitTypeId::RAVAGERCOCOON).size
|
93
|
+
elsif source_unit_types.include?(Api::UnitTypeId::OVERSEER)
|
94
|
+
return units.select_type(Api::UnitTypeId::OVERLORDCOCOON).size
|
95
|
+
elsif source_unit_types.include?(Api::UnitTypeId::LURKERMP)
|
96
|
+
return units.select_type(Api::UnitTypeId::LURKERMPEGG).size
|
97
|
+
elsif source_unit_types.include?(Api::UnitTypeId::BROODLORD)
|
98
|
+
return units.select_type(Api::UnitTypeId::BROODLORDCOCOON).size
|
99
|
+
end
|
100
|
+
|
101
|
+
unit_create_ability = Api::TechTree.unit_type_creation_ability_id(
|
102
|
+
source: source_unit_types.first,
|
103
|
+
target: unit_type_id
|
104
|
+
)
|
105
|
+
|
106
|
+
origin = if unit_data(source_unit_types.first).attributes.include?(:Structure)
|
107
|
+
structures
|
108
|
+
else
|
109
|
+
units
|
110
|
+
end
|
111
|
+
total_in_progress = origin.select_type(source_unit_types).sum do |source|
|
112
|
+
source.orders.count do |order|
|
113
|
+
true if order.ability_id == unit_create_ability
|
114
|
+
end
|
115
|
+
end
|
116
|
+
total_in_progress *= 2 if unit_type_id == Api::UnitTypeId::ZERGLING
|
117
|
+
|
118
|
+
total_in_progress
|
119
|
+
end
|
120
|
+
|
80
121
|
# An array of Protoss power sources, which have a point, radius and unit tag
|
81
122
|
# @!attribute power_sources
|
82
123
|
# @return [Array<Api::PowerSource>] an array of power sources
|
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#{@step_count} 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
|
@@ -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
|
@@ -1,12 +1,23 @@
|
|
1
1
|
module Sc2
|
2
2
|
# A unified construct that tames Api::* messages which contain location data
|
3
|
-
# Items which are of type Sc2::
|
3
|
+
# Items which are of type Sc2::Position will have #x and #y property at the least.
|
4
4
|
module Position
|
5
5
|
# Tolerance for floating-point comparisons.
|
6
6
|
TOLERANCE = 1e-9
|
7
7
|
|
8
8
|
# Basic operations
|
9
9
|
|
10
|
+
# Loose equality matches on floats x and y.
|
11
|
+
# We never check z-axis, because the map is single-level.
|
12
|
+
# TODO: We should almost certainly introduce TOLERANCE here, but verify it's cost first.
|
13
|
+
def ==(other)
|
14
|
+
if other.is_a? Position
|
15
|
+
x == other.x && y == other.y
|
16
|
+
else
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
10
21
|
# A new point representing the sum of this point and the other point.
|
11
22
|
# @param other [Api::Point2D, Numeric] The other point/number to add.
|
12
23
|
# @return [Api::Point2D]
|
@@ -50,12 +61,38 @@ module Sc2
|
|
50
61
|
# @see #divide
|
51
62
|
alias_method :/, :divide
|
52
63
|
|
53
|
-
#
|
54
|
-
#
|
64
|
+
# Returns x coordinate
|
65
|
+
# @return [Float]
|
66
|
+
def x
|
67
|
+
# Perf: Memoizing attributes which are hit hard, show gain
|
68
|
+
@x ||= send(:method_missing, :x)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sets x coordinate
|
72
|
+
# @return [Float]
|
73
|
+
def x=(x)
|
74
|
+
send(:method_missing, :x=, x)
|
75
|
+
@x = x
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns y coordinate
|
79
|
+
# @return [Float]
|
55
80
|
def y
|
81
|
+
# Bug: Psych implements method 'y' on Kernel, but protobuf uses method_missing to read AbstractMethod
|
82
|
+
# We send method missing ourselves when y to fix this chain.
|
56
83
|
# This is correct, but an unnecessary conditional:
|
57
84
|
# raise NoMethodError unless location == self
|
58
|
-
|
85
|
+
|
86
|
+
# Perf: Memoizing attributes which are hit hard, show gain
|
87
|
+
|
88
|
+
@y ||= send(:method_missing, :y)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Sets y coordinate
|
92
|
+
# @return [Float]
|
93
|
+
def y=(y)
|
94
|
+
send(:method_missing, :y=, y)
|
95
|
+
@y = y
|
59
96
|
end
|
60
97
|
|
61
98
|
# Randomly adjusts both x and y by a range of: -offset..offset
|
@@ -9,6 +9,22 @@ module Api
|
|
9
9
|
tag || super
|
10
10
|
end
|
11
11
|
|
12
|
+
# Returns an integer unique identifier
|
13
|
+
# If the unit goes out of vision and is snapshot-able, they get a random id
|
14
|
+
# - Such a unit gets the same unit tag when it re-enters vision
|
15
|
+
# @return [Integer]
|
16
|
+
def tag
|
17
|
+
# Perf: This speeds up hash and therefore common UnitGroup operations. Sometimes 3x!
|
18
|
+
@tag ||= send(:method_missing, :tag)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Sets unit tag
|
22
|
+
# @return [Integer]
|
23
|
+
def tag=(tag)
|
24
|
+
send(:method_missing, :tag=, tag)
|
25
|
+
@tag = tag
|
26
|
+
end
|
27
|
+
|
12
28
|
# Every unit gets access back to the bot to allow api access.
|
13
29
|
# For your own units, this allows API access.
|
14
30
|
# @return [Sc2::Player] player with active connection
|
@@ -161,6 +177,15 @@ module Api
|
|
161
177
|
|
162
178
|
# @!endgroup Virtual properties
|
163
179
|
|
180
|
+
# Whether unit is effected by buff_id
|
181
|
+
# @example
|
182
|
+
# unit.has_buff??(Api::BuffId::QUEENSPAWNLARVATIMER)
|
183
|
+
# @param [Integer] buff_id
|
184
|
+
# @return [Boolean]
|
185
|
+
def has_buff?(buff_id)
|
186
|
+
buff_ids.include?(buff_id)
|
187
|
+
end
|
188
|
+
|
164
189
|
# @!group Actions
|
165
190
|
|
166
191
|
# Performs action on this unit
|
@@ -235,6 +235,11 @@ module Sc2
|
|
235
235
|
select(&:is_completed?)
|
236
236
|
end
|
237
237
|
|
238
|
+
# Selects only units which do not have orders
|
239
|
+
def idle
|
240
|
+
select { |unit| unit.orders.empty? }
|
241
|
+
end
|
242
|
+
|
238
243
|
# NEUTRAL ------------------------------------------
|
239
244
|
|
240
245
|
# Selects mineral fields
|
@@ -290,7 +295,13 @@ module Sc2
|
|
290
295
|
# Selects overlords
|
291
296
|
# @return [Sc2::UnitGroup]
|
292
297
|
def overlords
|
293
|
-
select_type([Api::UnitTypeId::OVERLORD, Api::UnitTypeId::
|
298
|
+
select_type([Api::UnitTypeId::OVERLORD, Api::UnitTypeId::OVERLORDTRANSPORT, Api::UnitTypeId::TRANSPORTOVERLORDCOCOON])
|
299
|
+
end
|
300
|
+
|
301
|
+
# Selects overseers
|
302
|
+
# @return [Sc2::UnitGroup]
|
303
|
+
def overseers
|
304
|
+
select_type([Api::UnitTypeId::OVERLORDCOCOON, Api::UnitTypeId::OVERSEER, Api::UnitTypeId::OVERSEERSIEGEMODE])
|
294
305
|
end
|
295
306
|
|
296
307
|
# Selects creep tumors (all)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sc2ai/unit_group"
|
4
|
+
|
5
|
+
module Sc2
|
6
|
+
# A set geometric/map/math methods for unit group
|
7
|
+
class UnitGroup
|
8
|
+
# Returns the center (average) position of all units or nil if the group is empty.
|
9
|
+
# Outliers effect this point
|
10
|
+
# @return [Api::Point2D, nil]
|
11
|
+
def pos_centroid
|
12
|
+
return nil if size == 0
|
13
|
+
size = @units.size
|
14
|
+
sum_x = 0.0
|
15
|
+
sum_y = 0.0
|
16
|
+
i = 0
|
17
|
+
|
18
|
+
while i < size
|
19
|
+
unit = at(i)
|
20
|
+
sum_x += unit.pos.x.to_f
|
21
|
+
sum_y += unit.pos.y.to_f
|
22
|
+
i += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
Api::Point2D[sum_x / size.to_f, sum_y / size.to_f]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/sc2ai/unit_group.rb
CHANGED
@@ -182,6 +182,8 @@ module Sc2
|
|
182
182
|
UnitGroup.new(@units.reject { |tag, _unit| other_unit_group.units.has_key?(tag) })
|
183
183
|
end
|
184
184
|
|
185
|
+
alias_method :-, :subtract
|
186
|
+
|
185
187
|
# Merges unit_group with our units and returns a new unit group
|
186
188
|
# @return [Sc2::UnitGroup] a new unit group with items merged
|
187
189
|
def merge(unit_group)
|
@@ -295,3 +297,4 @@ end
|
|
295
297
|
|
296
298
|
require_relative "unit_group/action_ext"
|
297
299
|
require_relative "unit_group/filter_ext"
|
300
|
+
require_relative "unit_group/geo_ext"
|
data/lib/sc2ai/version.rb
CHANGED