sc2ai 0.0.0.pre → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/data/data.json +1 -0
- data/data/data_readable.json +22842 -0
- data/data/sc2ai/protocol/common.proto +59 -0
- data/data/sc2ai/protocol/data.proto +120 -0
- data/data/sc2ai/protocol/debug.proto +127 -0
- data/data/sc2ai/protocol/error.proto +221 -0
- data/data/sc2ai/protocol/query.proto +55 -0
- data/data/sc2ai/protocol/raw.proto +202 -0
- data/data/sc2ai/protocol/sc2api.proto +718 -0
- data/data/sc2ai/protocol/score.proto +108 -0
- data/data/sc2ai/protocol/spatial.proto +115 -0
- data/data/sc2ai/protocol/ui.proto +145 -0
- data/data/setup/setup.SC2Map +0 -0
- data/data/setup/setup.SC2Replay +0 -0
- data/data/stableid.json +35730 -0
- data/data/versions.json +554 -0
- data/exe/sc2ai +35 -0
- data/lib/docker_build/Dockerfile.ruby +74 -0
- data/lib/docker_build/docker-compose-base-image.yml +10 -0
- data/lib/docker_build/docker-compose-ladderzip.yml +9 -0
- data/lib/sc2ai/api/ability_id.rb +1644 -0
- data/lib/sc2ai/api/buff_id.rb +306 -0
- data/lib/sc2ai/api/data.rb +101 -0
- data/lib/sc2ai/api/effect_id.rb +20 -0
- data/lib/sc2ai/api/tech_tree.rb +83 -0
- data/lib/sc2ai/api/tech_tree_data.rb +2338 -0
- data/lib/sc2ai/api/unit_type_id.rb +2022 -0
- data/lib/sc2ai/api/upgrade_id.rb +310 -0
- data/lib/sc2ai/cli/cli.rb +175 -0
- data/lib/sc2ai/cli/ladderzip.rb +154 -0
- data/lib/sc2ai/cli/new.rb +88 -0
- data/lib/sc2ai/configuration.rb +145 -0
- data/lib/sc2ai/connection/connection_listener.rb +30 -0
- data/lib/sc2ai/connection/requests.rb +417 -0
- data/lib/sc2ai/connection/status_listener.rb +15 -0
- data/lib/sc2ai/connection.rb +146 -0
- data/lib/sc2ai/local_play/client/configurable_options.rb +115 -0
- data/lib/sc2ai/local_play/client.rb +159 -0
- data/lib/sc2ai/local_play/client_manager.rb +70 -0
- data/lib/sc2ai/local_play/map_file.rb +48 -0
- data/lib/sc2ai/local_play/match.rb +184 -0
- data/lib/sc2ai/overrides/array.rb +14 -0
- data/lib/sc2ai/overrides/async/process/child.rb +31 -0
- data/lib/sc2ai/overrides/kernel.rb +33 -0
- data/lib/sc2ai/paths.rb +294 -0
- data/lib/sc2ai/player/actions.rb +386 -0
- data/lib/sc2ai/player/debug.rb +224 -0
- data/lib/sc2ai/player/game_state.rb +131 -0
- data/lib/sc2ai/player/geometry.rb +766 -0
- data/lib/sc2ai/player/previous_state.rb +49 -0
- data/lib/sc2ai/player/units.rb +337 -0
- data/lib/sc2ai/player.rb +661 -0
- data/lib/sc2ai/ports.rb +152 -0
- data/lib/sc2ai/protocol/_meta_documentation.rb +39 -0
- data/lib/sc2ai/protocol/common_pb.rb +43 -0
- data/lib/sc2ai/protocol/data_pb.rb +47 -0
- data/lib/sc2ai/protocol/debug_pb.rb +56 -0
- data/lib/sc2ai/protocol/error_pb.rb +36 -0
- data/lib/sc2ai/protocol/extensions/color.rb +20 -0
- data/lib/sc2ai/protocol/extensions/point.rb +23 -0
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +26 -0
- data/lib/sc2ai/protocol/extensions/position.rb +202 -0
- data/lib/sc2ai/protocol/extensions/power_source.rb +19 -0
- data/lib/sc2ai/protocol/extensions/unit.rb +489 -0
- data/lib/sc2ai/protocol/query_pb.rb +47 -0
- data/lib/sc2ai/protocol/raw_pb.rb +57 -0
- data/lib/sc2ai/protocol/sc2api_pb.rb +130 -0
- data/lib/sc2ai/protocol/score_pb.rb +40 -0
- data/lib/sc2ai/protocol/spatial_pb.rb +48 -0
- data/lib/sc2ai/protocol/ui_pb.rb +56 -0
- data/lib/sc2ai/unit_group/action_ext.rb +74 -0
- data/lib/sc2ai/unit_group/filter_ext.rb +379 -0
- data/lib/sc2ai/unit_group.rb +277 -0
- data/lib/sc2ai/version.rb +2 -1
- data/lib/sc2ai.rb +93 -2
- data/lib/templates/ladderzip/bin/ladder.tt +23 -0
- data/lib/templates/new/.ladderignore +20 -0
- data/lib/templates/new/Gemfile.tt +7 -0
- data/lib/templates/new/api/common.proto +59 -0
- data/lib/templates/new/api/data.proto +120 -0
- data/lib/templates/new/api/debug.proto +127 -0
- data/lib/templates/new/api/error.proto +221 -0
- data/lib/templates/new/api/query.proto +55 -0
- data/lib/templates/new/api/raw.proto +202 -0
- data/lib/templates/new/api/sc2api.proto +718 -0
- data/lib/templates/new/api/score.proto +108 -0
- data/lib/templates/new/api/spatial.proto +115 -0
- data/lib/templates/new/api/ui.proto +145 -0
- data/lib/templates/new/boot.rb.tt +6 -0
- data/lib/templates/new/my_bot.rb.tt +23 -0
- data/lib/templates/new/run_example_match.rb.tt +14 -0
- data/sc2ai.gemspec +80 -0
- metadata +344 -13
@@ -0,0 +1,202 @@
|
|
1
|
+
module Sc2
|
2
|
+
# A unified construct that tames Api::* messages which contain location data
|
3
|
+
# Items which are of type Sc2::Location will have #x and #y property at the least.
|
4
|
+
module Position
|
5
|
+
# Tolerance for floating-point comparisons.
|
6
|
+
TOLERANCE = 1e-9
|
7
|
+
|
8
|
+
# Basic operations
|
9
|
+
|
10
|
+
# A new point representing the sum of this point and the other point.
|
11
|
+
# @param other [Api::Point2D, Numeric] The other point/number to add.
|
12
|
+
# @return [Api::Point2D]
|
13
|
+
def add(other)
|
14
|
+
if other.is_a? Numeric
|
15
|
+
Api::Point2D[x + other, y + other]
|
16
|
+
else
|
17
|
+
Api::Point2D[x + other.x, y + other.y]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
alias_method :+, :add
|
21
|
+
|
22
|
+
# Returns a new point representing the difference between this point and the other point/number.
|
23
|
+
# @param other [Api::Point2D, Numeric] The other to subtract.
|
24
|
+
# @return [Api::Point2D]
|
25
|
+
def subtract(other)
|
26
|
+
if other.is_a? Numeric
|
27
|
+
Api::Point2D[x - other, y - other]
|
28
|
+
else
|
29
|
+
Api::Point2D[x - other.x, y - other.y]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
alias_method :-, :subtract
|
33
|
+
|
34
|
+
# Returns this point multiplied by the scalar
|
35
|
+
# @param scalar [Float] The scalar to multiply by.
|
36
|
+
# @return [Api::Point2D]
|
37
|
+
def multiply(scalar)
|
38
|
+
Api::Point2D[x * scalar, y * scalar]
|
39
|
+
end
|
40
|
+
# @see #divide
|
41
|
+
alias_method :*, :multiply
|
42
|
+
|
43
|
+
# @param scalar [Float] The scalar to divide by.
|
44
|
+
# @return [Api::Point2D] A new point representing this point divided by the scalar.
|
45
|
+
# @raise [ZeroDivisionError] if the scalar is zero.
|
46
|
+
def divide(scalar)
|
47
|
+
raise ZeroDivisionError if scalar.zero?
|
48
|
+
Api::Point2D[x / scalar, y / scalar]
|
49
|
+
end
|
50
|
+
# @see #divide
|
51
|
+
alias_method :/, :divide
|
52
|
+
|
53
|
+
# Bug: Psych implements method 'y' on Kernel, but protobuf uses method_missing to read AbstractMethod
|
54
|
+
# We send method missing ourselves when y to fix this chain.
|
55
|
+
def y
|
56
|
+
# This is correct, but an unnecessary conditional:
|
57
|
+
# raise NoMethodError unless location == self
|
58
|
+
send(:method_missing, :y)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Randomly adjusts both x and y by a range of: -offset..offset
|
62
|
+
# @param offset [Float]
|
63
|
+
# @return [Api::Point2D]
|
64
|
+
def random_offset(offset)
|
65
|
+
Api::Point2D.new[x, y].random_offset!(offset)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Changes this point's x and y by the supplied offset
|
69
|
+
# @return [Sc2::Position] self
|
70
|
+
def random_offset!(offset)
|
71
|
+
offset = offset.to_f
|
72
|
+
range = rand(-offset..offset)
|
73
|
+
offset!(rand(range), rand(range))
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# Creates a new point with x and y which is offset
|
78
|
+
# @return [Api::Point2D] self
|
79
|
+
def offset(x, y)
|
80
|
+
Api::Point2D.new[x, y].offset!(x, y)
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
# Changes this point's x and y by the supplied offset
|
85
|
+
# @return [Sc2::Position] self
|
86
|
+
def offset!(x, y)
|
87
|
+
self.x -= x
|
88
|
+
self.y -= y
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Vector operations ---
|
93
|
+
|
94
|
+
# For vector returns the magnitude, synonymous with Math.hypot
|
95
|
+
# @return [Float]
|
96
|
+
def magnitude
|
97
|
+
Math.hypot(x, y)
|
98
|
+
end
|
99
|
+
|
100
|
+
# The dot product of this vector and the other vector.
|
101
|
+
# @param other [Api::Point2D] The other vector to calculate the dot product with.
|
102
|
+
# @return [Float]
|
103
|
+
def dot(other)
|
104
|
+
x * other.x + y * other.y
|
105
|
+
end
|
106
|
+
|
107
|
+
# The cross product of this vector and the other vector.
|
108
|
+
# @param other [Api::Point2D] The other vector to calculate the cross product with.
|
109
|
+
# @return [Float]
|
110
|
+
def cross_product(other)
|
111
|
+
x * other.y - y * other.x
|
112
|
+
end
|
113
|
+
|
114
|
+
# The angle between this vector and the other vector, in radians.
|
115
|
+
# @param other [Api::Point2D] The other vector to calculate the angle to.
|
116
|
+
# @return [Float]
|
117
|
+
def angle_to(other)
|
118
|
+
Math.acos(dot(other) / (magnitude * other.magnitude))
|
119
|
+
end
|
120
|
+
|
121
|
+
# A new point representing the normalized version of this vector (unit length).
|
122
|
+
# @return [Api::Point2D]
|
123
|
+
def normalize
|
124
|
+
divide(magnitude)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Other methods ---
|
128
|
+
|
129
|
+
# Linear interpolation between this point and another for scale
|
130
|
+
# Finds a point on a line between two points at % along the way. 0.0 returns self, 1.0 returns other, 0.5 is halfway.
|
131
|
+
# @param scale [Float] a value between 0.0..1.0
|
132
|
+
# @return [Api::Point2D]
|
133
|
+
def lerp(other, scale)
|
134
|
+
Api::Point2D[x + (other.x - x) * scale, y + (other.y - y) * scale]
|
135
|
+
end
|
136
|
+
|
137
|
+
# Distance calculations ---
|
138
|
+
|
139
|
+
# Calculates the distance between self and other
|
140
|
+
# @param other [Sc2::Position]
|
141
|
+
# @return [Float]
|
142
|
+
def distance_to(other)
|
143
|
+
if other.nil? || other == self
|
144
|
+
return 0.0
|
145
|
+
end
|
146
|
+
Math.hypot(self.x - other.x, self.y - other.y)
|
147
|
+
end
|
148
|
+
|
149
|
+
# The squared distance between this point and the other point.
|
150
|
+
# @param other [Point2D] The other point to calculate the squared distance to.
|
151
|
+
# @return [Float]
|
152
|
+
def distance_squared_to(other)
|
153
|
+
if other.nil? || other == self
|
154
|
+
return 0.0
|
155
|
+
end
|
156
|
+
(x - other.x) * (y - other.y)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Distance between this point and coordinate of x and y
|
160
|
+
# @return [Float]
|
161
|
+
def distance_to_coordinate(x:, y:)
|
162
|
+
Math.hypot(self.x - x, self.y - y)
|
163
|
+
end
|
164
|
+
|
165
|
+
# The distance from this point to the circle.
|
166
|
+
# @param center [Point2D] The center of the circle.
|
167
|
+
# @param radius [Float] The radius of the circle.
|
168
|
+
# @return [Float]
|
169
|
+
def distance_to_circle(center, radius)
|
170
|
+
distance_to_center = distance_to(center)
|
171
|
+
if distance_to_center <= radius
|
172
|
+
0.0 # Point is inside the circle
|
173
|
+
else
|
174
|
+
distance_to_center - radius
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Movement ---
|
179
|
+
|
180
|
+
# Moves in direction towards other point by distance
|
181
|
+
# @param other [Api::Point2D] The target point to move to.
|
182
|
+
# @param distance [Float] The distance to move.
|
183
|
+
# @return [Api::Point2D]
|
184
|
+
def towards(other, distance)
|
185
|
+
direction = other.subtract(self).normalize
|
186
|
+
add(direction.multiply(distance))
|
187
|
+
end
|
188
|
+
|
189
|
+
# Moves in direction away from the other point by distance
|
190
|
+
# @param other [Api::Point2D] The target point to move away from
|
191
|
+
# @param distance [Float] The distance to move.
|
192
|
+
# @return [Api::Point2D]
|
193
|
+
def away_from(other, distance)
|
194
|
+
towards(other, -distance)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
Api::Point.include Sc2::Position
|
200
|
+
Api::Point2D.include Sc2::Position
|
201
|
+
Api::PointI.include Sc2::Position
|
202
|
+
Api::Size2DI.include Sc2::Position
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Api
|
2
|
+
# Adds additional functionality to message object Api::PowerSource
|
3
|
+
module PowerSourceExtension
|
4
|
+
include Sc2::Position
|
5
|
+
|
6
|
+
# Adds additional functionality to message class Api::PowerSource
|
7
|
+
module ClassMethods
|
8
|
+
# Shorthand for creating an instance for [x, y, z]
|
9
|
+
# @example
|
10
|
+
# Api::Point[1,2,3] # Where x is 1.0, y is 2.0 and z is 3.0
|
11
|
+
# @return [Api::Point]
|
12
|
+
def [](x, y, z)
|
13
|
+
Api::Point.new(x: x, y: y, z: z)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
Api::PowerSource.include Api::PowerSourceExtension
|
19
|
+
Api::PowerSource.extend Api::PowerSourceExtension::ClassMethods
|
@@ -0,0 +1,489 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Api
|
4
|
+
# Adds additional functionality to message object Api::Unit
|
5
|
+
# Mostly adds convenience methods by adding direct access to the Sc2::Bot data and api
|
6
|
+
module UnitExtension
|
7
|
+
# @private
|
8
|
+
def hash
|
9
|
+
tag || super
|
10
|
+
end
|
11
|
+
|
12
|
+
# Every unit gets access back to the bot to allow api access.
|
13
|
+
# For your own units, this allows API access.
|
14
|
+
# @return [Sc2::Player] player with active connection
|
15
|
+
attr_accessor :bot
|
16
|
+
|
17
|
+
# Returns static [Api::UnitTypeData] for a unit
|
18
|
+
# @return [Api::UnitTypeData]
|
19
|
+
def unit_data
|
20
|
+
@bot.data.units[unit_type]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get the unit as from the previous frame. Good for comparison.
|
24
|
+
# @return [Api::Unit, nil] this unit from the previous frame or nil if it wasn't present
|
25
|
+
def previous
|
26
|
+
@bot.previous.all_units[tag]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Attributes ---
|
30
|
+
|
31
|
+
# Returns static [Api::UnitTypeData] for a unit
|
32
|
+
# @return [Array<Api::Attribute>]
|
33
|
+
def attributes
|
34
|
+
unit_data.attributes
|
35
|
+
end
|
36
|
+
|
37
|
+
# Checks unit data for an attribute value
|
38
|
+
# @return [Boolean] whether unit has attribute
|
39
|
+
# @example
|
40
|
+
# has_attribute?(Api::UnitTypeId::SCV, Api::Attribute::Mechanical)
|
41
|
+
# has_attribute?(units.workers.first, :Mechanical)
|
42
|
+
# has_attribute?(Api::UnitTypeId::SCV, :Mechanical)
|
43
|
+
def has_attribute?(attribute)
|
44
|
+
attributes.include? attribute
|
45
|
+
end
|
46
|
+
|
47
|
+
# Checks if unit is light
|
48
|
+
# @return [Boolean] whether unit has attribute :Light
|
49
|
+
def is_light?
|
50
|
+
has_attribute?(:Light)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Checks if unit is armored
|
54
|
+
# @return [Boolean] whether unit has attribute :Armored
|
55
|
+
def is_armored?
|
56
|
+
has_attribute?(:Armored)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Checks if unit is biological
|
60
|
+
# @return [Boolean] whether unit has attribute :Biological
|
61
|
+
def is_biological?
|
62
|
+
has_attribute?(:Biological)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Checks if unit is mechanical
|
66
|
+
# @return [Boolean] whether unit has attribute :Mechanical
|
67
|
+
def is_mechanical?
|
68
|
+
has_attribute?(:Mechanical)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Checks if unit is robotic
|
72
|
+
# @return [Boolean] whether unit has attribute :Robotic
|
73
|
+
def is_robotic?
|
74
|
+
has_attribute?(:Robotic)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Checks if unit is psionic
|
78
|
+
# @return [Boolean] whether unit has attribute :Psionic
|
79
|
+
def is_psionic?
|
80
|
+
has_attribute?(:Psionic)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Checks if unit is massive
|
84
|
+
# @return [Boolean] whether unit has attribute :Massive
|
85
|
+
def is_massive?
|
86
|
+
has_attribute?(:Massive)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Checks if unit is structure
|
90
|
+
# @return [Boolean] whether unit has attribute :Structure
|
91
|
+
def is_structure?
|
92
|
+
has_attribute?(:Structure)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Checks if unit is hover
|
96
|
+
# @return [Boolean] whether unit has attribute :Hover
|
97
|
+
def is_hover?
|
98
|
+
has_attribute?(:Hover)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Checks if unit is heroic
|
102
|
+
# @return [Boolean] whether unit has attribute :Heroic
|
103
|
+
def is_heroic?
|
104
|
+
has_attribute?(:Heroic)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Checks if unit is summoned
|
108
|
+
# @return [Boolean] whether unit has attribute :Summoned
|
109
|
+
def is_summoned?
|
110
|
+
has_attribute?(:Summoned)
|
111
|
+
end
|
112
|
+
|
113
|
+
# @!group Virtual properties
|
114
|
+
|
115
|
+
# Helpers for unit properties
|
116
|
+
|
117
|
+
def width = radius * 2
|
118
|
+
# @!parse
|
119
|
+
# # @!attribute width
|
120
|
+
# # width = radius * 2
|
121
|
+
# # @return [Float]
|
122
|
+
# attr_reader :width
|
123
|
+
|
124
|
+
# Some overrides to allow question mark references to boolean properties
|
125
|
+
|
126
|
+
# @!attribute [r] is_flying?
|
127
|
+
# @return [Boolean] Unit is currently flying.
|
128
|
+
def is_flying? = is_flying
|
129
|
+
|
130
|
+
# @!attribute [r] is_burrowed?
|
131
|
+
# @return [Boolean] Zerg burrowed ability active on unit.
|
132
|
+
def is_burrowed? = is_burrowed
|
133
|
+
|
134
|
+
# @!attribute [r] is_hallucination?
|
135
|
+
# @return [Boolean] Unit is your own or detected as a hallucination.
|
136
|
+
def is_hallucination? = is_hallucination
|
137
|
+
|
138
|
+
# @!attribute [r] is_selected?
|
139
|
+
# @return [Boolean] Whether unit is selected visually or on Feature layer.
|
140
|
+
def is_selected? = is_selected
|
141
|
+
|
142
|
+
# @!attribute [r] is_on_screen?
|
143
|
+
# @return [Boolean] Visible and within the camera frustrum.
|
144
|
+
def is_on_screen? = is_on_screen
|
145
|
+
|
146
|
+
# @!attribute [r] is_blip?
|
147
|
+
# @return [Boolean] Detected by sensor tower
|
148
|
+
def is_blip? = is_blip
|
149
|
+
|
150
|
+
# @!attribute [r] is_powered?
|
151
|
+
# @return [Boolean] Protoss building is powered by a source.
|
152
|
+
def is_powered? = is_powered
|
153
|
+
|
154
|
+
# @!attribute [r] is_active?
|
155
|
+
# @return [Boolean] Building is training/researching (i.e. animated).
|
156
|
+
def is_active? = is_active
|
157
|
+
|
158
|
+
# @!attribute [r] is_ground?
|
159
|
+
# Returns whether the unit is grounded (not flying).
|
160
|
+
# @return [Boolean]
|
161
|
+
def is_ground? = !is_flying?
|
162
|
+
|
163
|
+
# @!endgroup Virtual properties
|
164
|
+
|
165
|
+
# @!group Actions
|
166
|
+
|
167
|
+
# Performs action on this unit
|
168
|
+
# @param ability_id [Integer]
|
169
|
+
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
170
|
+
# @param queue_command [Boolean] shift+command
|
171
|
+
def action(ability_id:, target: nil, queue_command: false)
|
172
|
+
@bot.action(units: self, ability_id:, target:, queue_command:)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Shorthand for performing action SMART (right-click)
|
176
|
+
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
177
|
+
# @param queue_command [Boolean] shift+command
|
178
|
+
def smart(target: nil, queue_command: false)
|
179
|
+
action(ability_id: Api::AbilityId::SMART, target:, queue_command:)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Shorthand for performing action ATTACK
|
183
|
+
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
184
|
+
# @param queue_command [Boolean] shift+command
|
185
|
+
def attack(target:, queue_command: false)
|
186
|
+
action(ability_id: Api::AbilityId::ATTACK, target:, queue_command:)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Inverse of #attack, where you target self using another unit (source_unit)
|
190
|
+
# @param units [Api::Unit, Sc2::UnitGroup] a unit or unit group
|
191
|
+
# @param queue_command [Boolean] shift+command
|
192
|
+
# @return [void]
|
193
|
+
def attack_with(units:, queue_command: false)
|
194
|
+
return unless units.is_a?(Api::Unit) || units.is_a?(Sc2::UnitGroup)
|
195
|
+
|
196
|
+
units.attack(target: self, queue_command:)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Builds target unit type, i.e. issuing a build command to worker.build(...Api::UnitTypeId::BARRACKS)
|
200
|
+
# @param unit_type_id [Integer] Api::UnitTypeId the unit type which will do the creation
|
201
|
+
# @param target [Api::Point2D, Integer, nil] is a unit tag or a Api::Point2D. Nil for addons/orbital
|
202
|
+
# @param queue_command [Boolean] shift+command
|
203
|
+
def build(unit_type_id:, target: nil, queue_command: false)
|
204
|
+
@bot.build(units: self, unit_type_id:, target:, queue_command:)
|
205
|
+
end
|
206
|
+
|
207
|
+
# This structure creates a unit, i.e. this Barracks trains a Marine
|
208
|
+
# @see #build
|
209
|
+
alias_method :train, :build
|
210
|
+
# def train(unit_type_id:, target: nil, queue_command: false)
|
211
|
+
# @bot.build(units: self, unit_type_id:, target:, queue_command:)
|
212
|
+
# end
|
213
|
+
|
214
|
+
# Issues repair command on target
|
215
|
+
# @param target [Api::Unit, Integer] is a unit or unit tag
|
216
|
+
def repair(target:, queue_command: false)
|
217
|
+
action(ability_id: Api::AbilityId::EFFECT_REPAIR, target:, queue_command:)
|
218
|
+
end
|
219
|
+
|
220
|
+
# @!endgroup Actions
|
221
|
+
#
|
222
|
+
# Debug ----
|
223
|
+
|
224
|
+
# Draws a placement outline
|
225
|
+
# @param color [Api::Color] optional api color, default white
|
226
|
+
# @return [void]
|
227
|
+
def debug_draw_placement(color = nil)
|
228
|
+
# Slightly elevate the Z position so that the line doesn't clip into the terrain at same Z level
|
229
|
+
z_elevated = pos.z + 0.01
|
230
|
+
offset = footprint_radius
|
231
|
+
# Box corners
|
232
|
+
p0 = Api::Point.new(x: pos.x - offset, y: pos.y - offset, z: z_elevated)
|
233
|
+
p1 = Api::Point.new(x: pos.x - offset, y: pos.y + offset, z: z_elevated)
|
234
|
+
p2 = Api::Point.new(x: pos.x + offset, y: pos.y + offset, z: z_elevated)
|
235
|
+
p3 = Api::Point.new(x: pos.x + offset, y: pos.y - offset, z: z_elevated)
|
236
|
+
@bot.queue_debug_command Api::DebugCommand.new(
|
237
|
+
draw: Api::DebugDraw.new(
|
238
|
+
lines: [
|
239
|
+
Api::DebugLine.new(
|
240
|
+
color:,
|
241
|
+
line: Api::Line.new(p0:, p1:)
|
242
|
+
),
|
243
|
+
Api::DebugLine.new(
|
244
|
+
color:,
|
245
|
+
line: Api::Line.new(p0: p2, p1: p3)
|
246
|
+
),
|
247
|
+
Api::DebugLine.new(
|
248
|
+
color:,
|
249
|
+
line: Api::Line.new(p0:, p1: p3)
|
250
|
+
),
|
251
|
+
Api::DebugLine.new(
|
252
|
+
color:,
|
253
|
+
line: Api::Line.new(p0: p1, p1: p2)
|
254
|
+
)
|
255
|
+
]
|
256
|
+
)
|
257
|
+
)
|
258
|
+
end
|
259
|
+
|
260
|
+
# Draws a sphere around the unit's attack range
|
261
|
+
# @param weapon_index [Api::Color] default first weapon, see UnitTypeData.weapons
|
262
|
+
# @param color [Api::Color] optional api color, default red
|
263
|
+
def debug_fire_range(weapon_index = 0, color = nil)
|
264
|
+
color = Api::Color.new(r: 255, b: 0, g: 0) if color.nil?
|
265
|
+
attack_range = unit_data.weapons[weapon_index].range
|
266
|
+
raised_position = pos.dup
|
267
|
+
raised_position.z += 0.01
|
268
|
+
@bot.debug_draw_sphere(point: raised_position, radius: attack_range, color:)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Geometric/Map/Micro functions ---
|
272
|
+
|
273
|
+
# Calculates the distance between self and other
|
274
|
+
# @param other [Sc2::Position, Api::Unit, Api::PowerSource, Api::RadarRing, Api::Effect]
|
275
|
+
def distance_to(other)
|
276
|
+
return 0.0 if other.nil? || other == self
|
277
|
+
|
278
|
+
other = other.pos unless other.is_a? Sc2::Position
|
279
|
+
pos.distance_to(other)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Gets the nearest amount of unit(s) from a group, relative to this unit
|
283
|
+
# Omitting amount returns a single Unit.
|
284
|
+
# @param units [Sc2::UnitGroup]
|
285
|
+
# @param amount [Integer]
|
286
|
+
# @return [Sc2::UnitGroup, Api::Unit, nil] return group or a Unit if amount is not passed
|
287
|
+
def nearest(units:, amount: nil)
|
288
|
+
amount = 1 if !amount.nil? && amount.to_i <= 0
|
289
|
+
|
290
|
+
# Performs suboptimal if sending an array. Don't.
|
291
|
+
if units.is_a? Array
|
292
|
+
units = Sc2::UnitGroup.new(units)
|
293
|
+
units.use_kdtree = false # we will not re-use it's distance cache
|
294
|
+
end
|
295
|
+
|
296
|
+
units.nearest_to(pos:, amount:)
|
297
|
+
end
|
298
|
+
|
299
|
+
# Detects whether a unit is within a given circle
|
300
|
+
# @param point [Api::Point2D, Api::Point]
|
301
|
+
def in_circle?(point:, radius:)
|
302
|
+
distance_to(point) <= radius
|
303
|
+
end
|
304
|
+
|
305
|
+
# Returns whether unit is currently engaged with another
|
306
|
+
# @param target [Api::Unit, Integer] optionally check if unit is engaged with specific target
|
307
|
+
def is_attacking?(target: nil)
|
308
|
+
is_performing_ability_on_target?(
|
309
|
+
[Api::AbilityId::ATTACK_ATTACK],
|
310
|
+
target:
|
311
|
+
)
|
312
|
+
end
|
313
|
+
|
314
|
+
# Returns whether the unit's current order is to repair and optionally check it's target
|
315
|
+
# @param target [Api::Unit, Integer] optionally check if unit is engaged with specific target
|
316
|
+
# @return [Boolean]
|
317
|
+
def is_repairing?(target: nil)
|
318
|
+
is_performing_ability_on_target?(
|
319
|
+
[Api::AbilityId::EFFECT_REPAIR, Api::AbilityId::EFFECT_REPAIR_SCV, Api::AbilityId::EFFECT_REPAIR_MULE],
|
320
|
+
target:
|
321
|
+
)
|
322
|
+
end
|
323
|
+
|
324
|
+
# Checks whether the unit has
|
325
|
+
# @param ability_ids [Integer, Array<Integer>] accepts one or an array of Api::AbilityId
|
326
|
+
def is_performing_ability?(ability_ids)
|
327
|
+
return false if orders.empty?
|
328
|
+
|
329
|
+
if ability_ids.is_a? Array
|
330
|
+
ability_ids.include?(orders.first&.ability_id)
|
331
|
+
else
|
332
|
+
ability_ids == orders.first&.ability_id
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Returns whether engaged_target_tag or the current order matches supplied unit
|
337
|
+
# @param unit [Api::Unit, Integer] optionally check if unit is engaged with specific target
|
338
|
+
# @return [Boolean]
|
339
|
+
def is_engaged_with?(unit)
|
340
|
+
# First match on unit#engaged_target_tag, since it's solid for attacks
|
341
|
+
unit = unit.tag if unit.is_a? Api::Unit
|
342
|
+
return true if engaged_target_tag == unit
|
343
|
+
|
344
|
+
# Alternatively, check your order to see if your command ties you to the unit
|
345
|
+
# It may not be in distance or actively performing, just yet.
|
346
|
+
return orders.first.target_unit_tag == unit unless orders.empty?
|
347
|
+
|
348
|
+
false
|
349
|
+
end
|
350
|
+
|
351
|
+
# Checks whether enemy is within range of weapon or ability and can target ground/air.
|
352
|
+
# Defaults to basic weapon. Pass in ability to override
|
353
|
+
# @param unit [Api::Unit] enemy
|
354
|
+
# @param weapon_index [Integer] defaults to 0 which is it's basic weapon for it's current form
|
355
|
+
# @param ability_id [Integer] passing this will override weapon Api::AbilityId::*
|
356
|
+
# @return [Boolean]
|
357
|
+
# @example
|
358
|
+
# ghost.can_attack?(enemy, weapon_index: 0, ability_id: Api::AbilityId::SNIPE)
|
359
|
+
def can_attack?(unit:, weapon_index: 0, ability_id: nil)
|
360
|
+
if ability_id.nil?
|
361
|
+
# weapon
|
362
|
+
source_weapon = weapon(weapon_index)
|
363
|
+
can_weapon_target_unit?(unit:, weapon: source_weapon)
|
364
|
+
else
|
365
|
+
# ability
|
366
|
+
ability = @bot.ability_data(ability_id)
|
367
|
+
can_ability_target_unit?(unit:, ability:)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# Checks whether a weapon can target a unit
|
372
|
+
# @param unit [Api::unit]
|
373
|
+
# @param weapon [Api::Weapon]
|
374
|
+
# @return [Boolean]
|
375
|
+
def can_weapon_target_unit?(unit:, weapon:)
|
376
|
+
# false if enemy is air and we can only shoot ground
|
377
|
+
return false if unit.is_flying && weapon.type == :Ground # Api::Weapon::TargetType::Ground
|
378
|
+
|
379
|
+
# false if enemy is ground and we can only shoot air
|
380
|
+
return false if unit.is_ground? && weapon.type == :Air # A pi::Weapon::TargetType::Air
|
381
|
+
|
382
|
+
# Check if weapon and unit models are in range
|
383
|
+
in_attack_range?(unit:, range: weapon.range)
|
384
|
+
end
|
385
|
+
|
386
|
+
def can_ability_target_unit?(unit:, ability:)
|
387
|
+
# false if enemy is air and we can only shoot ground
|
388
|
+
return false if ability.target == Api::AbilityData::Target::None
|
389
|
+
|
390
|
+
# Check if weapon and unit models are in range
|
391
|
+
in_attack_range?(unit:, range: ability.cast_range)
|
392
|
+
end
|
393
|
+
|
394
|
+
# Checks whether opposing unit is in the attack range.
|
395
|
+
# @param unit [Api::Unit]
|
396
|
+
# @param range [Float, nil] nil. will use default weapon range if nothing provided
|
397
|
+
# @return [Boolean]
|
398
|
+
def in_attack_range?(unit:, range: nil)
|
399
|
+
range = weapon.range if range.nil?
|
400
|
+
distance_to(unit) <= radius + unit.radius + range
|
401
|
+
end
|
402
|
+
|
403
|
+
# Gets a weapon for this unit at index (default weapon is index 0)
|
404
|
+
# @param index [Integer] default 0
|
405
|
+
# @return [Api::Weapon]
|
406
|
+
def weapon(index = 0)
|
407
|
+
unit_data.weapons[index]
|
408
|
+
end
|
409
|
+
|
410
|
+
# Macro functions ---
|
411
|
+
|
412
|
+
# For saturation counters on bases or gas, get the amount of missing harvesters required to saturate.
|
413
|
+
# For a unit to which this effect doesn't apply, the amount is zero.
|
414
|
+
# @return [Integer] number of harvesters required to saturate this structure
|
415
|
+
def missing_harvesters
|
416
|
+
return 0 if ideal_harvesters.zero?
|
417
|
+
|
418
|
+
missing = ideal_harvesters - assigned_harvesters
|
419
|
+
missing.positive? ? missing : 0
|
420
|
+
end
|
421
|
+
|
422
|
+
# The placement size, by looking up unit's creation ability, then game ability data
|
423
|
+
# This value should be correct for building placement math (unit.radius is not good for this)
|
424
|
+
# @return [Float] placement radius
|
425
|
+
def footprint_radius
|
426
|
+
@bot.data.abilities[unit_data.ability_id].footprint_radius
|
427
|
+
end
|
428
|
+
|
429
|
+
# Returns true if build progress is 100%
|
430
|
+
# @return [Boolean]
|
431
|
+
def is_completed?
|
432
|
+
build_progress == 1.0 # standard:disable Lint/FloatComparison
|
433
|
+
end
|
434
|
+
|
435
|
+
# Convenience functions ---
|
436
|
+
|
437
|
+
# TERRAN Convenience functions ---
|
438
|
+
|
439
|
+
# Returns the Api::Unit add-on (Reactor/Tech Lab), if present for this structure
|
440
|
+
# @return [Api::Unit, nil] the unit if an addon is present or nil if not present
|
441
|
+
def add_on
|
442
|
+
@add_on ||= @bot.structures[add_on_tag]
|
443
|
+
end
|
444
|
+
|
445
|
+
# Returns whether the structure has a reactor add-on
|
446
|
+
# @return [Boolean] if the unit has a reactor attached
|
447
|
+
def has_reactor
|
448
|
+
Sc2::UnitGroup::TYPE_REACTOR.include?(add_on&.unit_type)
|
449
|
+
end
|
450
|
+
|
451
|
+
# Returns whether the structure has a tech lab add-on
|
452
|
+
# @example
|
453
|
+
# # Find the first Starport with a techlab
|
454
|
+
# sp = structures.select_type(Api::UnitTypeId::STARPORT).find(&:has_tech_lab)
|
455
|
+
# # Get the actual tech-lab with #add_on
|
456
|
+
# sp.add_on.research ...
|
457
|
+
# @return [Boolean] if the unit has a tech lab attached
|
458
|
+
def has_tech_lab
|
459
|
+
Sc2::UnitGroup::TYPE_TECHLAB.include?(add_on&.unit_type)
|
460
|
+
end
|
461
|
+
|
462
|
+
# For Terran builds a tech lab add-on on the current structure
|
463
|
+
# @return [void]
|
464
|
+
def build_reactor
|
465
|
+
build(unit_type_id: Api::UnitTypeId::REACTOR)
|
466
|
+
end
|
467
|
+
|
468
|
+
# For Terran builds a tech lab add-on on the current structure
|
469
|
+
# @return [void]
|
470
|
+
def build_tech_lab
|
471
|
+
build(unit_type_id: Api::UnitTypeId::TECHLAB)
|
472
|
+
end
|
473
|
+
|
474
|
+
private
|
475
|
+
|
476
|
+
# @private
|
477
|
+
# Reduces repitition in the is_*action*?(target:) methods
|
478
|
+
def is_performing_ability_on_target?(abilities, target: nil)
|
479
|
+
# Exit if not actioning the ability
|
480
|
+
return false unless is_performing_ability?(abilities)
|
481
|
+
|
482
|
+
# If a target was given and we're targeting it, us that value
|
483
|
+
return is_engaged_with?(target) unless target.nil?
|
484
|
+
|
485
|
+
true
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
Api::Unit.include Api::UnitExtension
|