sc2ai 0.0.3 → 0.0.5
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 -1
- data/data/data_readable.json +31 -28
- data/lib/sc2ai/api/data.rb +162 -2
- data/lib/sc2ai/api/tech_tree.rb +22 -0
- data/lib/sc2ai/api/tech_tree_data.rb +19 -8
- data/lib/sc2ai/connection.rb +1 -1
- data/lib/sc2ai/local_play/client_manager.rb +9 -1
- data/lib/sc2ai/player/actions.rb +21 -3
- data/lib/sc2ai/player/debug.rb +51 -4
- data/lib/sc2ai/player/geometry.rb +80 -5
- data/lib/sc2ai/player/previous_state.rb +2 -2
- data/lib/sc2ai/player/units.rb +77 -8
- data/lib/sc2ai/player.rb +7 -7
- data/lib/sc2ai/protocol/_meta_documentation.rb +2 -0
- data/lib/sc2ai/protocol/extensions/point.rb +13 -1
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +6 -0
- data/lib/sc2ai/protocol/extensions/unit.rb +64 -10
- data/lib/sc2ai/protocol/extensions/unit_type_data.rb +20 -0
- data/lib/sc2ai/unit_group/action_ext.rb +31 -2
- data/lib/sc2ai/unit_group/filter_ext.rb +6 -0
- data/lib/sc2ai/unit_group.rb +31 -11
- data/lib/sc2ai/version.rb +1 -1
- data/lib/templates/new/my_bot.rb.tt +2 -3
- data/lib/templates/new/run_example_match.rb.tt +1 -1
- data/sig/sc2ai.rbs +10382 -0
- metadata +43 -69
data/lib/sc2ai/player/debug.rb
CHANGED
@@ -14,6 +14,7 @@ module Sc2
|
|
14
14
|
# @param debug_command [Api::DebugCommand]
|
15
15
|
# @return [void]
|
16
16
|
def queue_debug_command(debug_command)
|
17
|
+
@debug_command_queue ||= []
|
17
18
|
@debug_command_queue << debug_command
|
18
19
|
end
|
19
20
|
|
@@ -37,6 +38,52 @@ module Sc2
|
|
37
38
|
)
|
38
39
|
end
|
39
40
|
|
41
|
+
# Prints text on screen from top and left
|
42
|
+
# @param text [String] will respect newlines
|
43
|
+
# @param left_percent [Numeric] range 0..100. percent from left of screen
|
44
|
+
# @param top_percent [Numeric] range 0..100. percent from top of screen
|
45
|
+
# @param color [Api::Color] default white
|
46
|
+
# @param size [Size] of font, default 14px
|
47
|
+
# @return [void]
|
48
|
+
def debug_text_screen(text, left_percent: 1.0, top_percent: 1.0, color: nil, size: 14)
|
49
|
+
queue_debug_command Api::DebugCommand.new(
|
50
|
+
draw: Api::DebugDraw.new(
|
51
|
+
text: [
|
52
|
+
Api::DebugText.new(
|
53
|
+
text:,
|
54
|
+
virtual_pos: Api::Point.new(
|
55
|
+
x: left_percent.to_f / 100,
|
56
|
+
y: top_percent.to_f / 100
|
57
|
+
),
|
58
|
+
color:,
|
59
|
+
size:
|
60
|
+
)
|
61
|
+
]
|
62
|
+
)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Prints text on screen at 3d world position
|
67
|
+
# @param text [String] will respect newlines
|
68
|
+
# @param point [Api::Point] point in the world, i.e. unit.pos
|
69
|
+
# @param color [Api::Color] default white
|
70
|
+
# @param size [Size] of font, default 14px
|
71
|
+
# @return [void]
|
72
|
+
def debug_text_world(text, point:, color: nil, size: 14)
|
73
|
+
queue_debug_command Api::DebugCommand.new(
|
74
|
+
draw: Api::DebugDraw.new(
|
75
|
+
text: [
|
76
|
+
Api::DebugText.new(
|
77
|
+
text:,
|
78
|
+
world_pos: point,
|
79
|
+
color:,
|
80
|
+
size:
|
81
|
+
)
|
82
|
+
]
|
83
|
+
)
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
40
87
|
# Draws a line between two Api::Point's for color
|
41
88
|
# @param p0 [Api::Point] the first point
|
42
89
|
# @param p1 [Api::Point] the second point
|
@@ -63,8 +110,8 @@ module Sc2
|
|
63
110
|
# # Draws a box on structure placement grid
|
64
111
|
# debug_draw_box(point: unit.pos, radius: unit.footprint_radius)
|
65
112
|
#
|
66
|
-
#
|
67
|
-
#
|
113
|
+
# @note Api::Color RGB is broken for this command. Will use min(r,b)
|
114
|
+
# @note Z index is elevated 0.02 so the line is visible and doesn't clip through terrain
|
68
115
|
# @param point [Api::Point]
|
69
116
|
# @param radius [Float] default one tile wide, 1.0
|
70
117
|
# @param color [Api::Color] default white. min(r,b) is used for both r&b
|
@@ -74,8 +121,8 @@ module Sc2
|
|
74
121
|
draw: Api::DebugDraw.new(
|
75
122
|
boxes: [
|
76
123
|
Api::DebugBox.new(
|
77
|
-
min: Api::Point.new(x: point.x - radius, y: point.y - radius, z: point.z + 0.
|
78
|
-
max: Api::Point.new(x: point.x + radius, y: point.y + radius, z: point.z + (radius * 2) + 0.
|
124
|
+
min: Api::Point.new(x: point.x - radius, y: point.y - radius, z: point.z + 0.02),
|
125
|
+
max: Api::Point.new(x: point.x + radius, y: point.y + radius, z: point.z + (radius * 2) + 0.02),
|
79
126
|
color:
|
80
127
|
)
|
81
128
|
]
|
@@ -7,7 +7,7 @@ module Sc2
|
|
7
7
|
class Player
|
8
8
|
# Holds map and geography helper functions
|
9
9
|
class Geometry
|
10
|
-
# @!attribute
|
10
|
+
# @!attribute bot
|
11
11
|
# @return [Sc2::Player] player with active connection
|
12
12
|
attr_accessor :bot
|
13
13
|
|
@@ -72,6 +72,7 @@ module Sc2
|
|
72
72
|
# Each value in [row][column] holds a boolean value represented as an integer
|
73
73
|
# It does not say whether a position is occupied by another building.
|
74
74
|
# One pixel covers one whole block. Rounds fractionated positions down.
|
75
|
+
# @return [Numo::Bit]
|
75
76
|
def parsed_placement_grid
|
76
77
|
if @parsed_placement_grid.nil?
|
77
78
|
image_data = bot.game_info.start_raw.placement_grid
|
@@ -83,6 +84,7 @@ module Sc2
|
|
83
84
|
end
|
84
85
|
|
85
86
|
# Returns a grid where ony the expo locations are marked
|
87
|
+
# @return [Numo::Bit]
|
86
88
|
def expo_placement_grid
|
87
89
|
if @expo_placement_grid.nil?
|
88
90
|
@expo_placement_grid = Numo::Bit.zeros(map_height, map_width)
|
@@ -97,6 +99,7 @@ module Sc2
|
|
97
99
|
end
|
98
100
|
|
99
101
|
# Returns a grid where powered locations are marked true
|
102
|
+
# @return [Numo::Bit]
|
100
103
|
def parsed_power_grid
|
101
104
|
# Cache for based on power unit tags
|
102
105
|
cache_key = bot.power_sources.map(&:tag).sort.hash
|
@@ -256,6 +259,12 @@ module Sc2
|
|
256
259
|
parsed_terrain_height[y.to_i, x.to_i]
|
257
260
|
end
|
258
261
|
|
262
|
+
# Returns the terrain height (z) at position x and y for a point
|
263
|
+
# @return [Float] z axis position between -16 and 16
|
264
|
+
def terrain_height_for_pos(position)
|
265
|
+
terrain_height(x: position.x, y: position.y)
|
266
|
+
end
|
267
|
+
|
259
268
|
# Returns a parsed terrain_height from bot.game_info.start_raw.
|
260
269
|
# Each value in [row][column] holds a float value which is the z height
|
261
270
|
# @return [Numo::SFloat] Numo array
|
@@ -399,12 +408,25 @@ module Sc2
|
|
399
408
|
output_grid
|
400
409
|
end
|
401
410
|
|
411
|
+
# Returns own 2d start position as set by initial camera
|
412
|
+
# This differs from position of first base structure
|
413
|
+
# @return [Api::Point2D]
|
414
|
+
def start_position
|
415
|
+
@start_position ||= bot.observation.raw_data.player.camera
|
416
|
+
end
|
417
|
+
|
418
|
+
# Returns the enemy 2d start position
|
419
|
+
# @return [Api::Point2D]
|
420
|
+
def enemy_start_position
|
421
|
+
bot.game_info.start_raw.start_locations.first
|
422
|
+
end
|
423
|
+
|
402
424
|
# Gets expos and surrounding minerals
|
403
425
|
# The index is a build location for an expo and the value is a UnitGroup, which has minerals and geysers
|
404
426
|
# @example
|
405
|
-
# random_expo = expansions.keys.sample #=> Point2D
|
427
|
+
# random_expo = geo.expansions.keys.sample #=> Point2D
|
406
428
|
# expo_resources = geo.expansions[random_expo] #=> UnitGroup
|
407
|
-
# alive_minerals = expo_resources.minerals
|
429
|
+
# alive_minerals = expo_resources.minerals & neutral.minerals
|
408
430
|
# geysers = expo_resources.geysers
|
409
431
|
# @return [Hash<Api::Point2D, UnitGroup>] Location => UnitGroup of resources (minerals+geysers)
|
410
432
|
def expansions
|
@@ -503,24 +525,77 @@ module Sc2
|
|
503
525
|
end
|
504
526
|
|
505
527
|
# Returns a slice of #expansions where a base hasn't been built yet
|
528
|
+
# The has index is a build position and the value is a UnitGroup of resources for the base
|
506
529
|
# @example
|
507
530
|
# # Lets find the nearest unoccupied expo
|
508
531
|
# expo_pos = expansions_unoccupied.keys.min { |p2d| p2d.distance_to(structures.hq.first) }
|
509
532
|
# # What minerals/geysers does it have?
|
510
533
|
# puts expansions_unoccupied[expo_pos].minerals # or expansions[expo_pos]... => UnitGroup
|
511
534
|
# puts expansions_unoccupied[expo_pos].geysers # or expansions[expo_pos]... => UnitGroup
|
512
|
-
# @return [Hash<Api::Point2D
|
535
|
+
# @return [Hash<Api::Point2D, UnitGroup>] Location => UnitGroup of resources (minerals+geysers)
|
513
536
|
def expansions_unoccupied
|
514
537
|
taken_bases = bot.structures.hq.map { |hq| hq.pos.to_p2d } + bot.enemy.structures.hq.map { |hq| hq.pos.to_p2d }
|
515
538
|
remaining_points = expansion_points - taken_bases
|
516
539
|
expansions.slice(*remaining_points)
|
517
540
|
end
|
518
541
|
|
542
|
+
# Gets minerals for a base or base position
|
543
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
544
|
+
# @return [Sc2::UnitGroup] UnitGroup of minerals for the base
|
545
|
+
def minerals_for_base(base)
|
546
|
+
# resources_for_base contains what we need, but slice neutral.minerals,
|
547
|
+
# so that only active patches remain
|
548
|
+
bot.neutral.minerals.slice(*resources_for_base(base).minerals.tags)
|
549
|
+
end
|
550
|
+
|
551
|
+
# Gets geysers for a base or base position
|
552
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
553
|
+
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
554
|
+
def geysers_for_base(base)
|
555
|
+
resources_for_base(base).geysers
|
556
|
+
end
|
557
|
+
|
558
|
+
# @private
|
559
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
560
|
+
# @return [Sc2::UnitGroup] UnitGroup of resources (minerals+geysers)
|
561
|
+
private def resources_for_base(base)
|
562
|
+
pos = base.is_a?(Api::Unit) ? base.pos : base
|
563
|
+
|
564
|
+
# If we have a base setup for this exact position, use it
|
565
|
+
if expansions.has_key?(pos)
|
566
|
+
return expansions[pos]
|
567
|
+
end
|
568
|
+
|
569
|
+
# Tolerance for misplaced base: Find the nearest base to this position
|
570
|
+
pos = expansion_points.min_by { |p| p.distance_to(pos) }
|
571
|
+
|
572
|
+
expansions[pos]
|
573
|
+
end
|
574
|
+
|
575
|
+
# Gets gasses for a base or base position
|
576
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
577
|
+
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
578
|
+
def gas_for_base(base)
|
579
|
+
# No gas structures at all yet, return nothing
|
580
|
+
return UnitGroup.new if bot.structures.gas.size.zero?
|
581
|
+
|
582
|
+
geysers = geysers_for_base(base)
|
583
|
+
|
584
|
+
# Mineral-only base, return nothing
|
585
|
+
return UnitGroup.new if geysers.size == 0
|
586
|
+
|
587
|
+
# Loop and collect gasses places exactly on-top of geysers
|
588
|
+
bot.structures.gas.select do |gas|
|
589
|
+
geysers.any? { |geyser| geyser.pos.to_p2d.eql?(gas.pos.to_p2d) }
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
519
593
|
# Gets buildable point grid for squares of size, i.e. 3 = 3x3 placements
|
520
594
|
# Uses pathing grid internally, to ignore taken positions
|
521
595
|
# Does not query the api and is generally fast.
|
522
596
|
# @param length [Integer] length of the building, 2 for depot/pylon, 3 for rax/gate
|
523
|
-
# @param on_creep [Boolean] whether this build
|
597
|
+
# @param on_creep [Boolean] whether this build location should be on creep
|
598
|
+
# @return [Array<Array<(Float, Float)>>] Array of [x,y] tuples
|
524
599
|
def build_coordinates(length:, on_creep: false, in_power: false)
|
525
600
|
length = 1 if length < 1
|
526
601
|
@_build_coordinates ||= {}
|
@@ -36,13 +36,13 @@ module Sc2
|
|
36
36
|
# Override to modify the previous frame before being set to current
|
37
37
|
# @param bot [Sc2::Player::Bot]
|
38
38
|
def before_reset(bot)
|
39
|
-
#
|
39
|
+
# no op
|
40
40
|
end
|
41
41
|
|
42
42
|
# Override to modify previous frame after reset is complete
|
43
43
|
# @param bot [Sc2::Player::Bot]
|
44
44
|
def after_reset(bot)
|
45
|
-
#
|
45
|
+
# no op
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
data/lib/sc2ai/player/units.rb
CHANGED
@@ -30,6 +30,53 @@ module Sc2
|
|
30
30
|
# @return [Sc2::UnitGroup] a group of neutral units
|
31
31
|
attr_accessor :effects # not a unit
|
32
32
|
|
33
|
+
# Returns the upgrade ids you have acquired such as weapon upgrade and armor upgrade ids.
|
34
|
+
# Shorthand for observation.raw_data.player.upgrade_ids
|
35
|
+
# @!attribute [r] upgrades_completed
|
36
|
+
# @return [Array<Integer>] a group of neutral units
|
37
|
+
def upgrades_completed = observation&.raw_data&.player&.upgrade_ids.to_a || [] # not a unit
|
38
|
+
|
39
|
+
# Returns the upgrade ids which are researching or queued
|
40
|
+
# Not set for enemy.
|
41
|
+
# @return [Array<Integer>]
|
42
|
+
def upgrades_in_progress
|
43
|
+
# We need to scan every structure which performs upgrades for any order with an upgrade ability
|
44
|
+
|
45
|
+
result = []
|
46
|
+
# Loop every upgrade structure
|
47
|
+
structures
|
48
|
+
.select_type(Api::TechTree.upgrade_structure_unit_type_ids)
|
49
|
+
.each do |structure|
|
50
|
+
next unless structure.is_active? # Skip idle
|
51
|
+
|
52
|
+
# Check if any order at a structure contains an upgrade ability
|
53
|
+
structure.orders.each do |order|
|
54
|
+
Api::TechTree.upgrade_ability_data(structure.unit_type).each do |upgrade_id, update_info|
|
55
|
+
if update_info[:ability] == order.ability_id
|
56
|
+
# Save the upgrade_id
|
57
|
+
result << upgrade_id
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# If the API told use it's complete, but an order still lingers, trust the API
|
64
|
+
result - upgrades_completed
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the upgrade ids which are researching or queued
|
68
|
+
# @return [Boolean]
|
69
|
+
def upgrade_in_progress?(upgrade_id)
|
70
|
+
structure_unit_type_id = Api::TechTree.upgrade_researched_from(upgrade_id: upgrade_id)
|
71
|
+
research_ability_id = Api::TechTree.upgrade_research_ability_id(upgrade_id: upgrade_id)
|
72
|
+
structures.select_type(structure_unit_type_id).each do |structure|
|
73
|
+
structure.orders.each do |order|
|
74
|
+
return true if order.ability_id == research_ability_id
|
75
|
+
end
|
76
|
+
end
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
33
80
|
# An array of Protoss power sources, which have a point, radius and unit tag
|
34
81
|
# @!attribute power_sources
|
35
82
|
# @return [Array<Api::PowerSource>] an array of power sources
|
@@ -47,10 +94,10 @@ module Sc2
|
|
47
94
|
|
48
95
|
# Event-driven unit groups ---
|
49
96
|
|
97
|
+
# @!attribute event_units_created
|
50
98
|
# Units created since last frame (visible only, units not structures)
|
51
99
|
# Read this on_step. Alternative to callback on_unit_created
|
52
|
-
#
|
53
|
-
# @!attribute event_units_created
|
100
|
+
# @note Morphed units should watch #event_units_type_changed
|
54
101
|
# @return [Sc2::UnitGroup] group of created units
|
55
102
|
attr_accessor :event_units_created
|
56
103
|
|
@@ -107,6 +154,13 @@ module Sc2
|
|
107
154
|
data.abilities[ability_id]
|
108
155
|
end
|
109
156
|
|
157
|
+
# Returns static [Api::UpgradeData] for an upgrade id
|
158
|
+
# @param upgrade_id [Integer] Api::UpgradeId::*
|
159
|
+
# @return [Api::UpgradeData]
|
160
|
+
def upgrade_data(upgrade_id)
|
161
|
+
data.upgrades[upgrade_id]
|
162
|
+
end
|
163
|
+
|
110
164
|
# Checks unit data for an attribute value
|
111
165
|
# @param unit [Integer,Api::Unit] Api::UnitTypeId or Api::Unit
|
112
166
|
# @param attribute [Symbol] Api::Attribute, i.e. Api::Attribute::Mechanical or :Mechanical
|
@@ -141,17 +195,13 @@ module Sc2
|
|
141
195
|
def subtract_cost(unit_type_id)
|
142
196
|
unit_type_data = unit_data(unit_type_id)
|
143
197
|
|
144
|
-
# food_required is a float. ensure half units are counted as full
|
145
|
-
# TODO: Extend UnitTypeData message. def food_required = unit_id == Api::UnitTypeId::ZERGLING ? 1 : send("method_missing", :food_required)
|
146
|
-
supply_cost = unit_type_data.food_required
|
147
|
-
supply_cost = 1 if unit_type_id == Api::UnitTypeId::ZERGLING
|
148
|
-
|
149
198
|
@spent_minerals += unit_type_data.mineral_cost
|
150
199
|
@spent_vespene += unit_type_data.vespene_cost
|
151
|
-
@spent_supply +=
|
200
|
+
@spent_supply += unit_type_data.food_required
|
152
201
|
end
|
153
202
|
|
154
203
|
# Checks whether you have the resources to construct quantity of unit type
|
204
|
+
# @return [Boolean]
|
155
205
|
def can_afford?(unit_type_id:, quantity: 1)
|
156
206
|
unit_type_data = unit_data(unit_type_id)
|
157
207
|
return false if unit_type_data.nil?
|
@@ -178,6 +228,25 @@ module Sc2
|
|
178
228
|
true
|
179
229
|
end
|
180
230
|
|
231
|
+
# Checks whether you have the resources to
|
232
|
+
# @return [Boolean]
|
233
|
+
def can_afford_upgrade?(upgrade_id)
|
234
|
+
unit_type_data = upgrade_data(upgrade_id)
|
235
|
+
return false if unit_type_data.nil?
|
236
|
+
|
237
|
+
mineral_cost = unit_type_data.mineral_cost
|
238
|
+
if common.minerals - spent_minerals < mineral_cost
|
239
|
+
return false # not enough minerals
|
240
|
+
end
|
241
|
+
|
242
|
+
vespene_cost = unit_type_data.vespene_cost
|
243
|
+
if common.vespene - spent_vespene < vespene_cost
|
244
|
+
return false # you require more vespene gas
|
245
|
+
end
|
246
|
+
|
247
|
+
true
|
248
|
+
end
|
249
|
+
|
181
250
|
private
|
182
251
|
|
183
252
|
# @private
|
data/lib/sc2ai/player.rb
CHANGED
@@ -97,7 +97,7 @@ module Sc2
|
|
97
97
|
@difficulty = difficulty
|
98
98
|
@ai_build = ai_build
|
99
99
|
@realtime = false
|
100
|
-
@step_count =
|
100
|
+
@step_count = 2
|
101
101
|
|
102
102
|
@enable_feature_layer = false
|
103
103
|
@interface_options = {}
|
@@ -201,7 +201,7 @@ module Sc2
|
|
201
201
|
|
202
202
|
# Override to customize initialization
|
203
203
|
# Alias of before_join
|
204
|
-
# You can enable_feature_layer=true, set
|
204
|
+
# You can enable_feature_layer=true, set step_count, define
|
205
205
|
# @example
|
206
206
|
# def configure
|
207
207
|
# step_count = 4 # Update less frequently
|
@@ -490,6 +490,8 @@ module Sc2
|
|
490
490
|
def started
|
491
491
|
# Calculate expansions
|
492
492
|
geo.expansions
|
493
|
+
# Set our start position base on camera
|
494
|
+
geo.start_position
|
493
495
|
end
|
494
496
|
|
495
497
|
# Moves emulation ahead and calls back #on_step
|
@@ -498,9 +500,7 @@ module Sc2
|
|
498
500
|
# Sc2.logger.debug "#{self.class} step_forward"
|
499
501
|
|
500
502
|
unless @realtime
|
501
|
-
|
502
|
-
num_steps = 1
|
503
|
-
@api.step(num_steps)
|
503
|
+
@api.step(@step_count)
|
504
504
|
end
|
505
505
|
|
506
506
|
refresh_state
|
@@ -610,9 +610,9 @@ module Sc2
|
|
610
610
|
# ##TODO: perfect loop implementation
|
611
611
|
# observation has an optional param game_loop and will only return once that step is reached (blocking).
|
612
612
|
# without it, it returns things as they are.
|
613
|
-
# broadly, i think this is what it should be doing, with
|
613
|
+
# broadly, i think this is what it should be doing, with step_count being minimum of 1, so no zero-steps occur.
|
614
614
|
# @example
|
615
|
-
# desired_game_loop = current_game_loop +
|
615
|
+
# desired_game_loop = current_game_loop + step_count
|
616
616
|
# response = client.observation(game_loop: desired_game_loop)
|
617
617
|
#
|
618
618
|
# if response.game_loop > desired_game_loop {
|
@@ -21,6 +21,8 @@
|
|
21
21
|
# class Point < Google::Protobuf::AbstractMessage; end;
|
22
22
|
# # Protobuf virtual class.
|
23
23
|
# class Unit < Google::Protobuf::AbstractMessage; end;
|
24
|
+
# # Protobuf virtual class.
|
25
|
+
# class UnitTypeData < Google::Protobuf::AbstractMessage; end;
|
24
26
|
# end
|
25
27
|
|
26
28
|
# Protobuf enums ---
|
@@ -1,6 +1,15 @@
|
|
1
1
|
module Api
|
2
2
|
# Adds additional functionality to message object Api::Point
|
3
3
|
module PointExtension
|
4
|
+
# @private
|
5
|
+
def hash
|
6
|
+
[x, y, z].hash
|
7
|
+
end
|
8
|
+
|
9
|
+
def eql?(other)
|
10
|
+
self.class == other.class && hash == other.hash
|
11
|
+
end
|
12
|
+
|
4
13
|
# Creates a Point2D using x and y
|
5
14
|
# @return [Api::Point2D]
|
6
15
|
def to_p2d
|
@@ -13,6 +22,9 @@ module Api
|
|
13
22
|
# @example
|
14
23
|
# Api::Point[1,2,3] # Where x is 1.0, y is 2.0 and z is 3.0
|
15
24
|
# @return [Api::Point]
|
25
|
+
# @param [Float] x
|
26
|
+
# @param [Float] y
|
27
|
+
# @param [Float] z
|
16
28
|
def [](x, y, z)
|
17
29
|
Api::Point.new(x: x, y: y, z: z)
|
18
30
|
end
|
@@ -20,4 +32,4 @@ module Api
|
|
20
32
|
end
|
21
33
|
end
|
22
34
|
Api::Point.include Api::PointExtension
|
23
|
-
Api::Point.
|
35
|
+
Api::Point.extend Api::PointExtension::ClassMethods
|
@@ -10,6 +10,12 @@ module Api
|
|
10
10
|
self.class == other.class && hash == other.hash
|
11
11
|
end
|
12
12
|
|
13
|
+
# Create a new 3d Point, by adding a y axis.
|
14
|
+
# @return [Api::Point]
|
15
|
+
def to_3d(z:)
|
16
|
+
Api::Point[x, y, z]
|
17
|
+
end
|
18
|
+
|
13
19
|
# Adds additional functionality to message class Api::Point2D
|
14
20
|
module ClassMethods
|
15
21
|
# Shorthand for creating an instance for [x, y]
|
@@ -37,9 +37,8 @@ module Api
|
|
37
37
|
# Checks unit data for an attribute value
|
38
38
|
# @return [Boolean] whether unit has attribute
|
39
39
|
# @example
|
40
|
-
# has_attribute?(Api::
|
41
|
-
# has_attribute?(
|
42
|
-
# has_attribute?(Api::UnitTypeId::SCV, :Mechanical)
|
40
|
+
# unit.has_attribute?(Api::Attribute::Mechanical)
|
41
|
+
# unit.has_attribute?(:Mechanical)
|
43
42
|
def has_attribute?(attribute)
|
44
43
|
attributes.include? attribute
|
45
44
|
end
|
@@ -179,6 +178,26 @@ module Api
|
|
179
178
|
action(ability_id: Api::AbilityId::SMART, target:, queue_command:)
|
180
179
|
end
|
181
180
|
|
181
|
+
# Shorthand for performing action MOVE
|
182
|
+
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
183
|
+
# @param queue_command [Boolean] shift+command
|
184
|
+
def move(target:, queue_command: false)
|
185
|
+
action(ability_id: Api::AbilityId::MOVE, target:, queue_command:)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Shorthand for performing action STOP
|
189
|
+
# @param queue_command [Boolean] shift+command
|
190
|
+
def stop(queue_command: false)
|
191
|
+
action(ability_id: Api::AbilityId::STOP, queue_command:)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Shorthand for performing action HOLDPOSITION
|
195
|
+
# @param queue_command [Boolean] shift+command
|
196
|
+
def hold(queue_command: false)
|
197
|
+
action(ability_id: Api::AbilityId::HOLDPOSITION, queue_command:)
|
198
|
+
end
|
199
|
+
alias_method :hold_position, :hold
|
200
|
+
|
182
201
|
# Shorthand for performing action ATTACK
|
183
202
|
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
184
203
|
# @param queue_command [Boolean] shift+command
|
@@ -213,10 +232,18 @@ module Api
|
|
213
232
|
|
214
233
|
# Issues repair command on target
|
215
234
|
# @param target [Api::Unit, Integer] is a unit or unit tag
|
235
|
+
# @param queue_command [Boolean] shift+command
|
216
236
|
def repair(target:, queue_command: false)
|
217
237
|
action(ability_id: Api::AbilityId::EFFECT_REPAIR, target:, queue_command:)
|
218
238
|
end
|
219
239
|
|
240
|
+
# Research a specific upgrade
|
241
|
+
# @param upgrade_id [Integer] Api::UnitTypeId the unit type which will do the creation
|
242
|
+
# @param queue_command [Boolean] shift+command
|
243
|
+
def research(upgrade_id:, queue_command: false)
|
244
|
+
@bot.research(units: self, upgrade_id:, queue_command:)
|
245
|
+
end
|
246
|
+
|
220
247
|
# @!endgroup Actions
|
221
248
|
#
|
222
249
|
# Debug ----
|
@@ -224,6 +251,7 @@ module Api
|
|
224
251
|
# Draws a placement outline
|
225
252
|
# @param color [Api::Color] optional api color, default white
|
226
253
|
# @return [void]
|
254
|
+
# noinspection RubyArgCount
|
227
255
|
def debug_draw_placement(color = nil)
|
228
256
|
# Slightly elevate the Z position so that the line doesn't clip into the terrain at same Z level
|
229
257
|
z_elevated = pos.z + 0.01
|
@@ -432,6 +460,12 @@ module Api
|
|
432
460
|
build_progress == 1.0 # standard:disable Lint/FloatComparison
|
433
461
|
end
|
434
462
|
|
463
|
+
# Returns true if build progress is < 100%
|
464
|
+
# @return [Boolean]
|
465
|
+
def in_progress?
|
466
|
+
!is_completed?
|
467
|
+
end
|
468
|
+
|
435
469
|
# Convenience functions ---
|
436
470
|
|
437
471
|
# TERRAN Convenience functions ---
|
@@ -444,7 +478,7 @@ module Api
|
|
444
478
|
|
445
479
|
# Returns whether the structure has a reactor add-on
|
446
480
|
# @return [Boolean] if the unit has a reactor attached
|
447
|
-
def has_reactor
|
481
|
+
def has_reactor?
|
448
482
|
Sc2::UnitGroup::TYPE_REACTOR.include?(add_on&.unit_type)
|
449
483
|
end
|
450
484
|
|
@@ -455,26 +489,46 @@ module Api
|
|
455
489
|
# # Get the actual tech-lab with #add_on
|
456
490
|
# sp.add_on.research ...
|
457
491
|
# @return [Boolean] if the unit has a tech lab attached
|
458
|
-
def has_tech_lab
|
492
|
+
def has_tech_lab?
|
459
493
|
Sc2::UnitGroup::TYPE_TECHLAB.include?(add_on&.unit_type)
|
460
494
|
end
|
461
495
|
|
462
496
|
# For Terran builds a tech lab add-on on the current structure
|
463
497
|
# @return [void]
|
464
|
-
def build_reactor
|
465
|
-
|
498
|
+
def build_reactor(queue_command: false)
|
499
|
+
unit_type_id = case unit_type
|
500
|
+
when Api::UnitTypeId::BARRACKS, Api::UnitTypeId::BARRACKSFLYING
|
501
|
+
Api::UnitTypeId::BARRACKSREACTOR
|
502
|
+
when Api::UnitTypeId::FACTORY, Api::UnitTypeId::FACTORYFLYING
|
503
|
+
Api::UnitTypeId::FACTORYREACTOR
|
504
|
+
when Api::UnitTypeId::STARPORT, Api::UnitTypeId::STARPORTFLYING
|
505
|
+
Api::UnitTypeId::STARPORTREACTOR
|
506
|
+
end
|
507
|
+
build(unit_type_id: unit_type_id, queue_command:)
|
466
508
|
end
|
467
509
|
|
468
510
|
# For Terran builds a tech lab add-on on the current structure
|
469
511
|
# @return [void]
|
470
|
-
def build_tech_lab
|
471
|
-
|
512
|
+
def build_tech_lab(queue_command: false)
|
513
|
+
unit_type_id = case unit_type
|
514
|
+
when Api::UnitTypeId::BARRACKS, Api::UnitTypeId::BARRACKSFLYING
|
515
|
+
Api::UnitTypeId::BARRACKSTECHLAB
|
516
|
+
when Api::UnitTypeId::FACTORY, Api::UnitTypeId::FACTORYFLYING
|
517
|
+
Api::UnitTypeId::FACTORYTECHLAB
|
518
|
+
when Api::UnitTypeId::STARPORT, Api::UnitTypeId::STARPORTFLYING
|
519
|
+
Api::UnitTypeId::STARPORTTECHLAB
|
520
|
+
end
|
521
|
+
build(unit_type_id: unit_type_id, queue_command:)
|
472
522
|
end
|
473
523
|
|
524
|
+
# GENERAL Convenience functions ---
|
525
|
+
|
526
|
+
# ...
|
527
|
+
|
474
528
|
private
|
475
529
|
|
476
530
|
# @private
|
477
|
-
# Reduces
|
531
|
+
# Reduces repetition in the is_*action*?(target:) methods
|
478
532
|
def is_performing_ability_on_target?(abilities, target: nil)
|
479
533
|
# Exit if not actioning the ability
|
480
534
|
return false unless is_performing_ability?(abilities)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Api
|
4
|
+
# Adds additional functionality to message object Api::UnitTypeData
|
5
|
+
module UnitTypeDataExtension
|
6
|
+
# @!attribute mineral_cost_sum
|
7
|
+
# Sum of all morphs mineral cost
|
8
|
+
# i.e. 550M Orbital command = 400M CC + 150M Upgrade
|
9
|
+
# i.e. 350M Hatchery = 50M Drone + 300M Build
|
10
|
+
# @return [Integer] sum of mineral costs
|
11
|
+
attr_accessor :mineral_cost_sum
|
12
|
+
|
13
|
+
# @!attribute vespene_cost_sum
|
14
|
+
# Sum of all morphs vespene gas cost
|
15
|
+
# i.e. 250G Broodlord = 100G Corruptor + 150G Morph
|
16
|
+
# @return [Integer] sum of vespene gas costs
|
17
|
+
attr_accessor :vespene_cost_sum
|
18
|
+
end
|
19
|
+
end
|
20
|
+
Api::UnitTypeData.include Api::UnitTypeDataExtension
|