Blizzard / s2client-api

StarCraft II Client - C++ library supported on Windows, Linux and Mac designed for building scripted bots and research using the SC2API.
MIT License
1.66k stars 281 forks source link

Units of time in the api #164

Closed arnfaldur closed 7 years ago

arnfaldur commented 7 years ago

I am writing a bot using the API and need accurate timing information about most aspects of the game. I have found that someUnitTypeData.build_time is in OnStep() steps which is very convenient, however obs->GetScore().score_details.collection_rate_minerals Is much more elusive. I have empirically determined that it is approximately the amount of minerals collected in 1343 OnStep() steps which seems very arbitrary and I haven't managed to correlate that number to any sensible unit of time. That problem is not so severe as one can just implement a mineral delta which functions better than the aforementioned one. My main issue is with someUnitTypeData.movement_speed as that is a much more dynamic variable that depends on numerous factors. A similar story goes as before and when measuring the number of steps per unit of distance as returned from Distance2D(a,b), I found no correlation to the reported movement speed. The documentation says little about this and I only found this speed information on the teamliquid wiki but it doesn't help much.

AnthonyBrunasso commented 7 years ago

I created a small test by replacing bot_simple's code with the code I'll copy below. The distance does indeed correlate to steps. Note I found a bug while testing this so you'll need to fetch latest :). It didn't have to do with inconsistent distances though.

Without seeing your tests I'd guess you may be checking distance in realtime where a call to OnStep won't be necessarily consistent with a game loop. See sc2::Coordinator::SetRealtime

#include "sc2api/sc2_api.h"
#include "sc2lib/sc2_lib.h"

#include "sc2utils/sc2_manage_process.h"

#include <iostream>

class FooBot : public sc2::Agent {
public:
    uint32_t restarts_;

    FooBot() :
        restarts_(0) {
    }

    virtual void OnGameStart() final {
        sc2::DebugInterface* debug = Debug();
        debug->DebugCreateUnit(sc2::UNIT_TYPEID::TERRAN_MARINE, Observation()->GetStartLocation(), Observation()->GetPlayerID());
        debug->SendDebug();
    };

    virtual void OnStep() final {
        float distance = sc2::Distance2D(prev_position, marine->pos);
        prev_position = marine->pos;
        std::cout << "Distance traveled: " << distance << std::endl;
    };

    virtual void OnGameEnd() final {

    };

    virtual void OnUnitCreated(const sc2::Unit* unit) override {
        marine = unit;
        prev_position = unit->pos;
    };

private:
    const sc2::Unit* marine;
    sc2::Point2D prev_position;
};

int main(int argc, char* argv[]) {
    sc2::Coordinator coordinator;
    if (!coordinator.LoadSettings(argc, argv)) {
        return 1;
    }

    coordinator.SetRealtime(false);
    coordinator.SetStepSize(1);

    // Add the custom bot, it will control the players.
    FooBot bot;

    coordinator.SetParticipants({
        CreateParticipant(sc2::Race::Terran, &bot),
        CreateComputer(sc2::Race::Terran)
    });

    // Start the game.
    coordinator.LaunchStarcraft();

    // Step forward the game simulation.
    bool do_break = false;
    while (!do_break) {
        coordinator.StartGame(sc2::kMapBelShirVestigeLE);
        while (coordinator.Update() && !do_break) {
            if (sc2::PollKeyPress()) {
                do_break = true;
            }
        }
    }

    return 0;
}
arnfaldur commented 7 years ago

Thank you for the prompt response. All my tests were done using non-realtime and some with realtime as well and that didn't have any noticeable influence on the result (perhaps because I call Observation() on each step). After pulling and testing your code, the numbers seem to be internally consistent but that wasn't a problem I encountered, the problem is that I fail to see the meaning of these numbers. The best I could gather is that the reported movement_speed in the unit type data and the distance per step numbers have a ratio of slightly more than 16 that is; movement_speed/dist_per_frame ~= 16.1 This seems to imply that there are about 16 steps per second, assuming movement_speed is measured in seconds but if I recall correctly I have found other values for steps per second.

