From 9d88f2859528f19f180978c375e1a5891eb7fb10 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Thu, 27 Feb 2025 03:56:03 -0500 Subject: [PATCH] Finish API Doc --- docs/API.md | 125 ++++++++++++++++++++++++------ mods/src/api/api.cpp | 166 ++++++++++++++++++++-------------------- mods/src/api/events.cpp | 6 +- mods/src/api/internal.h | 2 +- 4 files changed, 185 insertions(+), 114 deletions(-) diff --git a/docs/API.md b/docs/API.md index cab74103..e1000977 100644 --- a/docs/API.md +++ b/docs/API.md @@ -14,16 +14,16 @@ It is typically hosted on port `4711`, but this can be configured. By default, MCPI-Reborn runs in a "compatibility mode." This makes it completely compatible with RaspberryJuice, but limits functionality. * **Bold** text only applies to the compatibility mode. -* Underlined text only applies when it is disabled. -* Text enclosed in curly braces (`{}`) is meant to be [Base64-URL](https://base64.guru/standards/base64url) encoded when the compatibility mode is disabled. -* In compatibility mode, entity type IDs are automatically translated to/from their MC Java equivalents. +* Underlined text only applies when it is disabled. +* Text enclosed in curly braces (for instance `{text}`) is meant to be [Base64-URL](https://base64.guru/standards/base64url)-encoded when the compatibility mode is disabled. +* In compatibility mode, entity type IDs are automatically translated to/from their [MC Java equivalents](https://mcreator.net/wiki/entity-ids#toc-index-2). ## Commands * Commands are formatted like `()` and may return a response. The response `Fail` indicates an error. * All commands are responses end with a newline. * Arguments surrounded by square brackets (for instance `[abc]`) are optional. * Numbers surrounded by colons (for instance `:a:`) are floating-point, all other numbers are integers. -* Lists are delimited by `|`. For instance: `A|B|C`. +* Lists are delimited by pipes (`|`). For instance: `A|B|C`. * Unless otherwise noted, all `player.*(...)` commands are equivalent to `entity.*(local_player_id,...)`. ### Vanilla @@ -40,26 +40,6 @@ By default, MCPI-Reborn runs in a "compatibility mode." This makes it completely * `world.getHeight(x,z)` * Description: Get the last (from the top-down) non-solid block's Y-coordinate at the given location. * Output: `max_y` -* `entity.setTile(entity_id,x,y,z)` - * Description: Move the specified entity to the given position. -* `entity.setPos(entity_id,:x:,:y:,:z:)` - * Description: Same as above. -* `entity.getTile(entity_id)` - * Description: Retrieve the given entity's position. - * Output: `x,y,z` -* `entity.getPos(entity_id)` - * Description: Same as above. - * Output: `:x:,:y:,:z:` -* `chat.post(message)` - * Description: Post the specified message to chat. -* `camera.mode.setFixed()` - * Description: Set the camera to fixed-position mode. -* `camera.mode.setNormal([entity_id])` - * Description: Set the camera to normal mode. The camera will be the specified entity (or the local player if none is provided). -* `camera.mode.setFollow([entity_id])` - * Description: Set the camera to follow mode. The camera will follow the specified entity (or the local player if none is provided). -* `camera.setPos(:x:,:y:,:z:)` - * Description: Move the camera to the given position. The XZ-coordinates are automatically offset by `0.5`. * `world.getPlayerIds()` * Description: Retrieve the entity IDs of all players. * Output: List of `entity_id` @@ -72,6 +52,26 @@ By default, MCPI-Reborn runs in a "compatibility mode." This makes it completely * `autojump` * `nametags_visible` * `world_immutable` +* `entity.setTile(entity_id,x,y,z)` + * Description: Move the specified entity to the given position. +* `entity.setPos(entity_id,:x:,:y:,:z:)` + * Description: See above. +* `entity.getTile(entity_id)` + * Description: Retrieve the given entity's position. + * Output: `x,y,z` +* `entity.getPos(entity_id)` + * Description: See above. + * Output: `:x:,:y:,:z:` +* `chat.post(message)` + * Description: Post the specified message to chat. +* `camera.mode.setFixed()` + * Description: Set the camera to fixed-position mode. +* `camera.mode.setNormal([entity_id])` + * Description: Set the camera to normal mode. The camera will be the specified entity (or the local player if none is provided). +* `camera.mode.setFollow([entity_id])` + * Description: Set the camera to follow mode. The camera will follow the specified entity (or the local player if none is provided). +* `camera.setPos(:x:,:y:,:z:)` + * Description: Move the camera to the given position. The XZ-coordinates are automatically offset by `0.5`. * `events.clear()` * Description: Clear all queued events. * Note: On RaspberryJuice, this *does not* clear projectile events. This behavior is maintained only in compatibility mode. @@ -80,6 +80,78 @@ By default, MCPI-Reborn runs in a "compatibility mode." This makes it completely * Output: List of `x,y,z,face,entity_id` ### RaspberryJuice +* `world.getBlocks(x0,y0,z0,x1,y1,x1)` + * Description: Retrieve the blocks in the specified region. + * Output: List of block_id,data + * In compatibility mode, this list is delimited with commas (`,`). +* `world.getPlayerId({username})` + * Description: Retrieve the entity ID of the specified player. + * Output: `entity_id` +* `world.getEntities(entity_type_id)` + * Description: Retrieve all entities of the specified type[^1]. + * Output: List entity_id,entity_type_id,entity_type_name,:x:,:y:,:z: +* `entity.getEntities(entity_id,distance,entity_type_id)` + * Description: Retrieve all entities of the specified type[^1] within the given distance of the provided entity. + * Output: See above. +* `world.removeEntity(entity_id)` + * Description: Remove the specified entity. + * Output: `number_of_entities_removed` +* `world.removeEntities(entity_type_id)` + * Description: Remove all entities of the specified type[^1]. + * Output: See above. +* `entity.removeEntities(entity_id,distance,entity_type_id)` + * Description: Remove all entities of the specified type[^1] within the given distance of the provided entity. + * Output: See above. +* `world.spawnEntity(x,y,x,entity_type_id)` + * Description: Spawn the specified entity at the given position. + * Output: `entity_id` +* `world.getEntityTypes()` + * Description: Retrieve all supported entity types. + * Output: List of `entity_type_id,entity_type_name` +* `world.setSign(x,y,z,id,data[,{line_1}][,{line_2}][,{line_3}][,{line_4}])` + * Description: Set the specified block at the given location. If the block is a sign, then also set its contents. +* `entity.getName(entity_id)` + * Description: Retrieve the name of the specified entity. + * Output: `{entity_name}` +* `entity.setDirection(entity_id,:x:,:y:,:z:)` + * Description: Set the specified entity's rotation using a unit vector. +* `entity.getDirection(entity_id)` + * Description: Retrieve the specified entity's rotation as a unit vector. + * Output: `:x:,:y:,:z:` +* `entity.setRotation(entity_id,:yaw:)` + * Description: Set the specified entity's yaw. +* `entity.getRotation(entity_id)` + * Description: Retrieve the specified entity's yaw. + * Output: `:yaw:` +* `entity.setPitch(entity_id,:pitch:)` + * Description: Set the specified entity's pitch. +* `entity.getPitch(entity_id)` + * Description: Retrieve the specified entity's pitch. + * Output: `:pitch:` +* `entity.setAbsPos(entity_id,:x:,:y:,:z:)` + * Description: Move the specified entity to the given absolute[^2] position. +* `entity.getAbsPos(entity_id)` + * Description: Retrieve the given entity's absolute[^2] position. + * Output: `:x:,:y:,:z:` +* `entity.events.block.hits(entity_id)` + * Description: Retrieve all queued block hit events produced by the specified entity. + * Output: See `events.block.hits`. +* `events.chat.posts()` + * Description: Retrieve all queued chat events. + * Output: List of `entity_id,{message}` + * When compatibility mode is disabled, this will also include non-chat messages denoted with an entity ID of `-1`. +* `entity.events.chat.posts(entity_id)` + * Description: Retrieve all queued chat events produced by the specified entity. + * Output: See above. +* `events.projectile.hits()` + * Description: Retrieve all queued projectile hit events. + * Output: List of x,y,z,1,shooter_entity_name,\[target_entity_name\],shooter_entity_id,target_entity_id + * If the projectile hit a block, `target_entity_id` will be `-1` and `target_entity_name` will be blank. +* `entity.events.projectile.hits(entity_id)` + * Description: Retrieve all queued projectile hit events produced by the specified entity. + * Output: See above. +* `entity.events.clear(entity_id)` + * Description: Clear all queued events produced by the specified entity. ### Reborn-Specific * `reborn.disableCompatMode()` @@ -92,4 +164,7 @@ By default, MCPI-Reborn runs in a "compatibility mode." This makes it completely * Output: List of `{line}` * `entity.getType(entity_id)` * Description: Check the type of the given entity. - * Output: `entity_type` \ No newline at end of file + * Output: `entity_type_id` + +[^1]: If the ID is `-1`, it will match all entities. +[^2]: The API normally applies an offset to all coordinates, these commands use the raw data. \ No newline at end of file diff --git a/mods/src/api/api.cpp b/mods/src/api/api.cpp index 335727cf..814156e8 100644 --- a/mods/src/api/api.cpp +++ b/mods/src/api/api.cpp @@ -199,7 +199,11 @@ static std::unordered_map modern_entity_id_mapping = { {7, EntityType::THROWN_EGG}, {9, EntityType::PAINTING} }; -void api_convert_to_rj_entity_type(int &type) { +void api_convert_to_outside_entity_type(int &type) { + if (!api_compat_mode) { + return; + } + // Convert To RJ-Compatible Entity Type for (const std::pair &pair : modern_entity_id_mapping) { if (static_cast(pair.second) == type) { type = pair.first; @@ -207,6 +211,10 @@ void api_convert_to_rj_entity_type(int &type) { } } void api_convert_to_mcpi_entity_type(int &type) { + if (!api_compat_mode) { + return; + } + // Convert To Native Entity Type if (modern_entity_id_mapping.contains(type)) { type = static_cast(modern_entity_id_mapping[type]); } @@ -219,9 +227,7 @@ static std::string get_entity_message(CommandServer *server, Entity *entity) { pieces.push_back(std::to_string(entity->id)); // Type int type = entity->getEntityTypeId(); - if (api_compat_mode) { - api_convert_to_rj_entity_type(type); - } + api_convert_to_outside_entity_type(type); pieces.push_back(std::to_string(type)); if (api_compat_mode) { pieces.push_back(api_get_output(misc_get_entity_type_name(entity).second, true)); @@ -345,17 +351,13 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ return CommandServer::NullString; } else { int type = entity->getEntityTypeId(); - if (api_compat_mode) { - api_convert_to_rj_entity_type(type); - } + api_convert_to_outside_entity_type(type); return std::to_string(type) + '\n'; } } else if (cmd == "world.getEntities") { // Parse next_int(type); - if (api_compat_mode) { - api_convert_to_mcpi_entity_type(type); - } + api_convert_to_mcpi_entity_type(type); // Search std::vector result; for (Entity *entity : server->minecraft->level->entities) { @@ -378,9 +380,7 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ } else if (cmd == "world.removeEntities") { // Parse next_int(type); - if (api_compat_mode) { - api_convert_to_mcpi_entity_type(type); - } + api_convert_to_mcpi_entity_type(type); // Remove int removed = 0; for (Entity *entity : server->minecraft->level->entities) { @@ -390,6 +390,67 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ } } return std::to_string(removed) + '\n'; + } else if (cmd == "entity.getEntities") { + // Parse + next_int(id); + next_int(dist); + next_int(type); + Entity *src = server->minecraft->level->getEntity(id); + if (src == nullptr) { + return CommandServer::Fail; + } + // Run + std::vector result; + for (Entity *entity : server->minecraft->level->entities) { + if (is_entity_selected(entity, type) && distance_between(src, entity) < dist) { + result.push_back(get_entity_message(server, entity)); + } + } + return api_join_outputs(result, list_separator); + } else if (cmd == "entity.removeEntities") { + // Parse + next_int(id); + next_int(dist); + next_int(type); + Entity *src = server->minecraft->level->getEntity(id); + if (src == nullptr) { + return CommandServer::Fail; + } + // Run + int removed = 0; + for (Entity *entity : server->minecraft->level->entities) { + if (is_entity_selected(entity, type) && distance_between(src, entity) < dist) { + entity->remove(); + removed++; + } + } + return std::to_string(removed) + '\n'; + } else if (cmd == "world.spawnEntity") { + // Parse + next_float(x); + next_float(y); + next_float(z); + next_int(type); + // Translate + server->pos_translator.from_float(x, y, z); + api_convert_to_mcpi_entity_type(type); + // Spawn + Entity *entity = misc_make_entity_from_id(server->minecraft->level, type); + if (entity == nullptr) { + return CommandServer::Fail; + } + entity->moveTo(x, y, z, 0, 0); + server->minecraft->level->addEntity(entity); + return std::to_string(entity->id) + '\n'; + } else if (cmd == "world.getEntityTypes") { + // Get All Valid Entity Types + std::vector result; + for (const std::pair> &i : misc_get_entity_type_names()) { + int id = static_cast(i.first); + api_convert_to_outside_entity_type(id); + result.push_back(api_join_outputs({std::to_string(id), api_get_output(i.second.second, true)}, arg_separator)); + } + return api_join_outputs(result, list_separator); } else if (cmd == "events.chat.posts") { return api_get_chat_events(server, client, std::nullopt); } else if (cmd == "entity.events.chat.posts") { @@ -405,6 +466,13 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ } else if (cmd == "entity.events.projectile.hits") { next_int(id); return api_get_projectile_events(server, client, id); + } else if (cmd == "entity.events.clear") { + next_int(id); + api_clear_events(client, id); + return CommandServer::NullString; + } else if (cmd == "events.clear") { + api_clear_events(client); + return CommandServer::NullString; } else if (cmd == "entity.setDirection") { // Parse next_int(id); @@ -470,41 +538,6 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ } else { return std::to_string(entity->pitch) + '\n'; } - } else if (cmd == "entity.getEntities") { - // Parse - next_int(id); - next_int(dist); - next_int(type); - Entity *src = server->minecraft->level->getEntity(id); - if (src == nullptr) { - return CommandServer::Fail; - } - // Run - std::vector result; - for (Entity *entity : server->minecraft->level->entities) { - if (is_entity_selected(entity, type) && distance_between(src, entity) < dist) { - result.push_back(get_entity_message(server, entity)); - } - } - return api_join_outputs(result, list_separator); - } else if (cmd == "entity.removeEntities") { - // Parse - next_int(id); - next_int(dist); - next_int(type); - Entity *src = server->minecraft->level->getEntity(id); - if (src == nullptr) { - return CommandServer::Fail; - } - // Run - int removed = 0; - for (Entity *entity : server->minecraft->level->entities) { - if (is_entity_selected(entity, type) && distance_between(src, entity) < dist) { - entity->remove(); - removed++; - } - } - return std::to_string(removed) + '\n'; } else if (cmd == "world.setSign") { // Parse next_int(x); @@ -551,36 +584,6 @@ sign->lines[i] = get_input(line_##i); \ pieces.push_back(api_get_output(line, false)); } return api_join_outputs(pieces, list_separator); - } else if (cmd == "world.spawnEntity") { - // Parse - next_float(x); - next_float(y); - next_float(z); - next_int(type); - // Translate - server->pos_translator.from_float(x, y, z); - if (api_compat_mode) { - api_convert_to_mcpi_entity_type(type); - } - // Spawn - Entity *entity = misc_make_entity_from_id(server->minecraft->level, type); - if (entity == nullptr) { - return CommandServer::Fail; - } - entity->moveTo(x, y, z, 0, 0); - server->minecraft->level->addEntity(entity); - return std::to_string(entity->id) + '\n'; - } else if (cmd == "world.getEntityTypes") { - // Get All Valid Entity Types - std::vector result; - for (const std::pair> &i : misc_get_entity_type_names()) { - int id = static_cast(i.first); - if (api_compat_mode) { - api_convert_to_rj_entity_type(id); - } - result.push_back(api_join_outputs({std::to_string(id), api_get_output(i.second.second, true)}, arg_separator)); - } - return api_join_outputs(result, list_separator); } else if (cmd == "entity.setAbsPos") { // Parse next_int(id); @@ -603,13 +606,6 @@ sign->lines[i] = get_input(line_##i); \ return CommandServer::Fail; } return api_join_outputs({std::to_string(entity->x), std::to_string(entity->y - entity->height_offset), std::to_string(entity->z)}, arg_separator); - } else if (cmd == "entity.events.clear") { - next_int(id); - api_clear_events(client, id); - return CommandServer::NullString; - } else if (cmd == "events.clear") { - api_clear_events(client); - return CommandServer::NullString; } else if (cmd == "reborn.disableCompatMode") { api_compat_mode = false; return std::string(reborn_get_version()) + '\n'; diff --git a/mods/src/api/events.cpp b/mods/src/api/events.cpp index cf12f9db..9b12bcec 100644 --- a/mods/src/api/events.cpp +++ b/mods/src/api/events.cpp @@ -29,9 +29,9 @@ static std::string event_to_string(CommandServer *server, const ProjectileHitEve // Get Outputs std::vector pieces = { // Position - std::to_string(int(nx)), - std::to_string(int(ny)), - std::to_string(int(nz)) + std::to_string(nx), + std::to_string(ny), + std::to_string(nz) }; // Needed For Compatibility if (api_compat_mode) { diff --git a/mods/src/api/internal.h b/mods/src/api/internal.h index 25f23b55..e363f066 100644 --- a/mods/src/api/internal.h +++ b/mods/src/api/internal.h @@ -9,7 +9,7 @@ static constexpr char list_separator = '|'; __attribute__((visibility("internal"))) std::string api_get_output(std::string message, bool replace_comma); __attribute__((visibility("internal"))) std::string api_join_outputs(const std::vector &pieces, char separator); -__attribute__((visibility("internal"))) void api_convert_to_rj_entity_type(int &type); +__attribute__((visibility("internal"))) void api_convert_to_outside_entity_type(int &type); __attribute__((visibility("internal"))) void api_convert_to_mcpi_entity_type(int &type); __attribute__((visibility("internal"))) void _init_api_events();