Module: Sc2::Player::Units

Included in:
Bot, Enemy, PreviousState
Defined in:
lib/sc2ai/player/units.rb

Overview

Helper methods for working with units

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#_all_seen_unit_tags=(value) ⇒ Object

Privately keep track of all seen Unit tags (excl structures) in order to detect new created units



174
175
176
# File 'lib/sc2ai/player/units.rb', line 174

def _all_seen_unit_tags=(value)
  @_all_seen_unit_tags = value
end

#all_seen_unit_tagsObject

Privately keep track of all seen Unit tags (excl structures) in order to detect new created units



174
# File 'lib/sc2ai/player/units.rb', line 174

attr_accessor :_all_seen_unit_tags

#all_unitsSc2::UnitGroup

Returns:



9
10
11
# File 'lib/sc2ai/player/units.rb', line 9

def all_units
  @all_units
end

#blipsSc2::UnitGroup

Returns a group of blip units.

Returns:



41
42
43
# File 'lib/sc2ai/player/units.rb', line 41

def blips
  @blips
end

#effectsSc2::UnitGroup

Returns a group of neutral units.

Returns:



36
37
38
# File 'lib/sc2ai/player/units.rb', line 36

def effects
  @effects
end

#event_units_damagedSc2::UnitGroup

Returns group of Units and Structures effected.

Returns:



183
184
185
# File 'lib/sc2ai/player/units.rb', line 183

def event_units_damaged
  @event_units_damaged
end

#event_units_destroyedObject

TODO: Unit buff disabled, because it calls back too often (mineral in hand). Put back if useful Units (Unit/Structure) on which a new buff_ids appeared this frame See which buffs via: unit.buff_ids - unit.previous.buff_ids Read this on_step. Alternative to callback on_unit_buffed attr_accessor :event_units_buffed



189
190
191
# File 'lib/sc2ai/player/units.rb', line 189

def event_units_destroyed
  @event_units_destroyed
end

#neutralSc2::UnitGroup

Returns a group of neutral units.

Returns:



29
30
31
# File 'lib/sc2ai/player/units.rb', line 29

def neutral
  @neutral
end

#placeholdersObject

A list of structures which haven’t started



24
25
26
# File 'lib/sc2ai/player/units.rb', line 24

def placeholders
  @placeholders
end

#power_sourcesArray<Api::RadarRing>

Returns an array of radar rings sources.

Returns:



164
165
166
# File 'lib/sc2ai/player/units.rb', line 164

def power_sources
  @power_sources
end

#radar_ringsObject

