sc2ai 0.7.0 → 0.8.0
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 +482 -22
- data/data/setup/setup.SC2Replay +0 -0
- data/data/stableid.json +297 -2321
- data/data/versions.json +17 -6
- data/docker_build/Dockerfile.ruby +1 -1
- data/lib/sc2ai/api/ability_id.rb +38 -2
- data/lib/sc2ai/api/buff_id.rb +5 -7
- data/lib/sc2ai/api/tech_tree_data.rb +3 -10
- data/lib/sc2ai/api/unit_type_id.rb +21 -47
- data/lib/sc2ai/cli/cli.rb +19 -11
- data/lib/sc2ai/cli/new.rb +6 -3
- data/lib/sc2ai/connection/requests.rb +7 -2
- data/lib/sc2ai/connection.rb +14 -5
- data/lib/sc2ai/local_play/client/configurable_options.rb +1 -1
- data/lib/sc2ai/local_play/client.rb +27 -21
- data/lib/sc2ai/local_play/client_manager.rb +1 -0
- data/lib/sc2ai/local_play/map_file.rb +3 -0
- data/lib/sc2ai/player/actions.rb +55 -0
- data/lib/sc2ai/player/game_state.rb +1 -0
- data/lib/sc2ai/player/geo.rb +3 -3
- data/lib/sc2ai/player/units.rb +3 -12
- data/lib/sc2ai/player.rb +23 -36
- data/lib/sc2ai/ports.rb +15 -3
- data/lib/sc2ai/protocol/extensions/position.rb +15 -3
- data/lib/sc2ai/protocol/extensions/unit.rb +64 -19
- data/lib/sc2ai/step_timer.rb +134 -0
- data/lib/sc2ai/unit_group/action_ext.rb +1 -1
- data/lib/sc2ai/unit_group/filter_ext.rb +152 -10
- data/lib/sc2ai/unit_group.rb +4 -4
- data/lib/sc2ai/version.rb +1 -1
- data/lib/sc2ai.rb +3 -4
- data/lib/templates/new/run_example_match.rb.tt +1 -1
- data/sig/sc2ai.rbs +336 -174
- metadata +35 -71
data/lib/sc2ai/player/actions.rb
CHANGED
@@ -2,6 +2,29 @@ module Sc2
|
|
2
2
|
class Player
|
3
3
|
# Holds action list and queues batch
|
4
4
|
module Actions
|
5
|
+
COMBINABLE_ABILITIES = [
|
6
|
+
Api::AbilityId::MOVE,
|
7
|
+
Api::AbilityId::ATTACK,
|
8
|
+
Api::AbilityId::SCAN_MOVE,
|
9
|
+
Api::AbilityId::STOP,
|
10
|
+
Api::AbilityId::SMART,
|
11
|
+
Api::AbilityId::HOLDPOSITION,
|
12
|
+
Api::AbilityId::PATROL,
|
13
|
+
Api::AbilityId::HARVEST_GATHER,
|
14
|
+
Api::AbilityId::HARVEST_RETURN,
|
15
|
+
Api::AbilityId::EFFECT_REPAIR,
|
16
|
+
Api::AbilityId::LIFT,
|
17
|
+
Api::AbilityId::BURROWDOWN,
|
18
|
+
Api::AbilityId::BURROWUP,
|
19
|
+
Api::AbilityId::SIEGEMODE_SIEGEMODE,
|
20
|
+
Api::AbilityId::UNSIEGE_UNSIEGE,
|
21
|
+
Api::AbilityId::MORPH_LIBERATORAAMODE,
|
22
|
+
Api::AbilityId::EFFECT_STIM,
|
23
|
+
Api::AbilityId::MORPH_UPROOT,
|
24
|
+
Api::AbilityId::EFFECT_BLINK,
|
25
|
+
Api::AbilityId::MORPH_ARCHON
|
26
|
+
]
|
27
|
+
|
5
28
|
# Holds actions which will be queued off each time we step forward
|
6
29
|
# @!attribute action_queue
|
7
30
|
# @return [Array<Api::Action>]
|
@@ -382,6 +405,7 @@ module Sc2
|
|
382
405
|
# @return [Api::ResponseAction, nil]
|
383
406
|
def perform_actions
|
384
407
|
return nil if @action_queue.empty?
|
408
|
+
combine_similar_actions
|
385
409
|
|
386
410
|
response_action = @api.action(@action_queue)
|
387
411
|
if callback_defined?(:on_action_errors)
|
@@ -404,6 +428,37 @@ module Sc2
|
|
404
428
|
response_action
|
405
429
|
end
|
406
430
|
|
431
|
+
# @private
|
432
|
+
# Makes 10x unit moves to the same target turn into one action
|
433
|
+
# with many unit_tags
|
434
|
+
# @return [void]
|
435
|
+
private def combine_similar_actions
|
436
|
+
grouped_actions = @action_queue.group_by do |action|
|
437
|
+
unit_command = action&.action_raw&.unit_command
|
438
|
+
[unit_command.nil? || !!unit_command&.queue_command,
|
439
|
+
unit_command&.ability_id,
|
440
|
+
unit_command&.target_world_space_pos,
|
441
|
+
unit_command&.target_unit_tag]
|
442
|
+
end
|
443
|
+
|
444
|
+
grouped_actions.each do |key, dupe_actions|
|
445
|
+
# Don't merge if it's not a unit command
|
446
|
+
# or queue_command is true
|
447
|
+
# or the action is unique
|
448
|
+
next if key[0] || dupe_actions.size < 2
|
449
|
+
# Skip if not a combinable ability such as Attack or Move
|
450
|
+
next unless COMBINABLE_ABILITIES.include?(key[1])
|
451
|
+
|
452
|
+
new_action = dupe_actions.first.dup
|
453
|
+
unit_tags = dupe_actions.flat_map do |dupe_action|
|
454
|
+
dupe_action.action_raw.unit_command.unit_tags
|
455
|
+
end
|
456
|
+
new_action.action_raw.unit_command.unit_tags = unit_tags
|
457
|
+
@action_queue = @action_queue.difference(dupe_actions)
|
458
|
+
@action_queue.unshift(new_action)
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
407
462
|
# Empties and resets @action_queue
|
408
463
|
# @return [void]
|
409
464
|
def clear_action_queue
|
data/lib/sc2ai/player/geo.rb
CHANGED
@@ -347,14 +347,14 @@ module Sc2
|
|
347
347
|
|
348
348
|
# Returns a parsed terrain_height from bot.game_info.start_raw.
|
349
349
|
# Each value in [row][column] holds a float value which is the z height
|
350
|
-
# @return [::Numo::
|
350
|
+
# @return [::Numo::DFloat] Numo array
|
351
351
|
def parsed_terrain_height
|
352
352
|
if @parsed_terrain_height.nil?
|
353
353
|
|
354
354
|
image_data = bot.game_info.start_raw.terrain_height
|
355
355
|
@parsed_terrain_height = Numo::UInt8
|
356
356
|
.from_binary(image_data.data, [image_data.size.y, image_data.size.x])
|
357
|
-
.cast_to(Numo::
|
357
|
+
.cast_to(Numo::DFloat)
|
358
358
|
|
359
359
|
# Values are between -16 and +16. The api values is a float height compressed to rgb range (0-255) in that range of 32.
|
360
360
|
# real_height = -16 + (value / 255) * 32
|
@@ -400,7 +400,7 @@ module Sc2
|
|
400
400
|
# Returns a parsed map_state.visibility from bot.observation.raw_data.
|
401
401
|
# Each value in [row][column] holds one of three integers (0,1,2) to flag a vision type
|
402
402
|
# @see #visibility for reading from this value
|
403
|
-
# @return [::Numo::
|
403
|
+
# @return [::Numo::UInt8] Numo array
|
404
404
|
def parsed_visibility_grid
|
405
405
|
if @parsed_visibility_grid.nil?
|
406
406
|
image_data = bot.observation.raw_data.map_state.visibility
|
data/lib/sc2ai/player/units.rb
CHANGED
@@ -44,7 +44,7 @@ module Sc2
|
|
44
44
|
# Shorthand for observation.raw_data.player.upgrade_ids
|
45
45
|
# @!attribute [r] upgrades_completed
|
46
46
|
# @return [Array<Integer>] a group of neutral units
|
47
|
-
def upgrades_completed = observation&.raw_data&.player&.upgrade_ids.to_a
|
47
|
+
def upgrades_completed = observation&.raw_data&.player&.upgrade_ids.to_a # not a unit
|
48
48
|
|
49
49
|
# Returns true if this upgrade has finished researching
|
50
50
|
# @return [Boolean]
|
@@ -170,7 +170,7 @@ module Sc2
|
|
170
170
|
|
171
171
|
# @private
|
172
172
|
# @!attribute all_seen_unit_tags
|
173
|
-
# Privately keep track of all seen Unit tags
|
173
|
+
# Privately keep track of all seen Unit tags in order to detect new created units
|
174
174
|
attr_accessor :_all_seen_unit_tags
|
175
175
|
private :_all_seen_unit_tags
|
176
176
|
|
@@ -188,14 +188,6 @@ module Sc2
|
|
188
188
|
# @return [Sc2::UnitGroup] group of dead units
|
189
189
|
attr_accessor :event_units_destroyed
|
190
190
|
|
191
|
-
# TODO: Unit buff disabled, because it calls back too often (mineral in hand). Put back if useful
|
192
|
-
# @private
|
193
|
-
# Units (Unit/Structure) on which a new buff_ids appeared this frame
|
194
|
-
# See which buffs via: unit.buff_ids - unit.previous.buff_ids
|
195
|
-
# Read this on_step. Alternative to callback on_unit_buffed
|
196
|
-
# @!attribute event_units_destroyed
|
197
|
-
# attr_accessor :event_units_buffed
|
198
|
-
|
199
191
|
# Returns static [Api::UnitTypeData] for a unit
|
200
192
|
# @param unit [Integer,Api::Unit] Api::UnitTypeId or Api::Unit
|
201
193
|
# @return [Api::UnitTypeData]
|
@@ -360,7 +352,6 @@ module Sc2
|
|
360
352
|
|
361
353
|
# Event-driven unit groups as callback alternatives
|
362
354
|
@event_units_damaged = UnitGroup.new
|
363
|
-
# @event_units_buffed = UnitGroup.new
|
364
355
|
|
365
356
|
# Categorization of self/enemy, structure/unit ---
|
366
357
|
own_alliance = self.own_alliance
|
@@ -470,7 +461,7 @@ module Sc2
|
|
470
461
|
on_unit_type_changed(unit, previous_unit.unit_type)
|
471
462
|
end
|
472
463
|
|
473
|
-
# Check if a unit
|
464
|
+
# Check if a unit has taken damage
|
474
465
|
if unit.health < previous_unit.health || unit.shield < previous_unit.shield
|
475
466
|
damage_amount = previous_unit.health - unit.health + previous_unit.shield - unit.shield
|
476
467
|
@event_units_damaged.add(unit)
|
data/lib/sc2ai/player.rb
CHANGED
@@ -19,6 +19,7 @@ module Sc2
|
|
19
19
|
# include Sc2::Connection::ConnectionListener
|
20
20
|
|
21
21
|
extend Forwardable
|
22
|
+
|
22
23
|
def_delegators :@api, :add_listener
|
23
24
|
|
24
25
|
# Known races for detecting race on Api::Race::RANDOM or nil
|
@@ -72,6 +73,21 @@ module Sc2
|
|
72
73
|
# @return [String] ladder matches will set an opponent id
|
73
74
|
attr_accessor :opponent_id
|
74
75
|
|
76
|
+
# @!attribute timer
|
77
|
+
# Keeps track of time spent in steps.
|
78
|
+
# @see Sc2::StepTimer
|
79
|
+
# @example
|
80
|
+
# # Useful for step-time as on ladder
|
81
|
+
# @timer.avg_step_time
|
82
|
+
# # Recent steps time, updated periodically. Good for debug ui
|
83
|
+
# @timer.avg_recent_step_time
|
84
|
+
# # Step time between now and previous measure and how many steps
|
85
|
+
# @timer.previous_on_step_time # Time we spent on step
|
86
|
+
# @timer.previous_on_step_count # How many steps we took
|
87
|
+
#
|
88
|
+
# @return [Sc2::StepTimer]
|
89
|
+
attr_accessor :timer
|
90
|
+
|
75
91
|
# @param race [Integer] see {Api::Race}
|
76
92
|
# @param name [String]
|
77
93
|
# @param type [Integer] see {Api::PlayerType}
|
@@ -217,59 +233,31 @@ module Sc2
|
|
217
233
|
def configure
|
218
234
|
end
|
219
235
|
|
220
|
-
alias_method :before_join, :configure
|
221
|
-
|
222
236
|
# TODO: If this suffices for Bot and Observer, they should share this code.
|
223
237
|
# Initializes and refreshes game data and runs the game loop
|
224
238
|
# @return [Integer] One of Api::Result::VICTORY, Api::Result::DEFEAT, Api::Result::TIE, Api::Result::UNDECIDED
|
225
239
|
def play
|
240
|
+
@timer = StepTimer.new(self)
|
241
|
+
|
226
242
|
# Step 0
|
227
243
|
prepare_start
|
228
244
|
refresh_state
|
229
245
|
started
|
230
246
|
|
247
|
+
@timer.update
|
248
|
+
|
231
249
|
# Callback before first step is taken
|
232
250
|
on_start
|
251
|
+
|
233
252
|
# Callback for step 0
|
234
253
|
on_step
|
235
254
|
|
236
|
-
# Local play prints out avg times
|
237
|
-
unless Sc2.ladder?
|
238
|
-
running_avg_step_times = []
|
239
|
-
average_runtime = 0.0
|
240
|
-
end
|
241
|
-
|
242
|
-
puts ""
|
243
|
-
|
244
255
|
# Step 1 to n
|
245
|
-
i = 0
|
246
256
|
loop do
|
247
|
-
if i >= 5
|
248
|
-
i = 0
|
249
|
-
end
|
250
|
-
r = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
251
257
|
perform_actions
|
252
258
|
perform_debug_commands unless Sc2.ladder?
|
253
259
|
step_forward
|
254
260
|
|
255
|
-
unless Sc2.ladder?
|
256
|
-
time_delta = (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - r) * 1000
|
257
|
-
step_delta = game_loop - @previous.game_loop
|
258
|
-
# running_avg_step_times.shift if running_avg_step_times.size == 5
|
259
|
-
running_avg_step_times << [time_delta, step_delta]
|
260
|
-
|
261
|
-
if i == 0
|
262
|
-
sum_t, sum_s = running_avg_step_times.each_with_object([0, 0]) do |n, total|
|
263
|
-
total[0] += n[0]
|
264
|
-
total[1] += n[1]
|
265
|
-
end
|
266
|
-
average_runtime = sum_t / sum_s
|
267
|
-
running_avg_step_times.clear
|
268
|
-
end
|
269
|
-
print "\e[2K#{step_delta} Step(s) Took (ms): #{"%.2f" % time_delta} | Avg (ms/frame): #{"%.2f" % average_runtime}\n\e[1A\r"
|
270
|
-
end
|
271
|
-
|
272
|
-
i += 1
|
273
261
|
return @result unless @result.nil?
|
274
262
|
break if @status != :IN_GAME
|
275
263
|
end
|
@@ -587,6 +575,7 @@ module Sc2
|
|
587
575
|
end
|
588
576
|
|
589
577
|
refresh_state
|
578
|
+
@timer.update # Runtimes calc
|
590
579
|
on_step if @result.nil?
|
591
580
|
end
|
592
581
|
|
@@ -595,7 +584,6 @@ module Sc2
|
|
595
584
|
# @return [void]
|
596
585
|
# TODO: After cleaning up all the comments, review whether this is too heavy or not. #perf #clean
|
597
586
|
def refresh_state
|
598
|
-
# Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
599
587
|
step_to_loop = @realtime ? game_loop + @step_count : nil
|
600
588
|
response_observation = @api.observation(game_loop: step_to_loop)
|
601
589
|
return if response_observation.nil?
|
@@ -622,8 +610,7 @@ module Sc2
|
|
622
610
|
|
623
611
|
# First game-loop: set enemy and our race if random
|
624
612
|
if enemy.nil?
|
625
|
-
# Finish game_info load immediately, because we need
|
626
|
-
game_info
|
613
|
+
# Finish game_info load immediately, because we need its info
|
627
614
|
set_enemy
|
628
615
|
set_race_for_random if race == Api::Race::RANDOM
|
629
616
|
end
|
data/lib/sc2ai/ports.rb
CHANGED
@@ -113,7 +113,8 @@ module Sc2
|
|
113
113
|
end
|
114
114
|
|
115
115
|
# Will bind tcp port and return port if successful
|
116
|
-
#
|
116
|
+
# when port is zero, it will return random port bound to
|
117
|
+
# @param port [Integer]
|
117
118
|
# @return [Integer, Boolean] port if bind succeeds, false on failure
|
118
119
|
def bind(port)
|
119
120
|
socket = ::Socket.new(:AF_INET, :SOCK_STREAM, 0)
|
@@ -130,8 +131,19 @@ module Sc2
|
|
130
131
|
|
131
132
|
# A port configuration for a Match which allows generating Api::PortSet
|
132
133
|
class PortConfig
|
133
|
-
|
134
|
-
|
134
|
+
# @!attribute start_port
|
135
|
+
# @return [Integer]
|
136
|
+
attr_reader :start_port
|
137
|
+
# @!attribute server_port_set
|
138
|
+
# @return [Array<Integer>]
|
139
|
+
attr_reader :server_port_set
|
140
|
+
# @!attribute client_port_sets
|
141
|
+
# @return [Array<Integer>]
|
142
|
+
attr_reader :client_port_sets
|
143
|
+
|
144
|
+
# @param [Integer] start_port
|
145
|
+
# @param [Integer] num_players
|
146
|
+
# @param [Array<Integer>] ports
|
135
147
|
def initialize(start_port:, num_players:, ports: [])
|
136
148
|
@start_port = start_port
|
137
149
|
@server_port_set = nil
|
@@ -5,6 +5,14 @@ module Sc2
|
|
5
5
|
# Tolerance for floating-point comparisons.
|
6
6
|
TOLERANCE = 1e-9
|
7
7
|
|
8
|
+
# Returns self.
|
9
|
+
# If you're ever unsure if you have a Unit or Position in hand,
|
10
|
+
# this method allows safely calling `unknown_target.pos` to return a position.
|
11
|
+
# @example
|
12
|
+
# target.pos
|
13
|
+
# @return [Sc2::Position]
|
14
|
+
def pos = self
|
15
|
+
|
8
16
|
# Basic operations
|
9
17
|
|
10
18
|
# Loose equality matches on floats x and y.
|
@@ -70,10 +78,12 @@ module Sc2
|
|
70
78
|
dup.random_offset!(offset)
|
71
79
|
end
|
72
80
|
|
73
|
-
#
|
81
|
+
# Randomly change this point's x and y by the supplied offset.
|
82
|
+
# i.e. offset=2 can adjust x and y by any number in range -2..2
|
83
|
+
# @param offset [Float]
|
74
84
|
# @return [Sc2::Position] self
|
75
85
|
def random_offset!(offset)
|
76
|
-
offset = offset.to_f
|
86
|
+
offset = offset.to_f.abs
|
77
87
|
range = -offset..offset
|
78
88
|
offset!(rand(range), rand(range))
|
79
89
|
self
|
@@ -136,6 +146,7 @@ module Sc2
|
|
136
146
|
|
137
147
|
# Linear interpolation between this point and another for scale
|
138
148
|
# 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.
|
149
|
+
# @param other [Sc2::Position]
|
139
150
|
# @param scale [Float] a value between 0.0..1.0
|
140
151
|
# @return [Api::Point2D]
|
141
152
|
def lerp(other, scale)
|
@@ -214,12 +225,13 @@ module Sc2
|
|
214
225
|
|
215
226
|
# Returns [x,y] array tuple where floats are cast to ints
|
216
227
|
# Useful when trying to find the tile which something is on
|
217
|
-
# @return [Array<Integer, Integer>
|
228
|
+
# @return [Array<Integer, Integer>]
|
218
229
|
def to_atile
|
219
230
|
[x.to_i, y.to_i]
|
220
231
|
end
|
221
232
|
|
222
233
|
# @private
|
234
|
+
# @return [String]
|
223
235
|
def to_s
|
224
236
|
"#<#{self.class} x=#{x} y=#{y}>"
|
225
237
|
end
|
@@ -39,9 +39,50 @@ module Api
|
|
39
39
|
new_unit = @bot.all_units[tag]
|
40
40
|
return false if new_unit.nil? || new_unit == self
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
self.display_type = new_unit.display_type
|
43
|
+
self.alliance = new_unit.alliance
|
44
|
+
self.tag = new_unit.tag
|
45
|
+
self.unit_type = new_unit.unit_type
|
46
|
+
self.owner = new_unit.owner
|
47
|
+
self.pos = new_unit.pos
|
48
|
+
self.facing = new_unit.facing
|
49
|
+
self.radius = new_unit.radius
|
50
|
+
self.build_progress = new_unit.build_progress
|
51
|
+
self.cloak = new_unit.cloak
|
52
|
+
self.buff_ids = new_unit.buff_ids
|
53
|
+
self.detect_range = new_unit.detect_range
|
54
|
+
self.radar_range = new_unit.radar_range
|
55
|
+
self.is_selected = new_unit.is_selected
|
56
|
+
self.is_on_screen = new_unit.is_on_screen
|
57
|
+
self.is_blip = new_unit.is_blip
|
58
|
+
self.is_powered = new_unit.is_powered
|
59
|
+
self.is_active = new_unit.is_active
|
60
|
+
self.attack_upgrade_level = new_unit.attack_upgrade_level
|
61
|
+
self.armor_upgrade_level = new_unit.armor_upgrade_level
|
62
|
+
self.shield_upgrade_level = new_unit.shield_upgrade_level
|
63
|
+
self.health = new_unit.health
|
64
|
+
self.health_max = new_unit.health_max
|
65
|
+
self.shield = new_unit.shield
|
66
|
+
self.shield_max = new_unit.shield_max
|
67
|
+
self.energy = new_unit.energy
|
68
|
+
self.energy_max = new_unit.energy_max
|
69
|
+
self.mineral_contents = new_unit.mineral_contents
|
70
|
+
self.vespene_contents = new_unit.vespene_contents
|
71
|
+
self.is_flying = new_unit.is_flying
|
72
|
+
self.is_burrowed = new_unit.is_burrowed
|
73
|
+
self.is_hallucination = new_unit.is_hallucination
|
74
|
+
self.orders = new_unit.orders
|
75
|
+
self.add_on_tag = new_unit.add_on_tag
|
76
|
+
self.passengers = new_unit.passengers
|
77
|
+
self.cargo_space_taken = new_unit.cargo_space_taken
|
78
|
+
self.cargo_space_max = new_unit.cargo_space_max
|
79
|
+
self.assigned_harvesters = new_unit.assigned_harvesters
|
80
|
+
self.ideal_harvesters = new_unit.ideal_harvesters
|
81
|
+
self.weapon_cooldown = new_unit.weapon_cooldown
|
82
|
+
self.engaged_target_tag = new_unit.engaged_target_tag
|
83
|
+
self.buff_duration_remain = new_unit.buff_duration_remain
|
84
|
+
self.buff_duration_max = new_unit.buff_duration_max
|
85
|
+
self.rally_targets = new_unit.rally_targets
|
45
86
|
|
46
87
|
true
|
47
88
|
end
|
@@ -157,6 +198,24 @@ module Api
|
|
157
198
|
energy == energy_max
|
158
199
|
end
|
159
200
|
|
201
|
+
# Returns whether this is a melee (non-ranged) attacker
|
202
|
+
# Archon isn't melee; 3 range
|
203
|
+
# Hellbat is melee, but Hellion isn't melee; 5 range
|
204
|
+
# Roach isn't melee; just an attack animation when nearby
|
205
|
+
# @!attribute [r] is_melee?
|
206
|
+
# @return [Boolean] melee unit (non-ranged attacker)
|
207
|
+
def is_melee?
|
208
|
+
Sc2::UnitGroup::TYPE_MELEE.include?(unit_type)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Returns ranged attack units
|
212
|
+
# @see Sc2::UnitGroup::TYPE_RANGE
|
213
|
+
# @!attribute [r] is_ranged?
|
214
|
+
# @return [Boolean] if reasonably considered a ranged attacker
|
215
|
+
def is_ranged?
|
216
|
+
Sc2::UnitGroup::TYPE_RANGE.include?(unit_type)
|
217
|
+
end
|
218
|
+
|
160
219
|
# Some overrides to allow question mark references to boolean properties
|
161
220
|
|
162
221
|
# @!attribute [r] is_flying?
|
@@ -593,7 +652,7 @@ module Api
|
|
593
652
|
Api::UnitTypeId::STARPORTREACTOR
|
594
653
|
end
|
595
654
|
|
596
|
-
build(unit_type_id: unit_type_id, target
|
655
|
+
build(unit_type_id: unit_type_id, target:, queue_command:)
|
597
656
|
end
|
598
657
|
|
599
658
|
# For Terran builds a tech lab add-on on the current structure
|
@@ -607,21 +666,7 @@ module Api
|
|
607
666
|
when Api::UnitTypeId::STARPORT, Api::UnitTypeId::STARPORTFLYING
|
608
667
|
Api::UnitTypeId::STARPORTTECHLAB
|
609
668
|
end
|
610
|
-
build(unit_type_id: unit_type_id, target
|
611
|
-
end
|
612
|
-
|
613
|
-
private def target_for_addon_placement
|
614
|
-
# Attempts to auto-move left if not placeable
|
615
|
-
x = pos.x.floor
|
616
|
-
y = pos.y.floor
|
617
|
-
if !bot.geo.placeable?(x: x + 3, y: y - 1) ||
|
618
|
-
!bot.geo.placeable?(x: x + 3, y: y) ||
|
619
|
-
!bot.geo.placeable?(x: x + 2, y: y - 1) ||
|
620
|
-
!bot.geo.placeable?(x: x + 2, y: y)
|
621
|
-
return Api::Point2D[pos.x - 1, pos.y]
|
622
|
-
end
|
623
|
-
|
624
|
-
nil
|
669
|
+
build(unit_type_id: unit_type_id, target:, queue_command:)
|
625
670
|
end
|
626
671
|
|
627
672
|
# PROTOSS Convenience functions ---
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Sc2
|
2
|
+
# Tracks various metrics about your step time performance.
|
3
|
+
class StepTimer
|
4
|
+
TRUNCATED_TIME_RANGE = 0..999.99
|
5
|
+
HEALTHY_STEP_TIME_MS = (1000.0 / 22.4)
|
6
|
+
private_constant :TRUNCATED_TIME_RANGE, :HEALTHY_STEP_TIME_MS
|
7
|
+
|
8
|
+
# @!attribute [r] avg_real_time
|
9
|
+
# @return [Float] Realtime average time per step in ms. Includes SC2 wait time
|
10
|
+
attr_reader :avg_real_time
|
11
|
+
|
12
|
+
# @!attribute [r] avg_step_time
|
13
|
+
# @return [Float] Total average time per step in ms. "Ladder Time" as measured by aiarena
|
14
|
+
attr_reader :avg_step_time
|
15
|
+
|
16
|
+
# @!attribute [r] recent_average
|
17
|
+
# @return [Float] Running average time per step in ms for recent couple of steps
|
18
|
+
attr_accessor :avg_recent_step_time
|
19
|
+
|
20
|
+
# @!attribute [r] previous_on_step_time
|
21
|
+
# @return [Float] Previous on_step took this amount of ms to run
|
22
|
+
attr_reader :previous_on_step_time
|
23
|
+
|
24
|
+
# @!attribute [r] previous_on_step_count
|
25
|
+
# @return [Integer] Number of frames which passed previous on_step
|
26
|
+
attr_reader :previous_on_step_count
|
27
|
+
|
28
|
+
# @private
|
29
|
+
private attr_accessor :bot
|
30
|
+
# @private
|
31
|
+
private attr_accessor :previous_external_time, :previous_update_time
|
32
|
+
# @private
|
33
|
+
private attr_accessor :recent_sum_time, :recent_sum_steps, :recent_update_counter
|
34
|
+
|
35
|
+
# @private
|
36
|
+
# @param bot [Sc2::Player]
|
37
|
+
def initialize(bot)
|
38
|
+
@bot = bot
|
39
|
+
|
40
|
+
# Tracking vars
|
41
|
+
@previous_external_time = @bot.api.external_time
|
42
|
+
@previous_update_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
43
|
+
@recent_update_counter = 0
|
44
|
+
@recent_sum_steps = 0
|
45
|
+
@recent_sum_time = 0.0
|
46
|
+
@total_step_time = 0.0
|
47
|
+
@total_real_time = 0.0
|
48
|
+
|
49
|
+
# Output vars
|
50
|
+
@avg_real_time = 0.0
|
51
|
+
@avg_step_time = 0.0
|
52
|
+
@avg_recent_step_time = 0.0
|
53
|
+
@previous_on_step_time = 0.0
|
54
|
+
@previous_on_step_count = 0
|
55
|
+
end
|
56
|
+
|
57
|
+
# How much time we have left in this step, to be healthy
|
58
|
+
def allowance
|
59
|
+
return 0.0 if @bot.realtime
|
60
|
+
time_passed = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @previous_update_time
|
61
|
+
external_delta = @bot.api.external_time - @previous_external_time
|
62
|
+
(HEALTHY_STEP_TIME_MS * @bot.step_count) - (time_passed - external_delta)
|
63
|
+
end
|
64
|
+
|
65
|
+
# A one-line string summary of all tracked times
|
66
|
+
# @return [String]
|
67
|
+
def summary
|
68
|
+
"AVG Real: #{format_time(@avg_real_time)} | " \
|
69
|
+
"AVG Total: #{format_time(@avg_step_time)} | " \
|
70
|
+
"AVG Recent: #{format_time(@avg_recent_step_time)} | " \
|
71
|
+
"Previous #{@previous_on_step_count} Step(s): #{format_time(@previous_on_step_time)} (ms)"
|
72
|
+
end
|
73
|
+
|
74
|
+
# A hash containing :avg_real_time, :avg_step_time, :avg_recent_step_time, :previous_on_step_count, :previous_on_step_time
|
75
|
+
# @return [Hash]
|
76
|
+
def to_h
|
77
|
+
{
|
78
|
+
avg_real_time: avg_real_time,
|
79
|
+
avg_step_time: avg_step_time,
|
80
|
+
avg_recent_step_time: avg_recent_step_time,
|
81
|
+
previous_on_step_count: previous_on_step_count,
|
82
|
+
previous_on_step_time: previous_on_step_time
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def update
|
87
|
+
# Number of steps which have passed
|
88
|
+
step_delta = @bot.game_loop - @bot.previous.game_loop
|
89
|
+
|
90
|
+
# Time spent waiting for SC2 is not counted on ladder
|
91
|
+
external_time = @bot.api.external_time
|
92
|
+
external_delta = external_time - @previous_external_time
|
93
|
+
@previous_external_time = external_time
|
94
|
+
|
95
|
+
# Start calculating...
|
96
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
97
|
+
|
98
|
+
time_delta = now - @previous_update_time
|
99
|
+
@previous_update_time = now
|
100
|
+
|
101
|
+
# Update real time
|
102
|
+
@total_real_time += time_delta
|
103
|
+
|
104
|
+
# Update step time (excl external)
|
105
|
+
time_delta -= external_delta
|
106
|
+
@total_step_time += time_delta
|
107
|
+
|
108
|
+
# Write public values...
|
109
|
+
@previous_on_step_time = time_delta
|
110
|
+
@previous_on_step_count = step_delta
|
111
|
+
@avg_real_time = @total_real_time / (@bot.game_loop + 1)
|
112
|
+
@avg_step_time = @total_step_time / (@bot.game_loop + 1)
|
113
|
+
|
114
|
+
@recent_sum_time += time_delta
|
115
|
+
@recent_sum_steps += step_delta
|
116
|
+
if @recent_update_counter >= 11
|
117
|
+
@avg_recent_step_time = @recent_sum_time / @recent_sum_steps
|
118
|
+
|
119
|
+
@recent_sum_time = 0.0
|
120
|
+
@recent_sum_steps = 0
|
121
|
+
@recent_update_counter = 0
|
122
|
+
end
|
123
|
+
@recent_update_counter += 1
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
# @private
|
129
|
+
# @return [String] time with ansi colour coded
|
130
|
+
def format_time(time)
|
131
|
+
"%5.2f" % time.clamp(TRUNCATED_TIME_RANGE)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|