diff --git a/docs/API.md b/docs/API.md index eb02109d..04428002 100644 --- a/docs/API.md +++ b/docs/API.md @@ -16,7 +16,7 @@ By default, MCPI-Reborn runs in a "compatibility mode." This makes it completely * Bold text only applies to the compatibility mode. * 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). +* In the 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. @@ -88,30 +88,30 @@ By default, MCPI-Reborn runs in a "compatibility mode." This makes it completely * 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]. + * Description: Retrieve all entities of the specified type[^1][^2]. * 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. + * Description: Retrieve all entities of the specified type[^1][^2] within the given distance of the provided entity. * Output: See above. * `world.removeEntity(entity_id)` - * Description: Remove the specified entity. + * Description: Remove the specified entity[^1]. * Output: `number_of_entities_removed` * `world.removeEntities(entity_type_id)` - * Description: Remove all entities of the specified type[^1]. + * Description: Remove all entities of the specified type[^1][^2]. * 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. + * Description: Remove all entities of the specified type[^1][^2] 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 known entity types. + * Description: Retrieve all spawnable 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. + * Description: Retrieve the name of the specified entity. For players, this will be their username. * Output: `{entity_name}` * `entity.setDirection(entity_id,:x:,:y:,:z:)` * Description: Set the specified entity's rotation using a unit vector. @@ -128,10 +128,10 @@ By default, MCPI-Reborn runs in a "compatibility mode." This makes it completely * `entity.getPitch(entity_id)` * Description: Retrieve the specified entity's pitch. * Output: `:pitch:` -* `entity.setAbsPos(entity_id,:x:,:y:,:z:)`[^2] - * Description: Move the specified entity to the given absolute[^3] position. -* `entity.getAbsPos(entity_id)`[^2] - * Description: Retrieve the given entity's absolute[^3] position. +* `entity.setAbsPos(entity_id,:x:,:y:,:z:)`[^3] + * Description: Move the specified entity to the given absolute[^4] position. +* `entity.getAbsPos(entity_id)`[^3] + * Description: Retrieve the given entity's absolute[^4] position. * Output: `:x:,:y:,:z:` * `entity.events.block.hits(entity_id)` * Description: Retrieve all queued block hit events produced by the specified entity. @@ -163,9 +163,10 @@ By default, MCPI-Reborn runs in a "compatibility mode." This makes it completely * Description: Retrieve the text of the given sign. * Output: List of `{line}` * `entity.getType(entity_id)` - * Description: Check the type of the given entity. + * Description: Check the type of the given entity. For special entities like players, this will be `0`. * Output: `entity_type_id` -[^1]: If the ID is `-1`, it will match all entities. -[^2]: RaspberryJuice only implements the `player.*` versions of these commands. -[^3]: The API normally applies an offset to all coordinates, these commands use the raw data. \ No newline at end of file +[^1]: These commands will never match players. +[^2]: If the ID is `-1`, it will match all entities. +[^3]: RaspberryJuice only implements the `player.*` versions of these commands. +[^4]: 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/CMakeLists.txt b/mods/CMakeLists.txt index 40d0b8d6..1bef7671 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -102,6 +102,7 @@ add_library(mods SHARED # api src/api/api.cpp src/api/events.cpp + src/api/compat.cpp # shading src/shading/init.cpp src/shading/tesselator.cpp diff --git a/mods/src/api/api.cpp b/mods/src/api/api.cpp index 814156e8..fc5cf1be 100644 --- a/mods/src/api/api.cpp +++ b/mods/src/api/api.cpp @@ -1,13 +1,8 @@ #include -#include -#include -#include #include -#include #include #include -#include #include #include @@ -16,62 +11,6 @@ #include "internal.h" -// Compatibility Mode -bool api_compat_mode = true; - -// Read String Input -static std::string get_input(std::string message) { - // Decode - if (!api_compat_mode) { - message = misc_base64_decode(message); - } - // Convert To CP-437 - return to_cp437(message); -} -// Output String -std::string api_get_output(std::string message, const bool replace_comma) { - // Convert To Unicode - message = from_cp437(message); - // Escape Characters - if (api_compat_mode) { - // Output In Plaintext For RJ Compatibility - std::ranges::replace(message, list_separator, '\\'); - if (replace_comma) { - std::ranges::replace(message, arg_separator, '.'); - } - } else { - // Encode - message = misc_base64_encode(message); - } - // Return - return message; -} - -// Join Strings Into Output -std::string api_join_outputs(const std::vector &pieces, const char separator) { - // Join - std::string out; - for (std::string piece : pieces) { - // Check - if (piece.find(separator) != std::string::npos) { - // This Should Be Escapes - IMPOSSIBLE(); - } - // Remove Trailing Newline - if (!piece.empty() && piece.back() == '\n') { - piece.pop_back(); - } - // Add - out += piece + separator; - } - // Remove Hanging Comma - if (!out.empty()) { - out.pop_back(); - } - // Return - return out + '\n'; -} - // Get Blocks In Region static std::string get_blocks(CommandServer *server, const Vec3 &start, const Vec3 &end) { // Start Coordinate @@ -180,46 +119,6 @@ static Vec3 get_dir(const Entity *entity) { return Vec3{x, y, z}; } -// Entity Types -static std::unordered_map modern_entity_id_mapping = { - {93, EntityType::CHICKEN}, - {92, EntityType::COW}, - {90, EntityType::PIG}, - {91, EntityType::SHEEP}, - {54, EntityType::ZOMBIE}, - {50, EntityType::CREEPER}, - {51, EntityType::SKELETON}, - {52, EntityType::SPIDER}, - {57, EntityType::ZOMBIE_PIGMAN}, - {1, EntityType::DROPPED_ITEM}, - {20, EntityType::PRIMED_TNT}, - {21, EntityType::FALLING_SAND}, - {10, EntityType::ARROW}, - {11, EntityType::THROWN_SNOWBALL}, - {7, EntityType::THROWN_EGG}, - {9, EntityType::PAINTING} -}; -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; - } - } -} -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]); - } -} - // Convert Entity To String static std::string get_entity_message(CommandServer *server, Entity *entity) { std::vector pieces; @@ -252,7 +151,7 @@ static float distance_between(const Entity *e1, const Entity *e2) { const float dx = e2->x - e1->x; const float dy = e2->y - e1->y; const float dz = e2->z - e1->z; - return std::sqrt(dx * dx + dy * dy + dz * dz); + return std::sqrt((dx * dx) + (dy * dy) + (dz * dz)); } // Get Sign Tile Entity @@ -291,6 +190,7 @@ static const std::string player_namespace = "player."; #define next_int(out) next_number(out, int, std::stoi) #define next_float(out) next_number(out, float, std::stof) std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServer *server, ConnectedClient &client, const std::string &command) { + // Parse Command size_t arg_start = command.find('('); if (arg_start == std::string::npos) { return CommandServer::Fail; @@ -324,7 +224,7 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ // Parse next_string(input, true); // Search - std::string username = get_input(input); + std::string username = api_get_input(input); for (Player *player : server->minecraft->level->players) { if (misc_get_player_username_utf(player) == username) { // Found @@ -402,7 +302,7 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ // Run std::vector result; for (Entity *entity : server->minecraft->level->entities) { - if (is_entity_selected(entity, type) && distance_between(src, entity) < dist) { + if (is_entity_selected(entity, type) && distance_between(src, entity) <= dist) { result.push_back(get_entity_message(server, entity)); } } @@ -419,7 +319,7 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ // Run int removed = 0; for (Entity *entity : server->minecraft->level->entities) { - if (is_entity_selected(entity, type) && distance_between(src, entity) < dist) { + if (is_entity_selected(entity, type) && distance_between(src, entity) <= dist) { entity->remove(); removed++; } @@ -554,7 +454,7 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ if (sign != nullptr) { #define next_sign_line(i) \ next_string(line_##i, false); \ -sign->lines[i] = get_input(line_##i); \ +sign->lines[i] = api_get_input(line_##i); \ (void) 0 next_sign_line(0); next_sign_line(1); diff --git a/mods/src/api/compat.cpp b/mods/src/api/compat.cpp new file mode 100644 index 00000000..acc59e4a --- /dev/null +++ b/mods/src/api/compat.cpp @@ -0,0 +1,104 @@ +#include + +#include +#include + +#include + +#include "internal.h" + +// Compatibility Mode +bool api_compat_mode = true; + +// Read String Input +std::string api_get_input(std::string message) { + // Decode + if (!api_compat_mode) { + message = misc_base64_decode(message); + } + // Convert To CP-437 + return to_cp437(message); +} +// Output String +std::string api_get_output(std::string message, const bool replace_comma) { + // Convert To Unicode + message = from_cp437(message); + // Escape Characters + if (api_compat_mode) { + // Output In Plaintext For RJ Compatibility + std::ranges::replace(message, list_separator, '\\'); + if (replace_comma) { + std::ranges::replace(message, arg_separator, '.'); + } + } else { + // Encode + message = misc_base64_encode(message); + } + // Return + return message; +} + +// Join Strings Into Output +std::string api_join_outputs(const std::vector &pieces, const char separator) { + // Join + std::string out; + for (std::string piece : pieces) { + // Check + if (piece.find(separator) != std::string::npos) { + // This Should Be Escapes + IMPOSSIBLE(); + } + // Remove Trailing Newline + if (!piece.empty() && piece.back() == '\n') { + piece.pop_back(); + } + // Add + out += piece + separator; + } + // Remove Hanging Comma + if (!out.empty()) { + out.pop_back(); + } + // Return + return out + '\n'; +} + +// Entity Types +static std::unordered_map modern_entity_id_mapping = { + {93, EntityType::CHICKEN}, + {92, EntityType::COW}, + {90, EntityType::PIG}, + {91, EntityType::SHEEP}, + {54, EntityType::ZOMBIE}, + {50, EntityType::CREEPER}, + {51, EntityType::SKELETON}, + {52, EntityType::SPIDER}, + {57, EntityType::ZOMBIE_PIGMAN}, + {1, EntityType::DROPPED_ITEM}, + {20, EntityType::PRIMED_TNT}, + {21, EntityType::FALLING_SAND}, + {10, EntityType::ARROW}, + {11, EntityType::THROWN_SNOWBALL}, + {7, EntityType::THROWN_EGG}, + {9, EntityType::PAINTING} +}; +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; + } + } +} +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]); + } +} \ No newline at end of file diff --git a/mods/src/api/events.cpp b/mods/src/api/events.cpp index 9b12bcec..50a91687 100644 --- a/mods/src/api/events.cpp +++ b/mods/src/api/events.cpp @@ -1,10 +1,7 @@ -#include #include #include -#include #include -#include #include #include diff --git a/mods/src/api/internal.h b/mods/src/api/internal.h index e363f066..4d761989 100644 --- a/mods/src/api/internal.h +++ b/mods/src/api/internal.h @@ -1,11 +1,18 @@ #pragma once +#include +#include +#include + +#include + __attribute__((visibility("internal"))) extern bool api_compat_mode; static constexpr int no_entity_id = -1; -static constexpr char arg_separator = ','; -static constexpr char list_separator = '|'; +static constexpr char arg_separator = ','; // Used For Arguments And Tuples +static constexpr char list_separator = '|'; // Used For Lists +__attribute__((visibility("internal"))) std::string api_get_input(std::string message); __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);