An array of Sensor tower rings as per minimap. It has a ‘pos` and a `radius`



169
170
171
# File 'lib/sc2ai/player/units.rb', line 169

def radar_rings
  @radar_rings
end

#structuresObject

A full list of all your structures (non-units)



19
20
21
# File 'lib/sc2ai/player/units.rb', line 19

def structures
  @structures
end

#unitsSc2::UnitGroup

Returns a group of placeholder structures.

Returns:



14
15
16
# File 'lib/sc2ai/player/units.rb', line 14

def units
  @units
end

#upgrades_completedObject (readonly)

Returns the upgrade ids you have acquired such as weapon upgrade and armor upgrade ids. Shorthand for observation.raw_data.player.upgrade_ids



47
# File 'lib/sc2ai/player/units.rb', line 47

def upgrades_completed = observation&.raw_data&.player&.upgrade_ids.to_a || [] # not a unit

Instance Method Details

#ability_data(ability_id) ⇒ Api::AbilityData

Returns static [Api::AbilityData] for an ability

Parameters:

  • ability_id (Integer)

    Api::AbilityId::*

Returns:



210
211
212
# File 'lib/sc2ai/player/units.rb', line 210

def ability_data(ability_id)
  data.abilities[ability_id]
end

#can_afford?(unit_type_id, quantity: 1) ⇒ Boolean

Checks whether you have the resources to construct quantity of unit type

Returns:

  • (Boolean)


262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/sc2ai/player/units.rb', line 262

def can_afford?(unit_type_id, quantity: 1)
  unit_type_data = unit_data(unit_type_id)
  return false if unit_type_data.nil?

  mineral_cost = unit_type_data.mineral_cost * quantity
  if common.minerals - spent_minerals < mineral_cost
    return false # not enough minerals
  end

  vespene_cost = unit_type_data.vespene_cost * quantity
  if common.vespene - spent_vespene < vespene_cost
    return false # you require more vespene gas
  end

  supply_cost = unit_type_data.food_required
  supply_cost = 1 if unit_type_id == Api::UnitTypeId::ZERGLING
  supply_cost *= quantity

  free_supply = common.food_cap - common.food_used
  if free_supply - spent_supply < supply_cost
    return false # you must construct additional pylons
  end

  true
end

#can_afford_upgrade?(upgrade_id) ⇒ Boolean

Checks whether you have the resources to

Returns:

  • (Boolean)


290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/sc2ai/player/units.rb', line 290

def can_afford_upgrade?(upgrade_id)
  unit_type_data = upgrade_data(upgrade_id)
  return false if unit_type_data.nil?

  mineral_cost = unit_type_data.mineral_cost
  if common.minerals - spent_minerals < mineral_cost
    return false # not enough minerals
  end

  vespene_cost = unit_type_data.vespene_cost
  if common.vespene - spent_vespene < vespene_cost
    return false # you require more vespene gas
  end

  true
end

#subtract_cost(unit_type_id) ⇒ void

This method returns an undefined value.

Sums the cost (mineral/vespene/supply) of unit type used for internal spend trackers This is called internally when building/morphing/training



252
253
254
255
256
257
258
# File 'lib/sc2ai/player/units.rb', line 252

def subtract_cost(unit_type_id)
  unit_type_data = unit_data(unit_type_id)

  @spent_minerals += unit_type_data.mineral_cost
  @spent_vespene += unit_type_data.vespene_cost
  @spent_supply += unit_type_data.food_required
end

#tech_requirement_met?(unit_type_id) ⇒ Boolean

Checks whether you have met the tech requirements for building a specific unit type

Parameters:

  • unit_type_id (Integer)

Returns:

  • (Boolean)


310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/sc2ai/player/units.rb', line 310

def tech_requirement_met?(unit_type_id)
  created_from_unit_type = Api::TechTree.unit_created_from(unit_type_id: unit_type_id)
  required_building = Api::TechTree.unit_type_creation_abilities(
    source: created_from_unit_type.first,
    target: unit_type_id
  )[:required_building]

  # Ensure we have required building, if there's such a requirement
  if required_building
    return false unless all_units.owned.select_type(required_building).completed.size > 0
  end

  # Ensure we have a completed source which it's created from
  all_units.owned.select_type(created_from_unit_type).completed.size > 0
end

#unit_ability_available?(unit_tag:, ability_id:) ⇒ Boolean

Returns whether Query Available Ability is true for unit and tag Queries API if necessary. Uses batching in the background.

Parameters:

  • unit_tag (Integer)
  • ability_id (Integer)

Returns:

  • (Boolean)


333
334
335
# File 'lib/sc2ai/player/units.rb', line 333

def unit_ability_available?(unit_tag:, ability_id:)
  !!available_abilities[unit_tag]&.include?(ability_id)
end

#unit_data(unit) ⇒ Api::UnitTypeData

Returns static [Api::UnitTypeData] for a unit

Parameters:

  • unit (Integer, Api::Unit)

    Api::UnitTypeId or Api::Unit

Returns:



202
203
204
205
# File 'lib/sc2ai/player/units.rb', line 202

def unit_data(unit)
  id = unit.is_a?(Integer) ? unit : unit.unit_type
  data.units[id]
end

#unit_group_from_tags(tags) ⇒ Sc2::UnitGroup

Creates a unit group from all_units with matching tag

Parameters:

  • tags (Array<Integer>)

    array of unit tags

Returns:



236
237
238
239
240
241
242
243
244
# File 'lib/sc2ai/player/units.rb', line 236

def unit_group_from_tags(tags)
  return unless tags.is_a? Array

  ug = UnitGroup.new
  tags.each do |tag|
    ug.add(@all_units[tag])
  end
  ug
end

#unit_has_attribute?(unit, attribute) ⇒ Boolean

Checks unit data for an attribute value

Examples:

unit_has_attribute?(Api::UnitTypeId::SCV, Api::Attribute::MECHANICAL)
unit_has_attribute?(units.workers.first, :MECHANICAL)
unit_has_attribute?(Api::UnitTypeId::SCV, :MECHANICAL)

Parameters:

  • unit (Integer, Api::Unit)

    Api::UnitTypeId or Api::Unit

  • attribute (Symbol)

    Api::Attribute, i.e. Api::Attribute::MECHANICAL or :Mechanical

Returns:

  • (Boolean)

    whether unit has attribute



229
230
231
# File 'lib/sc2ai/player/units.rb', line 229

def unit_has_attribute?(unit, attribute)
  unit_data(unit).attributes.include? attribute
end

#units_in_progress(unit_type_id) ⇒ Integer

For this unit type, tells you how many are in progress by checking orders for all it’s sources.

Returns:

  • (Integer)


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/sc2ai/player/units.rb', line 98

def units_in_progress(unit_type_id)
  if unit_type_id == Api::UnitTypeId::REACTOR
    return units_in_progress(Api::UnitTypeId::BARRACKSREACTOR) +
        units_in_progress(Api::UnitTypeId::FACTORYREACTOR) +
        units_in_progress(Api::UnitTypeId::STARPORTREACTOR)
  elsif unit_type_id == Api::UnitTypeId::TECHLAB
    return units_in_progress(Api::UnitTypeId::BARRACKSTECHLAB) +
        units_in_progress(Api::UnitTypeId::FACTORYTECHLAB) +
        units_in_progress(Api::UnitTypeId::STARPORTTECHLAB)
  end

  # Get source unit
  source_unit_types = Api::TechTree.unit_created_from(unit_type_id: unit_type_id)

  # When building from LARVA, check the intermediate models
  if source_unit_types.include?(Api::UnitTypeId::LARVA)
    source_unit_types << Api::UnitTypeId::EGG
  elsif source_unit_types.include?(Api::UnitTypeId::BANELING)
    # For certain Zerg types, return the count of specific intermediate egg/cocoon
    return units.select_type(Api::UnitTypeId::BANELINGCOCOON).size
  elsif source_unit_types.include?(Api::UnitTypeId::RAVAGER)
    return units.select_type(Api::UnitTypeId::RAVAGERCOCOON).size
  elsif source_unit_types.include?(Api::UnitTypeId::OVERSEER)
    return units.select_type(Api::UnitTypeId::OVERLORDCOCOON).size
  elsif source_unit_types.include?(Api::UnitTypeId::LURKERMP)
    return units.select_type(Api::UnitTypeId::LURKERMPEGG).size
  elsif source_unit_types.include?(Api::UnitTypeId::BROODLORD)
    return units.select_type(Api::UnitTypeId::BROODLORDCOCOON).size
  end

  unit_create_ability = Api::TechTree.unit_type_creation_ability_id(
    source: source_unit_types.first,
    target: unit_type_id
  )

  origin = if unit_data(source_unit_types.first).attributes.include?(Api::Attribute::STRUCTURE)
    structures
  else
    units
  end
  # For SCV, the structure might be completed but dangling order for a few frames.
  source_is_scv = source_unit_types.include?(Api::UnitTypeId::SCV)

  # Let's count orders matching the ability
  total_in_progress = origin.select_type(source_unit_types).sum do |source|
    source.orders.count do |order|
      if order.ability_id == unit_create_ability
        if source_is_scv
          # If we are a SCV, do not count our order if the target is a completed structure pos
          structures.select_type(unit_type_id).completed.none? do |structure|
            structure.pos == order.target_world_space_pos
          end
        else
          true
        end
      end
    end
  end
  total_in_progress *= 2 if unit_type_id == Api::UnitTypeId::ZERGLING

  total_in_progress
end

#upgrade_completed?(upgrade_id) ⇒ Boolean

Returns true if this upgrade has finished researching

Returns:

  • (Boolean)


51
52
53
# File 'lib/sc2ai/player/units.rb', line 51

def upgrade_completed?(upgrade_id)
  upgrades_completed.include?(upgrade_id)
end

#upgrade_data(upgrade_id) ⇒ Api::UpgradeData

Returns static [Api::UpgradeData] for an upgrade id

Parameters:

  • upgrade_id (Integer)

    Api::UpgradeId::*

Returns:



217
218
219
# File 'lib/sc2ai/player/units.rb', line 217

def upgrade_data(upgrade_id)
  data.upgrades[upgrade_id]
end

#upgrade_in_progress?(upgrade_id) ⇒ Boolean

Returns true if the upgrade is busy researching

Returns:

  • (Boolean)


85
86
87
88
89
90
91
92
93
94
# File 'lib/sc2ai/player/units.rb', line 85

def upgrade_in_progress?(upgrade_id)
  structure_unit_type_id = Api::TechTree.upgrade_researched_from(upgrade_id: upgrade_id)
  research_ability_id = Api::TechTree.upgrade_research_ability_id(upgrade_id: upgrade_id)
  structures.select_type(structure_unit_type_id).each do |structure|
    structure.orders.each do |order|
      return true if order.ability_id == research_ability_id
    end
  end
  false
end

#upgrades_in_progressArray<Integer>

Returns the upgrade ids which are researching or queued Not set for enemy.

Returns:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/sc2ai/player/units.rb', line 58

def upgrades_in_progress
  # We need to scan every structure which performs upgrades for any order with an upgrade ability

  result = []
  # Loop every upgrade structure
  structures
    .select_type(Api::TechTree.upgrade_structure_unit_type_ids)
    .each do |structure|
    next unless structure.is_active? # Skip idle

    # Check if any order at a structure contains an upgrade ability
    structure.orders.each do |order|
      Api::TechTree.upgrade_ability_data(structure.unit_type).each do |upgrade_id, update_info|
        if update_info[:ability] == order.ability_id
          # Save the upgrade_id
          result << upgrade_id
        end
      end
    end
  end

  # If the API told use it's complete, but an order still lingers, trust the API
  result - upgrades_completed
end