Module: Sc2::Connection::Requests

Included in:
Sc2::Connection
Defined in:
lib/sc2ai/connection/requests.rb

Overview

Sends protobuf requests over Connection to Client

Instance Method Summary collapse

Instance Method Details

#action(actions) ⇒ Api::ResponseAction

Executes an array of [Api::Action] for a participant

Parameters:

Returns:



251
252
253
254
255
# File 'lib/sc2ai/connection/requests.rb', line 251

def action(actions)
  send_request_for action: Api::RequestAction.new(
    actions: actions
  )
end

#available_mapsApi::ResponseAvailableMaps

Returns directory of maps that can be played on.

Returns:



398
399
400
# File 'lib/sc2ai/connection/requests.rb', line 398

def available_maps
  send_request_for available_maps: Api::RequestAvailableMaps.new
end

#create_game(map:, players:, realtime: false) ⇒ Object

Send to host to initialize game



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/sc2ai/connection/requests.rb', line 10

def create_game(map:, players:, realtime: false)
  send_request_for create_game: Api::RequestCreateGame.new(
    local_map: Api::LocalMap.new(map_path: map.path),
    player_setup: players.map do |player|
      Api::PlayerSetup.new(
        type: player.type,
        race: player.race,
        player_name: player.name,
        difficulty: player.difficulty,
        ai_build: player.ai_build
      )
    end,
    realtime:
  )
end

#data(ability_id: true, unit_type_id: true, upgrade_id: true, buff_id: true, effect_id: true) ⇒ Api::ResponseData

Data about different gameplay elements. May be different for different games. Note that buff_id and effect_id gives worse quality data than generated from stableids (EffectId and BuffId) Those options are disabled by default

Parameters:

  • ability_id (Boolean) (defaults to: true)

    to include ability data

  • unit_type_id (Boolean) (defaults to: true)

    to include unit data

  • upgrade_id (Boolean) (defaults to: true)

    to include upgrade data

  • buff_id (Boolean) (defaults to: true)

    to get include buff data

  • effect_id (Boolean) (defaults to: true)

    to get to include effect data

Returns:



190
191
192
193
194
195
196
197
198
# File 'lib/sc2ai/connection/requests.rb', line 190

def data(ability_id: true, unit_type_id: true, upgrade_id: true, buff_id: true, effect_id: true)
  send_request_for data: Api::RequestData.new(
    ability_id:,
    unit_type_id:,
    upgrade_id:,
    buff_id:,
    effect_id:
  )
end

#debug(commands) ⇒ void

This method returns an undefined value.

Display debug information and execute debug actions

Parameters:



415
416
417
418
419
# File 'lib/sc2ai/connection/requests.rb', line 415

def debug(commands)
  send_request_for debug: Api::RequestDebug.new(
    debug: commands
  )
end

#game_infoApi::ResponseGameInfo

Static data about the current game and map.



177
178
179
# File 'lib/sc2ai/connection/requests.rb', line 177

def game_info
  send_request_for game_info: Api::RequestGameInfo.new
end

#join_game(race:, name:, server_host:, port_config:, enable_feature_layer: false, interface_options: {}) ⇒ Object

Send to host and all clients for game to begin.

Parameters:

  • race (Google::Protobuf::EnumValue)

    Api::Race

  • name (String)

    player name

  • server_host (String)

    hostname or ip of sc2 client

  • port_config (Sc2::PortConfig)

    port config auto or basic, using start port

  • enable_feature_layer (Boolean) (defaults to: false)

    Enables the feature layer at 1x1 pixels

  • interface_options (Hash) (defaults to: {})

Options Hash (interface_options:):

  • :raw (Boolean) — default: true

    raw interface enabled, default true

  • :score (Boolean) — default: false

    score game info

  • :show_cloaked (Boolean) — default: true

    hows details about cloaked units

  • :show_burrowed_shadows (Boolean) — default: true

    shows some details for those that produce a shadow

  • :show_placeholders (Boolean) — default: true

    return placeholder units (buildings to be constructed)

  • :raw_affects_selection (Boolean) — default: false

    for live raw does whatever it wants, for local it keeps your selection by default.

  • :raw_crop_to_playable_area (Boolean) — default: true

    trims away unplayable parts of map, else map is 255x255 with dead space. performant if true.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/sc2ai/connection/requests.rb', line 40

