text: // Released under the MIT License. See LICENSE for details.
  
  #include "ballistica/python/methods/python_methods_gameplay.h"
  
  #include <list>
  
  #include "ballistica/app/app.h"
  #include "ballistica/dynamics/bg/bg_dynamics.h"
  #include "ballistica/dynamics/collision.h"
  #include "ballistica/dynamics/dynamics.h"
  #include "ballistica/dynamics/material/material_action.h"
  #include "ballistica/game/connection/connection_set.h"
  #include "ballistica/game/connection/connection_to_client.h"
  #include "ballistica/game/game_stream.h"
  #include "ballistica/game/host_activity.h"
  #include "ballistica/generic/json.h"
  #include "ballistica/graphics/graphics.h"
  #include "ballistica/input/device/input_device.h"
  #include "ballistica/media/component/sound.h"
  #include "ballistica/platform/platform.h"
  #include "ballistica/python/python.h"
  #include "ballistica/python/python_context_call_runnable.h"
  #include "ballistica/python/python_sys.h"
  #include "ballistica/scene/node/node.h"
  #include "ballistica/scene/node/node_type.h"
  #include "ballistica/scene/scene.h"
  
  namespace ballistica {
  
  // Ignore signed bitwise stuff; python macros do it quite a bit.
  #pragma clang diagnostic push
  #pragma ide diagnostic ignored "hicpp-signed-bitwise"
  
  auto PyNewNode(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("new_node");
    Node* n = g_python->DoNewNode(args, keywds);
    if (!n) {
      return nullptr;
    }
    return n->NewPyRef();
    BA_PYTHON_CATCH;
  }
  
  auto PyPrintNodes(PyObject* self, PyObject* args) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("print_nodes");
    HostActivity* host_activity =
        g_game->GetForegroundContext().GetHostActivity();
    if (!host_activity) {
      throw Exception(PyExcType::kContext);
    }
    Scene* scene = host_activity->scene();
    std::string s;
    int count = 1;
    for (auto&& i : scene->nodes()) {
      char buffer[128];
      snprintf(buffer, sizeof(buffer), "#%d:   type: %-14s desc: %s", count,
               i->type()->name().c_str(), i->label().c_str());
      s += buffer;
      Log(buffer);
      count++;
    }
    Py_RETURN_NONE;
    BA_PYTHON_CATCH;
  }
  
  auto PyGetNodes(PyObject* self, PyObject* args) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("get_nodes");
    HostActivity* host_activity = Context::current().GetHostActivity();
    if (!host_activity) {
      throw Exception(PyExcType::kContext);
    }
    Scene* scene = host_activity->scene();
    PyObject* py_list = PyList_New(0);
    for (auto&& i : scene->nodes()) {
      PyList_Append(py_list, i->BorrowPyRef());
    }
    return py_list;
    BA_PYTHON_CATCH;
  }
  
  static auto DoGetCollideValue(Dynamics* dynamics, const Collision* c,
                                const char* name) -> PyObject* {
    BA_PYTHON_TRY;
    if (!strcmp(name, "depth")) {
      return Py_BuildValue("f", c->depth);
    } else if (!strcmp(name, "position")) {
      return Py_BuildValue("(fff)", c->x, c->y, c->z);
    } else if (!strcmp(name, "sourcenode")) {
      if (!dynamics->in_collide_message()) {
        PyErr_SetString(
            PyExc_AttributeError,
            "collide value 'sourcenode' is only valid while processing "
            "collide messages");
        return nullptr;
      }
      Node* n = dynamics->GetActiveCollideSrcNode();
      if (n) {
        return n->NewPyRef();
      } else {
        Py_RETURN_NONE;
      }
    } else if (!strcmp(name, "opposingnode")) {
      if (!dynamics->in_collide_message()) {
        PyErr_SetString(
            PyExc_AttributeError,
            "collide value 'opposingnode' is only valid while processing "
            "collide messages");
        return nullptr;
      }
      Node* n = dynamics->GetActiveCollideDstNode();
      if (n) {
        return n->NewPyRef();
      } else {
        Py_RETURN_NONE;
      }
    } else if (!strcmp(name, "opposingbody")) {
      return Py_BuildValue("i", dynamics->GetCollideMessageReverseOrder()
                                    ? c->body_id_2
                                    : c->body_id_1);
    } else {
      PyErr_SetString(
          PyExc_AttributeError,
          (std::string("\"") + name + "\" is not a valid collide value name")
              .c_str());
      return nullptr;
    }
    BA_PYTHON_CATCH;
  }
  
  auto PyGetCollisionInfo(PyObject* self, PyObject* args) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("get_collision_info");
    HostActivity* host_activity = Context::current().GetHostActivity();
    if (!host_activity) {
      throw Exception(PyExcType::kContext);
    }
    Dynamics* dynamics = host_activity->scene()->dynamics();
    assert(dynamics);
    PyObject* obj = nullptr;
  
    // Take arg list as individual items or possibly a single tuple
    Py_ssize_t argc = PyTuple_GET_SIZE(args);
    if (argc > 1) {
      obj = args;
    } else if (argc == 1) {
      obj = PyTuple_GET_ITEM(args, 0);
    }
    Collision* c = dynamics->active_collision();
    if (!c) {
      PyErr_SetString(PyExc_RuntimeError,
                      "This must be called from a collision callback.");
      return nullptr;
    }
    if (PyUnicode_Check(obj)) {
      return DoGetCollideValue(dynamics, c, PyUnicode_AsUTF8(obj));
    } else if (PyTuple_Check(obj)) {
      Py_ssize_t size = PyTuple_GET_SIZE(obj);
      PyObject* return_tuple = PyTuple_New(size);
      for (Py_ssize_t i = 0; i < size; i++) {
        PyObject* o = PyTuple_GET_ITEM(obj, i);
        if (PyUnicode_Check(o)) {
          PyObject* val_obj = DoGetCollideValue(dynamics, c, PyUnicode_AsUTF8(o));
          if (val_obj) {
            PyTuple_SetItem(return_tuple, i, val_obj);
          } else {
            Py_DECREF(return_tuple);
            return nullptr;
          }
        } else {
          Py_DECREF(return_tuple);
          PyErr_SetString(PyExc_TypeError, "Expected a string as tuple member.");
          return nullptr;
        }
      }
      return return_tuple;
    } else {
      PyErr_SetString(PyExc_TypeError, "Expected a string or tuple.");
      return nullptr;
    }
    BA_PYTHON_CATCH;
  }
  
  auto PyCameraShake(PyObject* self, PyObject* args, PyObject* keywds)
      -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("camera_shake");
    assert(InGameThread());
    float intensity = 1.0f;
    static const char* kwlist[] = {"intensity", nullptr};
    if (!PyArg_ParseTupleAndKeywords(args, keywds, "|f",
                                     const_cast<char**>(kwlist), &intensity)) {
      return nullptr;
    }
    g_graphics->LocalCameraShake(intensity);
    Py_RETURN_NONE;
    BA_PYTHON_CATCH;
  }
  
  auto PyPlaySound(PyObject* self, PyObject* args, PyObject* keywds)
      -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("play_sound");
  
    assert(InGameThread());
    PyObject* sound_obj;
    float volume = 1.0f;
    int host_only = 0;
    PyObject* pos_obj = Py_None;
    static const char* kwlist[] = {"sound", "volume", "position", "host_only",
                                   nullptr};
    if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|fOp",
                                     const_cast<char**>(kwlist), &sound_obj,
                                     &volume, &pos_obj, &host_only)) {
      return nullptr;
    }
  
    Sound* sound = Python::GetPySound(sound_obj);
  
    // Can play sounds in a host scene context.
    if (Scene* scene = Context::current().GetMutableScene()) {
      if (sound->scene() != scene) {
        throw Exception("Sound was not loaded in this context.",
                        PyExcType::kContext);
      }
      if (pos_obj != Py_None) {
        std::vector<float> vals = Python::GetPyFloats(pos_obj);
        if (vals.size() != 3) {
          throw Exception("Expected 3 floats for pos (got "
                              + std::to_string(vals.size()) + ")",
                          PyExcType::kValue);
        }
        scene->PlaySoundAtPosition(sound, volume, vals[0], vals[1], vals[2],
                                   static_cast<bool>(host_only));
      } else {
        scene->PlaySound(sound, volume, static_cast<bool>(host_only));
      }
    } else {
      throw Exception("Can't play sounds in this context.", PyExcType::kContext);
    }
  
    Py_RETURN_NONE;
  
    BA_PYTHON_CATCH;
  }
  
  auto PyEmitFx(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("emit_fx");
    static const char* kwlist[] = {"position",  "velocity",     "count",
                                   "scale",     "spread",       "chunk_type",
                                   "emit_type", "tendril_type", nullptr};
    PyObject* pos_obj = Py_None;
    PyObject* vel_obj = Py_None;
    int count = 10;
    float scale = 1.0f;
    float spread = 1.0f;
    const char* chunk_type_str = "rock";
    const char* emit_type_str = "chunks";
    const char* tendril_type_str = "smoke";
    assert(InGameThread());
    if (!PyArg_ParseTupleAndKeywords(
            args, keywds, "O|Oiffsss", const_cast<char**>(kwlist), &pos_obj,
            &vel_obj, &count, &scale, &spread, &chunk_type_str, &emit_type_str,
            &tendril_type_str)) {
      return nullptr;
    }
    float x, y, z;
    assert(pos_obj);
    {
      std::vector<float> vals = Python::GetPyFloats(pos_obj);
      if (vals.size() != 3) {
        throw Exception("Expected 3 floats for position.", PyExcType::kValue);
      }
      x = vals[0];
      y = vals[1];
      z = vals[2];
    }
    float vx = 0.0f;
    float vy = 0.0f;
    float vz = 0.0f;
    if (vel_obj != Py_None) {
      std::vector<float> vals = Python::GetPyFloats(vel_obj);
      if (vals.size() != 3) {
        throw Exception("Expected 3 floats for velocity.", PyExcType::kValue);
      }
      vx = vals[0];
      vy = vals[1];
      vz = vals[2];
    }
    BGDynamicsChunkType chunk_type;
    if (!strcmp(chunk_type_str, "rock")) {
      chunk_type = BGDynamicsChunkType::kRock;
    } else if (!strcmp(chunk_type_str, "ice")) {
      chunk_type = BGDynamicsChunkType::kIce;
    } else if (!strcmp(chunk_type_str, "slime")) {
      chunk_type = BGDynamicsChunkType::kSlime;
    } else if (!strcmp(chunk_type_str, "metal")) {
      chunk_type = BGDynamicsChunkType::kMetal;
    } else if (!strcmp(chunk_type_str, "spark")) {
      chunk_type = BGDynamicsChunkType::kSpark;
    } else if (!strcmp(chunk_type_str, "splinter")) {
      chunk_type = BGDynamicsChunkType::kSplinter;
    } else if (!strcmp(chunk_type_str, "sweat")) {
      chunk_type = BGDynamicsChunkType::kSweat;
    } else {
      throw Exception(
          "Invalid chunk type: '" + std::string(chunk_type_str) + "'.",
          PyExcType::kValue);
    }
    BGDynamicsTendrilType tendril_type;
    if (!strcmp(tendril_type_str, "smoke")) {
      tendril_type = BGDynamicsTendrilType::kSmoke;
    } else if (!strcmp(tendril_type_str, "thin_smoke")) {
      tendril_type = BGDynamicsTendrilType::kThinSmoke;
    } else if (!strcmp(tendril_type_str, "ice")) {
      tendril_type = BGDynamicsTendrilType::kIce;
    } else {
      throw Exception(
          "Invalid tendril type: '" + std::string(tendril_type_str) + "'.",
          PyExcType::kValue);
    }
    BGDynamicsEmitType emit_type;
    if (!strcmp(emit_type_str, "chunks")) {
      emit_type = BGDynamicsEmitType::kChunks;
    } else if (!strcmp(emit_type_str, "stickers")) {
      emit_type = BGDynamicsEmitType::kStickers;
    } else if (!strcmp(emit_type_str, "tendrils")) {
      emit_type = BGDynamicsEmitType::kTendrils;
    } else if (!strcmp(emit_type_str, "distortion")) {
      emit_type = BGDynamicsEmitType::kDistortion;
    } else if (!strcmp(emit_type_str, "flag_stand")) {
      emit_type = BGDynamicsEmitType::kFlagStand;
    } else {
      throw Exception("Invalid emit type: '" + std::string(emit_type_str) + "'.",
                      PyExcType::kValue);
    }
    if (Scene* scene = Context::current().GetMutableScene()) {
      BGDynamicsEmission e;
      e.emit_type = emit_type;
      e.position = Vector3f(x, y, z);
      e.velocity = Vector3f(vx, vy, vz);
      e.count = count;
      e.scale = scale;
      e.spread = spread;
      e.chunk_type = chunk_type;
      e.tendril_type = tendril_type;
      if (GameStream* output_stream = scene->GetGameStream()) {
        output_stream->EmitBGDynamics(e);
      }
  #if !BA_HEADLESS_BUILD
      g_bg_dynamics->Emit(e);
  #endif  // !BA_HEADLESS_BUILD
    } else {
      throw Exception("Can't emit bg dynamics in this context.",
                      PyExcType::kContext);
    }
    Py_RETURN_NONE;
    BA_PYTHON_CATCH;
  }
  
  auto PySetMapBounds(PyObject* self, PyObject* args) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("set_map_bounds");
    HostActivity* host_activity = Context::current().GetHostActivity();
    if (!host_activity) {
      throw Exception(PyExcType::kContext);
    }
    float xmin, ymin, zmin, xmax, ymax, zmax;
    assert(InGameThread());
    if (!PyArg_ParseTuple(args, "(ffffff)", &xmin, &ymin, &zmin, &xmax, &ymax,
                          &zmax)) {
      return nullptr;
    }
    host_activity->scene()->SetMapBounds(xmin, ymin, zmin, xmax, ymax, zmax);
    Py_RETURN_NONE;
    BA_PYTHON_CATCH;
  }
  
  auto PyGetForegroundHostActivity(PyObject* self, PyObject* args,
                                   PyObject* keywds) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("get_foreground_host_activity");
    static const char* kwlist[] = {nullptr};
    if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
                                     const_cast<char**>(kwlist))) {
      return nullptr;
    }
  
    // Note: we return None if not in the game thread.
    HostActivity* h = InGameThread()
                          ? g_game->GetForegroundContext().GetHostActivity()
                          : nullptr;
    if (h != nullptr) {
      PyObject* obj = h->GetPyActivity();
      Py_INCREF(obj);
      return obj;
    }
    Py_RETURN_NONE;
    BA_PYTHON_CATCH;
  }
  
  auto PyGetGameRoster(PyObject* self, PyObject* args, PyObject* keywds)
      -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("get_game_roster");
    BA_PRECONDITION(InGameThread());
    static const char* kwlist[] = {nullptr};
    if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
                                     const_cast<char**>(kwlist))) {
      return nullptr;
    }
    PythonRef py_client_list(PyList_New(0), PythonRef::kSteal);
    cJSON* party = g_game->game_roster();
    assert(party);
    int len = cJSON_GetArraySize(party);
    for (int i = 0; i < len; i++) {
      cJSON* client = cJSON_GetArrayItem(party, i);
      assert(client);
      cJSON* spec = cJSON_GetObjectItem(client, "spec");
      cJSON* players = cJSON_GetObjectItem(client, "p");
      PythonRef py_player_list(PyList_New(0), PythonRef::kSteal);
      if (players != nullptr) {
        int plen = cJSON_GetArraySize(players);
        for (int j = 0; j < plen; ++j) {
          cJSON* player = cJSON_GetArrayItem(players, j);
          if (player != nullptr) {
            cJSON* name = cJSON_GetObjectItem(player, "n");
            cJSON* py_name_full = cJSON_GetObjectItem(player, "nf");
            cJSON* id_obj = cJSON_GetObjectItem(player, "i");
            int id_val = id_obj ? id_obj->valueint : -1;
            if (name != nullptr && name->valuestring != nullptr
                && py_name_full != nullptr && py_name_full->valuestring != nullptr
                && id_val != -1) {
              PythonRef py_player(
                  Py_BuildValue(
                      "{sssssi}", "name",
                      Utils::GetValidUTF8(name->valuestring, "ggr1").c_str(),
                      "name_full",
                      Utils::GetValidUTF8(py_name_full->valuestring, "ggr2")
                          .c_str(),
                      "id", id_val),
                  PythonRef::kSteal);
              // This increments ref.
              PyList_Append(py_player_list.get(), py_player.get());
            }
          }
        }
      }
  
      // If there's a client_id with this data, include it; otherwise pass None.
      cJSON* client_id = cJSON_GetObjectItem(client, "i");
      int clientid{};
      PythonRef client_id_ref;
      if (client_id != nullptr) {
        clientid = client_id->valueint;
        client_id_ref.Steal(PyLong_FromLong(clientid));
      } else {
        client_id_ref.Acquire(Py_None);
      }
  
      // Let's also include a public account-id if we have one.
      std::string account_id;
      if (clientid == -1) {
        account_id = AppInternalGetPublicAccountID();
      } else {
        auto client2 =
            g_game->connections()->connections_to_clients().find(clientid);
        if (client2 != g_game->connections()->connections_to_clients().end()) {
          account_id = client2->second->peer_public_account_id();
        }
      }
      PythonRef account_id_ref;
      if (account_id.empty()) {
        account_id_ref.Acquire(Py_None);
      } else {
        account_id_ref.Steal(PyUnicode_FromString(account_id.c_str()));
      }
  
      // Py_BuildValue steals a ref; gotta increment ourself (edit: NO IT DOESNT)
      // Py_INCREF(py_player_list.get());
      PythonRef py_client(
          Py_BuildValue(
              "{sssssOsOsO}", "display_string",
              (spec && spec->valuestring)
                  ? PlayerSpec(spec->valuestring).GetDisplayString().c_str()
                  : "",
              "spec_string", (spec && spec->valuestring) ? spec->valuestring : "",
              "players", py_player_list.get(), "client_id", client_id_ref.get(),
              "account_id", account_id_ref.get()),
          PythonRef::kSteal);
      PyList_Append(py_client_list.get(),
                    py_client.get());  // this increments ref
    }
    return py_client_list.NewRef();
    BA_PYTHON_CATCH;
  }
  
  auto PyGetScoresToBeat(PyObject* self, PyObject* args, PyObject* keywds)
      -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("get_scores_to_beat");
    const char* level;
    const char* config;
    PyObject* callback_obj = Py_None;
    static const char* kwlist[] = {"level", "config", "callback", nullptr};
    if (!PyArg_ParseTupleAndKeywords(args, keywds, "ssO",
                                     const_cast<char**>(kwlist), &level, &config,
                                     &callback_obj)) {
      return nullptr;
    }
  
    // Allocate a Call object for this and pass its pointer to the main thread;
    // we'll ref/de-ref it when it comes back.
    auto* call = Object::NewDeferred<PythonContextCall>(callback_obj);
    g_app->PushGetScoresToBeatCall(level, config, call);
    Py_RETURN_NONE;
    BA_PYTHON_CATCH;
  }
  
  auto PySetDebugSpeedExponent(PyObject* self, PyObject* args) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("set_debug_speed_exponent");
    int speed;
    if (!PyArg_ParseTuple(args, "i", &speed)) {
      return nullptr;
    }
    HostActivity* host_activity = Context::current().GetHostActivity();
    if (!host_activity) {
      throw Exception(PyExcType::kContext);
    }
  #if BA_DEBUG_BUILD
    g_game->SetDebugSpeedExponent(speed);
  #else
    throw Exception("This call only functions in the debug build.");
  #endif
    Py_RETURN_NONE;
    BA_PYTHON_CATCH;
  }
  
  auto PyGetReplaySpeedExponent(PyObject* self, PyObject* args) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("get_replay_speed_exponent");
    assert(g_game);
    return PyLong_FromLong(g_game->replay_speed_exponent());
    BA_PYTHON_CATCH;
  }
  
  auto PySetReplaySpeedExponent(PyObject* self, PyObject* args) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("set_replay_speed_exponent");
    int speed;
    if (!PyArg_ParseTuple(args, "i", &speed)) return nullptr;
    assert(g_game);
    g_game->SetReplaySpeedExponent(speed);
    Py_RETURN_NONE;
    BA_PYTHON_CATCH;
  }
  
  auto PyResetGameActivityTracking(PyObject* self, PyObject* args,
                                   PyObject* keywds) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("reset_game_activity_tracking");
    static const char* kwlist[] = {nullptr};
    if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
                                     const_cast<char**>(kwlist))) {
      return nullptr;
    }
    if (g_game) {
      g_game->ResetActivityTracking();
    }
    Py_RETURN_NONE;
    BA_PYTHON_CATCH;
  }
  
  auto PyResetRandomPlayerNames(PyObject* self, PyObject* args, PyObject* keywds)
      -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("reset_random_player_names");
    InputDevice::ResetRandomNames();
    Py_RETURN_NONE;
    BA_PYTHON_CATCH;
  }
  
  auto PyGetRandomNames(PyObject* self, PyObject* args) -> PyObject* {
    BA_PYTHON_TRY;
    Platform::SetLastPyCall("get_random_names");
    PyObject* list = PyList_New(0);
    const std::list<std::string>& random_name_list = Utils::GetRandomNameList();
    for (const auto& i : random_name_list) {
      assert(Utils::IsValidUTF8(i));
      PyObject* obj = PyUnicode_FromString(i.c_str());
      PyList_Append(list, obj);
      Py_DECREF(obj);
    }
    return list;
    BA_PYTHON_CATCH;
  }
  
  auto PythonMethodsGameplay::GetMethods() -> std::vector<PyMethodDef> {
    return {
        {"get_random_names", PyGetRandomNames, METH_VARARGS,
         "get_random_names() -> list\n"
         "\n"
         "(internal)\n"
         "\n"
         "Returns the random names used by the game."},
  
        {"reset_random_player_names", (PyCFunction)PyResetRandomPlayerNames,
         METH_VARARGS | METH_KEYWORDS,
         "reset_random_player_names() -> None\n"
         "\n"
         "(internal)"},
  
        {"reset_game_activity_tracking", (PyCFunction)PyResetGameActivityTracking,
         METH_VARARGS | METH_KEYWORDS,
         "reset_game_activity_tracking() -> None\n"
         "\n"
         "(internal)"},
  
        {"set_replay_speed_exponent", PySetReplaySpeedExponent, METH_VARARGS,
         "set_replay_speed_exponent(speed: int) -> None\n"
         "\n"
         "(internal)\n"
         "\n"
         "Set replay speed. Actual displayed speed is pow(2, speed)."},
  
        {"get_replay_speed_exponent", PyGetReplaySpeedExponent, METH_VARARGS,
         "get_replay_speed_exponent() -> int\n"
         "\n"
         "(internal)\n"
         "\n"
         "Returns current replay speed value. Actual displayed speed is "
         "pow(2,speed)."},
  
        {"set_debug_speed_exponent", PySetDebugSpeedExponent, METH_VARARGS,
         "set_debug_speed_exponent(speed: int) -> None\n"
         "\n"
         "(internal)\n"
         "\n"
         "Sets the debug speed scale for the game. Actual speed is "
         "pow(2,speed)."},
  
        {"get_scores_to_beat", (PyCFunction)PyGetScoresToBeat,
         METH_VARARGS | METH_KEYWORDS,
         "get_scores_to_beat(level: str, config: str, callback: Callable) -> "
         "None\n"
         "\n"
         "(internal)"},
  
        {"get_game_roster", (PyCFunction)PyGetGameRoster,
         METH_VARARGS | METH_KEYWORDS,
         "get_game_roster() -> List[Dict[str, Any]]\n"
         "\n"
         "(internal)"},
  
        {"get_foreground_host_activity", (PyCFunction)PyGetForegroundHostActivity,
         METH_VARARGS | METH_KEYWORDS,
         "get_foreground_host_activity() -> Optional[ba.Activity]\n"
         "\n"
         "(internal)\n"
         "\n"
         "Returns the ba.Activity currently in the foreground, or None if there\n"
         "is none.\n"},
  
        {"set_map_bounds", (PyCFunction)PySetMapBounds,
         METH_VARARGS | METH_KEYWORDS,
         "set_map_bounds(bounds: Tuple[float, float, float, float, float, "
         "float])\n"
         "  -> None\n"
         "\n"
         "(internal)\n"
         "\n"
         "Set map bounds. Generally nodes that go outside of this box are "
         "killed."},
  
        {"emitfx", (PyCFunction)PyEmitFx, METH_VARARGS | METH_KEYWORDS,
         "emitfx(position: Sequence[float],\n"
         "  velocity: Optional[Sequence[float]] = None,\n"
         "  count: int = 10, scale: float = 1.0, spread: float = 1.0,\n"
         "  chunk_type: str = 'rock', emit_type: str ='chunks',\n"
         "  tendril_type: str = 'smoke') -> None\n"
         "\n"
         "Emit particles, smoke, etc. into the fx sim layer.\n"
         "\n"
         "Category: Gameplay Functions\n"
         "\n"
         "The fx sim layer is a secondary dynamics simulation that runs in\n"
         "the background and just looks pretty; it does not affect gameplay.\n"
         "Note that the actual amount emitted may vary depending on graphics\n"
         "settings, exiting element counts, or other factors."},
  
        {"playsound", (PyCFunction)PyPlaySound, METH_VARARGS | METH_KEYWORDS,
         "playsound(sound: Sound, volume: float = 1.0,\n"
         "  position: Sequence[float] = None, host_only: bool = False) -> None\n"
         "\n"
         "Play a ba.Sound a single time.\n"
         "\n"
         "Category: Gameplay Functions\n"
         "\n"
         "If position is not provided, the sound will be at a constant volume\n"
         "everywhere.  Position should be a float tuple of size 3."},
  
        {"camerashake", (PyCFunction)PyCameraShake, METH_VARARGS | METH_KEYWORDS,
         "camerashake(intensity: float = 1.0) -> None\n"
         "\n"
         "Shake the camera.\n"
         "\n"
         "Category: Gameplay Functions\n"
         "\n"
         "Note that some cameras and/or platforms (such as VR) may not display\n"
         "camera-shake, so do not rely on this always being visible to the\n"
         "player as a gameplay cue."},
  
        {"get_collision_info", PyGetCollisionInfo, METH_VARARGS,
         "get_collision_info(*args: Any) -> Any\n"
         "\n"
         "Return collision related values\n"
         "\n"
         "Category: Gameplay Functions\n"
         "\n"
         "Returns a single collision value or tuple of values such as location,\n"
         "depth, nodes involved, etc. Only call this in the handler of a\n"
         "collision-triggered callback or message"},
  
        {"getnodes", PyGetNodes, METH_VARARGS,
         "getnodes() -> list\n"
         "\n"
         "Return all nodes in the current ba.Context."
         "\n"
         "Category: Gameplay Functions"},
  
        {"printnodes", PyPrintNodes, METH_VARARGS,
         "printnodes() -> None\n"
         "\n"
         "Print various info about existing nodes; useful for debugging.\n"
         "\n"
         "Category: Gameplay Functions"},
  
        {"newnode", (PyCFunction)PyNewNode, METH_VARARGS | METH_KEYWORDS,
         "newnode(type: str, owner: ba.Node = None,\n"
         "attrs: dict = None, name: str = None, delegate: Any = None)\n"
         " -> Node\n"
         "\n"
         "Add a node of the given type to the game.\n"
         "\n"
         "Category: Gameplay Functions\n"
         "\n"
         "If a dict is provided for 'attributes', the node's initial attributes\n"
         "will be set based on them.\n"
         "\n"
         "'name', if provided, will be stored with the node purely for "
         "debugging\n"
         "purposes. If no name is provided, an automatic one will be generated\n"
         "such as 'terrain@foo.py:30'.\n"
         "\n"
         "If 'delegate' is provided, Python messages sent to the node will go "
         "to\n"
         "that object's handlemessage() method. Note that the delegate is "
         "stored\n"
         "as a weak-ref, so the node itself will not keep the object alive.\n"
         "\n"
         "if 'owner' is provided, the node will be automatically killed when "
         "that\n"
         "object dies. 'owner' can be another node or a ba.Actor"},
    };
  }
  
  #pragma clang diagnostic pop
  
  }  // namespace ballistica
  