sc2ai 0.0.5 → 0.0.6
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/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