def join_game(race:, name:, server_host:, port_config:, enable_feature_layer: false, interface_options: {})
  interface_options ||= {}

  send_request_for join_game: Api::RequestJoinGame.new(
    # TODO: For Observer support, get player_index for observer,
    #   don't set race and pass observed_player_id: player_index
    # observed_player_id: 0, # For observer
    # --
    race:,
    player_name: name,
    host_ip: server_host,
    server_ports: port_config.server_port_set,
    client_ports: port_config.client_port_sets,
    options: Api::InterfaceOptions.new(
      {
        raw: true,
        score: false,
        feature_layer: feature_layer_interface_options(enable_feature_layer),
        show_cloaked: true,
        show_burrowed_shadows: true,
        show_placeholders: true,
        raw_affects_selection: Sc2.ladder?,
        raw_crop_to_playable_area: true
      }.merge!(interface_options)
    )
  )
end

#leave_gameObject

Multiplayer only. Disconnects from a multiplayer game, equivalent to surrender. Keeps client alive.



152
153
154
# File 'lib/sc2ai/connection/requests.rb', line 152

def leave_game
  send_request_for leave_game: Api::RequestLeaveGame.new
end

#observation(game_loop: nil) ⇒ Object

Snapshot of the current game state. Primary source for raw information

Parameters:

  • game_loop (Integer) (defaults to: nil)

    you wish to wait for (realtime only)



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/sc2ai/connection/requests.rb', line 202

def observation(game_loop: nil)
  # Sc2.logger.debug { "#{self.class}.#{__method__} game_loop: #{game_loop}" }
  if game_loop.nil?
    # Uncomment to enable multiple gc
    # Async do
    #   result = Async do

    @_cached_request_observation ||= Api::Request.new(
      observation: Api::RequestObservation.new
    ).to_proto
    @websocket.send_binary(@_cached_request_observation)
    response = Api::Response.decode(@websocket.read.to_str)

    if @status != response.status
      @status = response.status
      @listeners[StatusListener.name]&.each { _1.on_status_change(@status) }
    end

    response.observation

    # Uncomment to enable manual GC
    # end

    #   Async do
    #     # A step command is synchronous for both bots.
    #     # Bot A will wait for Bot B, then both get responses.
    #     # If we're ahead or even not, we can perform a minor GC sweep while we wait.
    #     # If the server notifies the other machine first
    #     # This smooths out unexpected hiccups and reduces overall major gc sweeps, possibly for free.
    #     begin
    #       GC.start(full_mark: false, immediate_sweep: true)
    #       # if rand(100).zero? # Just below every 5 seconds
    #       #   GC.compact
    #       # end
    #     rescue
    #       # noop - just here for cleaner exceptions on interrupt
    #     end
    #   end
    #   result.wait
    # end.wait

  else
    send_request_for observation: Api::RequestObservation.new(game_loop:)
  end
end

#observer_action(actions) ⇒ Object

Executes an actions for an observer.

Parameters:



259
260
261
262
263
264
265
# File 'lib/sc2ai/connection/requests.rb', line 259

def observer_action(actions)
  # ActionObserverCameraMove camera_move = 2;
  # ActionObserverCameraFollowPlayer camera_follow_player = 3;
  send_request_for obs_action: Api::RequestObserverAction.new(
    actions: actions
  )
end

#observer_action_camera_move(world_pos, distance = 0) ⇒ Object

Moves observer camera to a position at a distance

Parameters:

  • world_pos (Api::Point2D)
  • distance (Float) (defaults to: 0)

    Distance between camera and terrain. Larger value zooms out camera. Defaults to standard camera distance if set to 0.



270
271
272
273
274
275
276
277
# File 'lib/sc2ai/connection/requests.rb', line 270

def observer_action_camera_move(world_pos, distance = 0)
  observer_action([Api::ObserverAction.new(
    camera_move: Api::ActionObserverCameraMove.new(
      world_pos:,
      distance:
    )
  )])
end

#pingObject

Network ping for testing connection.



408
409
410
# File 'lib/sc2ai/connection/requests.rb', line 408

def ping
  send_request_for ping: Api::RequestPing.new
end

#query(pathing: nil, abilities: nil, placements: nil, ignore_resource_requirements: false) ⇒ Api::ResponseQuery

Additional methods for inspecting game state. Synchronous and must wait on response

Parameters:

Returns:



295
296
297
298
299
300
301
302
# File 'lib/sc2ai/connection/requests.rb', line 295

def query(pathing: nil, abilities: nil, placements: nil, ignore_resource_requirements: false)
  send_request_for query: Api::RequestQuery.new(
    pathing:,
    abilities:,
    placements:,
    ignore_resource_requirements:
  )