Ultimately my questions are: what is the unit of movement_speed, what is the unit of collection_rate_minerals and vespene, how often is OnStep() called per these units, how often is it called per in game second and, how does the slower/slow/normal/fast/faster settings affect the clock?

AnthonyBrunasso commented 7 years ago

You are correct in that movement is measured in distance per 16 game loops or 16 steps where distance is measured as a unit square of the building grid. Some units may need to accelerate up to their speed so there will be a few frames in which their speed is not fixed.

As far as collection_rate_minerals goes let me investigate more and get back to you.

arnfaldur commented 7 years ago

Fantastic, I figured that the acceleration could have inflated my result a bit. Although this is not a pressing issue, could it be possible to access the acceleration data of a unit and as an extension It would be trivial to implement a timeToMove(Unit u, Point2D start, Point2D end) convenience function. I could do it given the data is exposed.

AnthonyBrunasso commented 7 years ago

Yes that shouldn't be too hard to add. I can get around to it in the near future.

I'm still working to find some exact numbers. Slower/slow/normal/fast/faster are all measurements of how long a game loop (or step) takes so while the game speed may change the speed of the unit is measured in game loops so it remains consistent. The original link you gave is a bit confusing because I'm not sure if it's talking about an in game second which is different per setting.

It'd be worth documenting what I find so I'll work on getting a doc drafted for it as well as collection rate.

AnthonyBrunasso commented 7 years ago

Ok. Without getting too into the engine specific implementation of collection_rate_minerals it is roughly equivalent to how many minerals collected over an in game minute. The actual in game implementation samples collection deltas over some time and approximates what you will collect in a minute. I will attach some code below so you can run to confirm that. So the unit is minerals/minute. On "faster" the game runs at 22.4 gameloops per second. 22.4 60 = 1344 game loops so the code below prints the actual delta of minerals gathered in a minute and the collection_rate_minerals on* that minute.

#include "sc2api/sc2_api.h"
#include "sc2lib/sc2_lib.h"
#include "sc2api/sc2_score.h"

#include "sc2utils/sc2_manage_process.h"

#include <iostream>

class FooBot : public sc2::Agent {
public:
    uint32_t restarts_;

    FooBot() :
        restarts_(0) {
    }

    virtual void OnStep() final {
        if (Observation()->GetGameLoop() && Observation()->GetGameLoop() % 1344 == 0) {
            std::cout << (Observation()->GetMinerals() - previous_minerals) << "    " << Observation()->GetScore().score_details.collection_rate_minerals << std::endl;
            previous_minerals = Observation()->GetMinerals();
        }
    };

private:
    int32_t previous_minerals = 0;
};

//*************************************************************************************************
int main(int argc, char* argv[]) {
    sc2::Coordinator coordinator;
    if (!coordinator.LoadSettings(argc, argv)) {
        return 1;
    }

    coordinator.SetRealtime(false);
    coordinator.SetStepSize(1);

    // Add the custom bot, it will control the players.
    FooBot bot;

    coordinator.SetParticipants({
        CreateParticipant(sc2::Race::Terran, &bot),
        CreateComputer(sc2::Race::Terran)
    });

    // Start the game.
    coordinator.LaunchStarcraft();

    // Step forward the game simulation.
    bool do_break = false;
    while (!do_break) {
        coordinator.StartGame(sc2::kMapBelShirVestigeLE);
        while (coordinator.Update() && !do_break) {
            if (sc2::PollKeyPress()) {
                do_break = true;
            }
        }
    }

    return 0;
}
AnthonyBrunasso commented 7 years ago

I have documented this and some other score stats in the underlying protocol repo. See: https://github.com/Blizzard/s2client-proto/blob/master/s2clientprotocol/score.proto

Let me know if I can help you with anything else!