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



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

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



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

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

#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:



178
179
180
# File 'lib/sc2ai/player/units.rb', line 178

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



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

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:



159
160
161
# File 'lib/sc2ai/player/units.rb', line 159

def power_sources
  @power_sources
end

#radar_ringsObject

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



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

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



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

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:



205
206
207
# File 'lib/sc2ai/player/units.rb', line 205

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)


257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/sc2ai/player/units.rb', line 257

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)


285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/sc2ai/player/units.rb', line 285

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



247
248
249
250
251
252
253
# File 'lib/sc2ai/player/units.rb', line 247

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

#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)


309
310
311
# File 'lib/sc2ai/player/units.rb', line 309

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:



197
198
199
200
# File 'lib/sc2ai/player/units.rb', line 197

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:



231
232
233
234
235
236
237
238
239
# File 'lib/sc2ai/player/units.rb', line 231

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



224
225
226
# File 'lib/sc2ai/player/units.rb', line 224

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)


93
94
95
96
97
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
# File 'lib/sc2ai/player/units.rb', line 93

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?(: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)


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

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:



212
213
214
# File 'lib/sc2ai/player/units.rb', line 212

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)


80
81
82
83
84
85
86
87
88
89
# File 'lib/sc2ai/player/units.rb', line 80

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:



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/sc2ai/player/units.rb', line 53

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