end

#query_abilities(queries, ignore_resource_requirements: false) ⇒ Array<Api::ResponseQueryAvailableAbilities>

Queries one or more ability-available checks

Parameters:

  • queries (Array<Api::RequestQueryAvailableAbilities>)

    one or more pathing queries

  • ignore_resource_requirements (Boolean) (defaults to: false)

    Ignores requirements like food, minerals and so on.

Returns:



320
321
322
323
324
325
326
327
328
# File 'lib/sc2ai/connection/requests.rb', line 320

def query_abilities(queries, ignore_resource_requirements: false)
  arr_queries = queries.is_a?(Array) ? queries : [queries]

  response = send_request_for query: Api::RequestQuery.new(
    abilities: arr_queries,
    ignore_resource_requirements:
  )
  response.abilities
end

#query_abilities_for_unit_tags(unit_tags, ignore_resource_requirements: false) ⇒ Array<Api::ResponseQueryAvailableAbilities>

Queries available abilities for units

Parameters:

  • unit_tags (Array<Integer>)

    an array of unit tags or a single tag

  • ignore_resource_requirements (Boolean) (defaults to: false)

    Ignores requirements like food, minerals and so on.

Returns:



334
335
336
337
338
339
340
341
342
343
# File 'lib/sc2ai/connection/requests.rb', line 334

def query_abilities_for_unit_tags(unit_tags, ignore_resource_requirements: false)
  return [] if unit_tags.nil?
  queries = []
  unit_tags = [unit_tags] unless unit_tags.is_a? Array
  unit_tags.each do |unit_tag|
    queries << Api::RequestQueryAvailableAbilities.new(unit_tag: unit_tag)
  end

  query_abilities(queries, ignore_resource_requirements:)
end

#query_ability_ids_for_unit(unit, ignore_resource_requirements: false) ⇒ Array<Integer>

Queries available ability ids for one unit Shortened response over #query_abilities_for_unit_tags, since we know the tag already and can just return an array of ability ids. Note: Querying single units are expensive and should be batched with #query_abilities_for_unit_tags

Parameters:

  • unit (Api::Unit, Integer)

    a unit or a tag.

Returns:

  • (Array<Integer>)

    array of ability ids



351
352
353
354
355
356
357
358
359
# File 'lib/sc2ai/connection/requests.rb', line 351

def query_ability_ids_for_unit(unit, ignore_resource_requirements: false)
  tag = unit.is_a?(Api::Unit) ? unit.tag : unit
  result = query_abilities_for_unit_tags([tag], ignore_resource_requirements:)
  if result.nil?
    []
  else
    result.first.abilities.map(&:ability_id)
  end
end

#query_pathings(queries) ⇒ Array<Api::ResponseQueryPathing>

Queries one or more pathing queries

Parameters:

Returns:



307
308
309
310
311
312
313
314
# File 'lib/sc2ai/connection/requests.rb', line 307

def query_pathings(queries)
  arr_queries = queries.is_a?(Array) ? queries : [queries]

  response = send_request_for query: Api::RequestQuery.new(
    pathing: arr_queries
  )
  response.pathing
end

#query_placements(queries) ⇒ Array<Api::ResponseQueryBuildingPlacement>

Queries one or more pathing queries

Parameters:

Returns:



364
365
366
367
368
369
370
# File 'lib/sc2ai/connection/requests.rb', line 364

def query_placements(queries)
  arr_queries = queries.is_a?(Array) ? queries : [queries]

  response = query(placements: arr_queries)

  response.placements
end

#quitObject

Quits Sc2. Does not work on ladder.



167
168
169
# File 'lib/sc2ai/connection/requests.rb', line 167

def quit
  send_request_for quit: Api::RequestQuit.new
end

#replay_info(replay_path: nil, replay_data: nil, download_data: false) ⇒ Api::ResponseReplayInfo

Returns metadata about a replay file. Does not load the replay. RequestReplayInfo replay_info = 16; //

Parameters:

  • replay_path (String) (defaults to: nil)

    path to replay

  • replay_data (String) (defaults to: nil)

    alternative to file, binary string of replay_file.read

  • download_data (String) (defaults to: false)

    if true, ensure the data and binary are downloaded if this is an old version replay.

Returns:

Raises:



386
387
388
389
390
391
392
393
394
# File 'lib/sc2ai/connection/requests.rb', line 386

def replay_info(replay_path: nil, replay_data: nil, download_data: false)
  raise Sc2::Error, "Missing replay." if replay_data.nil? && replay_path.nil?

  send_request_for replay_info: Api::RequestReplayInfo.new(
    replay_path: replay_path.to_s,
    replay_data: replay_data,
    download_data: download_data
  )
end

#request_quick_loadObject

Loads from an in-memory bookmark.



162
163
164
# File 'lib/sc2ai/connection/requests.rb', line 162

def request_quick_load
  send_request_for quick_load: Api::RequestQuickLoad.new
end

#request_quick_saveObject

Saves game to an in-memory bookmark.



157
158
159
# File 'lib/sc2ai/connection/requests.rb', line 157

def request_quick_save
  send_request_for quick_save: Api::RequestQuickSave.new
end

#restart_gameObject

Single player only. Reinitializes the game with the same player setup.



90
91
92
# File 'lib/sc2ai/connection/requests.rb', line 90

def restart_game
  send_request_for restart_game: Api::RequestRestartGame.new
end

#save_mapObject

Saves binary map data to the local temp directory.



403
404
405
# File 'lib/sc2ai/connection/requests.rb', line 403

def save_map
  send_request_for save_map: Api::RequestSaveMap.new
end

#save_replayObject

Generates a replay.



373
374
375
# File 'lib/sc2ai/connection/requests.rb', line 373

def save_replay
  send_request_for save_replay: Api::RequestSaveReplay.new
end

#send_request_for(**kwargs) ⇒ Object

Sends request for type and returns response that type, i.e.

send_request_for(observation: RequestObservation)

Is identical to

send_request(
  Api::Request.new(observation: RequestObservation)
)[:observation]


427
428
429
430
# File 'lib/sc2ai/connection/requests.rb', line 427

def send_request_for(**kwargs)
  response = send_request(Api::Request.new(kwargs))
  response[kwargs.keys.first.to_s]
end

#start_replay(replay_path: nil, replay_data: nil, map_data: nil, record_replay: true, interface_options: {}, **options) ⇒ Object

Given a replay file path or replay file contents, will start the replay

Examples:

Sc2.config do |config|
  config.version = "4.10"
end
Async do
  client = Sc2::ClientManager.obtain(0)
  observer = Sc2::Player::Observer.new
  observer.connect(host: client.host, port: client.port)
  pp observer.api.start_replay(
    replay_path: Pathname("./replays/test.SC2Replay").realpath
  )
  while observer.status == :in_replay
    #   Step forward
    observer.api.step(1)
    #   fresh observation info
    observation = observer.api.observation
    #   fresh game info
    game_info = observer.api.game_info
  end
ensure
  Sc2::ClientManager.stop(0)
end

Parameters:

  • replay_path (String) (defaults to: nil)

    path to replay

  • replay_data (String) (defaults to: nil)

    alternative to file, binary string of replay_file.read

  • map_data (String) (defaults to: nil)

    optional binary string of SC2 map if not present in paths

  • options (Hash)

    Api:RequestStartReplay options, such as disable_fog, observed_player_id, map_data

  • interface_options (Hash) (defaults to: {})

Raises:



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
# File 'lib/sc2ai/connection/requests.rb', line 122

def start_replay(replay_path: nil, replay_data: nil, map_data: nil, record_replay: true, interface_options: {}, **options)
  raise Sc2::Error, "Missing replay." if replay_data.nil? && replay_path.nil?

  interface_options ||= {}
  send_request_for start_replay: Api::RequestStartReplay.new(
    {
      replay_path: replay_path.to_s,
      replay_data: replay_data,
      map_data: map_data,
      realtime: false,
      disable_fog: true,
      record_replay: record_replay,
      observed_player_id: 0,
      options: Api::InterfaceOptions.new(
        {
          raw: true,
          score: true,
          feature_layer: feature_layer_interface_options(true),
          show_cloaked: true,
          show_burrowed_shadows: true,
          show_placeholders: true,
          raw_affects_selection: false,
          raw_crop_to_playable_area: true
        }.merge!(interface_options)
      )
    }.merge(options)
  )
end

#step(step_count = 1) ⇒ Object

Advances the game simulation by step_count. Not used in realtime mode. Only constant step size supported - subsequent requests use cache.



281
282
283
284
285
286
287
# File 'lib/sc2ai/connection/requests.rb', line 281

def step(step_count = 1)
  @_cached_request_step ||= {}
  @_cached_request_step[step_count] ||= Api::Request.new(
    step: Api::RequestStep.new(count: step_count)
  ).to_proto
  send_request_and_ignore(@_cached_request_step[step_count])
end