diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index b7aa57b0..802d4f84 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -99,6 +99,8 @@ set(SRC src/multidraw/storage.cpp # classic-ui src/classic-ui/classic-ui.cpp + # api + src/api/api.cpp ) # Install Splashes install( diff --git a/mods/include/mods/init/init.h b/mods/include/mods/init/init.h index 994d396a..162083d8 100644 --- a/mods/include/mods/init/init.h +++ b/mods/include/mods/init/init.h @@ -31,4 +31,5 @@ void init_screenshot(); void init_f3(); void init_multidraw(); void init_classic_ui(); +void init_api(); } diff --git a/mods/include/mods/server/server.h b/mods/include/mods/server/server.h index 7a6db404..f36bc342 100644 --- a/mods/include/mods/server/server.h +++ b/mods/include/mods/server/server.h @@ -1,5 +1,7 @@ #pragma once +#pragma once + #include #include "server_properties.h" @@ -16,4 +18,5 @@ struct ServerCommand { extern "C" { std::vector *server_get_commands(Minecraft *minecraft, ServerSideNetworkHandler *server_side_network_handler); ServerProperties &get_server_properties(); +std::string get_player_username(Player *player); } \ No newline at end of file diff --git a/mods/src/api/README.md b/mods/src/api/README.md new file mode 100644 index 00000000..5d4821dc --- /dev/null +++ b/mods/src/api/README.md @@ -0,0 +1,88 @@ +# `api` Mod + +This mod implements all of the RaspberryJuice extensions to the MCPI API, for the modding API, see [../misc/api.cpp](). + +This includes: +- [x] `world.getBlocks(x0: int, y0: int, z0: int, x1: int, y1: int, z1: int) -> int[]` +- - Gets all the block ids between x0, y0, z0 and x1, y1, z1 +- [x] `world.getPlayerId(name: str) -> int` +- - Gets the id of the first player who has the same name as `name`. "Fail" is returned on failure. +- [x] `entity.getName(id: int) -> str` +- - Gets the name of the entity of `id`, as MCPI does not have name tags, it will just be the entity type for non-players +- [x] `world.getEntities(type: int) -> {id: int, type: int, name: str, x: float, y: float, z: float}[]` +- - returns a list of entities +- [x] `world.removeEntity(id: int) -> int` +- - removes a single entity and returns 1 if successful +- [x] `world.removeEntities(type: int) -> int` +- - removes all entities of a type, and returns how many were removed +- [x] `events.chat.posts() -> {"0", message}[]` +- - gets a list of chat messages, see the limitations section down below to see why it has a zero +- - The messages are not cleared +- [x] `events.projectile.hits() -> {x: int, y: int, z: int, "1", owner: string, target: int}` +- - Returns a list of projectile hit events, the list is cleared each time it is read by this call +- - When target id is not 0, it means the projectile hit an entity and `x, y, z` is the entity's position +- - When target id is 0, it means the projectile hit a block and `x, y, z` is the block's position +- - Unlike RaspberryJuice, the last argument is the hit entity id, not the entity's name. This shouldn't break anything, and indeed should be more useful. +- [x] `player.setDirection(x: float, y: float, z: float)` +- - sets rotation as if the player was at 0, 0, 0 and looking towards x, y, z +- [x] `entity.setDirection(id: int, x: float, y: float, z: float)` +- - sets rotation as if the entity of `id` was at 0, 0, 0 and looking towards x, y, z +- [x] `player.getDirection() -> {x: float, y: float, z: float}` +- - gets the location the player would be looking at if they were at 0, 0, 0 +- [x] `entity.getDirection() -> {x: float, y: float, z: float}` +- - gets the location the entity of `id` would be looking at if they were at 0, 0, 0 +- [x] `player.setRotation(id: int)` +- - Sets the yaw of the player +- [x] `entity.setRotation(id: int)` +- - Sets the yaw of the entity of `id` +- [x] `player.getRotation` +- - Gets the yaw of the player +- [x] `entity.getRotation` +- - Gets the yaw of the entity of `id` +- [x] `player.getPitch` +- - Gets the pitch of the player +- [x] `entity.getPitch` +- - Gets the pitch of the entity of `id` +- [x] `player.setPitch(id: int)` +- - Sets the pitch of the player +- [x] `entity.setPitch(id: int)` +- - Sets the pitch of the entity of `id` +- [x] `player.getEntities(dist: int, type: int) -> {id: int, type: int, name: str, x: float, y: float, z: float}[]` +- - Gets all entities within `dist` of the player if the entity is of `type` (if `type` is -1, all entity types are included). +- - This can include the player. +- [x] `entity.getEntities(id: int, dist: int, type: int) -> {id: int, type: int, name: str, x: float, y: float, z: float}[]` +- - Gets all entities within `dist` of the entity of `id` if the entity is of `type` (if `type` is -1, all entity types are included). +- - This can include the entity. +- [x] `player.removeEntities(dist: int, type: int) -> int` +- - Removes all entities within `dist` of the player if the entity is of `type` (if `type` is -1, all entity types are included). +- - This will not include any players. +- - It returns the number of entities removed. +- [x] `entity.removeEntities(id: int, dist: int, type: int) -> int` +- - Removes all entities within `dist` of the entity of `id` if the entity is of `type` (if `type` is -1, all entity types are included). +- - This will not include any players, but may include the entity calling it if the entity is not a player. +- - It returns the number of entities removed. +- [x] `world.setSign(x: int, y: int, z: int, id: int, data: int, [l1: str], [l2: str], [l3: str], [l4: str])` +- - Sets a block of `id:data` at the specified point, if the block is a sign, it will attempt to set lines 1 through 4 of the sign to the given text +- - For the API, the lines must be below 100 characters, however when loading signs MCPI will cap it at 16 characters (this can be disabled by patching out the call at `0xd1e2c`). +- - The lines are optional +- - The wiki has a list of blocks: [https://mcpirevival.miraheze.org/wiki/Minecraft:_Pi_Edition_Complete_Block_List], sign is 63 and wall sign is 68 +- [x] `world.spawnEntity(x: int, y: int, z: int, type: int) -> int` +- - Spawns an entity of `type` at the given position +- - Entities with a type of 0 cannot be spawned +- - The list of entity types can be found by running the command below, or on the wiki: [https://mcpirevival.miraheze.org/wiki/Minecraft:_Pi_Edition_Complete_Entity_List] +- [x] `world.getEntityTypes() -> {type: int, name: str}` +- - Returns a list of known entity types, if there are modded entities this list may be incorrect + +It does not implement (due to MCPI limitations): +- Per-entity events +- Per-player events +- `player.getAbsPos`/`player.setAbsPos` as MCPI already does this +- `events.chat.posts`'s id field is always set to -1 +- `events.chat.posts` only returns the last 30 messages, and does *not* clear them after each API call + +Egdecases: +- `world.removeEntity` will remove a player if given a players id. +- `world.removeEntities`/`player.removeEntities`/`entity.removeEntities` will not remove players when given 0 or -1 for type. +- `entity.getName` will not get the name of non-player entities with a type id of 0 due to ambiguity. +- All Raspberry Juice commands/responses involving the player name are designed around the MCJE username restrictions, not the much looser MCPI restrictions. They may cause problems. + diff --git a/mods/src/api/api.cpp b/mods/src/api/api.cpp new file mode 100644 index 00000000..80241193 --- /dev/null +++ b/mods/src/api/api.cpp @@ -0,0 +1,490 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +static std::string getBlocks(CommandServer *commandserver, Vec3 start, Vec3 end) { + int startx = start.x, starty = start.y, startz = start.z; + int endx = end.x, endy = end.y, endz = end.z; + + // Offset cords + commandserver->pos_translator.from(startx, starty, startz); + commandserver->pos_translator.from(endx, endy, endz); + + if (endx < startx) std::swap(startx, endx); + if (endy < starty) std::swap(starty, endy); + if (endz < startz) std::swap(startz, endz); + + // Get + std::string ret = ""; + for (int x = startx; x <= endx; x++) { + for (int y = starty; y <= endy; y++) { + for (int z = startz; z <= endz; z++) { + ret += std::to_string(commandserver->minecraft->level->getTile(x, y, z)) + ","; + } + } + } + if (ret.size() > 1) ret.pop_back(); + return ret + "\n"; +} + +static std::map names = { + // Unsavables + {0, "Unknown"}, + // Animals + {10, "Chicken"}, {11, "Cow"}, {12, "Pig"}, {13, "Sheep"}, + // Hostiles + {32, "Zombie"}, {33, "Creeper"}, {34, "Skeleton"}, {35, "Spider"}, {36, "PigZombie"}, + // Special 2, projectiles + {80, "Arrow"}, {81, "Snowball"}, {82, "ThrownEgg"}, + // Special 1, misc + {64, "ItemEntity"}, {65, "PrimedTnt"}, {66, "FallingTile"}, {83, "Painting"} +}; +void setEntityName(int type, std::string name) { + names[type] = name; +} +std::string getEntityName(Entity *entity) { + if (entity == NULL) { + return ""; + } else if (entity->isPlayer()) { + return get_player_username((Player *) entity); + } else { + int type = entity->getEntityTypeId(); + if (names.find(type) != names.end()) return names[type]; + return std::to_string(entity->id); + } +} + +void setDir(Entity *entity, float x, float y, float z) { + if (entity == NULL) return; + + constexpr double _2PI = 2 * M_PI; + if (x == 0 && z == 0) { + entity->pitch = y > 0 ? -90 : 90; + return; + } + + entity->yaw = fmod((float) atan2(-x, z), (float) _2PI) * (180.0f / M_PI); + float xz = sqrt(x * x + z * z); + entity->pitch = (float) atan(-y / xz) * (180.0f / M_PI); +} +Vec3 getDir(Entity *entity) { + float y = -sin(entity->pitch * (M_PI / 180)); + float xz = cos(entity->pitch * (M_PI / 180)); + float x = -xz * sin(entity->yaw * (M_PI / 180)); + float z = xz * cos(entity->yaw * (M_PI / 180)); + return Vec3{x, y, z}; +} + +std::string getEntityData(CommandServer *commandserver, Entity *entity) { + float x = entity->x, y = entity->y - entity->height_offset, z = entity->z; + commandserver->pos_translator.to(x, y, z); + return std::to_string(entity->id) + "," + + // type + std::to_string(entity->getEntityTypeId()) + "," + + // name + getEntityName(entity) + "," + + // x + std::to_string(x) + "," + + // y + std::to_string(y) + "," + + // z + std::to_string(z); +} + +float edist(Entity *e1, Entity *e2) { + if (e1 == NULL || e2 == NULL) return 0; + float dx = e2->x - e1->x; + float dy = e2->y - e1->y; + float dz = e2->z - e1->z; + return sqrt(dx * dx + dy * dy + dz * dz); +} + +struct ProjectileHitEvent { + int x, y, z; + std::string owner = ""; + int targetId = 0; + + std::string toString(CommandServer *commandserver) { + float nx = x, ny = y, nz = z; + commandserver->pos_translator.to(nx, ny, nz); + return std::to_string((int) nx) + "," + + std::to_string((int) ny) + "," + + std::to_string((int) nz) + ",1," + + owner + "," + std::to_string(targetId); + } +}; + +constexpr size_t EVENT_SIZE = 50; +static int event_at = 0, event_start = 0; +static ProjectileHitEvent hitEvents[EVENT_SIZE]; + +void addProjectile(ProjectileHitEvent event) { + hitEvents[event_at] = event; + event_at++; + event_at %= EVENT_SIZE; + if (event_at == event_start) { + event_start++; + event_start %= EVENT_SIZE; + } +} + +ProjectileHitEvent popProjectile() { + if (event_start == event_at) { + ERR("Over popped projectile hit events!"); + } + ProjectileHitEvent ret = hitEvents[event_start]; + event_start++; + event_start %= EVENT_SIZE; + return ret; +} + +std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServer *commandserver, ConnectedClient &client, const std::string &command) { + size_t arg_start = command.find("("); + if (arg_start == std::string::npos) return "Fail\n"; + std::string cmd = command.substr(0, arg_start); + + // rfind so ) in input doesn't break + size_t cmd_end = command.rfind(")"); + if (cmd_end == std::string::npos) return "Fail\n"; + std::string args = command.substr(arg_start + 1, cmd_end - arg_start - 1); + + // And now the big if-else chain + if (cmd == "world.getBlocks") { + int x0, y0, z0, x1, y1, z1; + int ret = sscanf(args.c_str(), "%d,%d,%d,%d,%d,%d", &x0, &y0, &z0, &x1, &y1, &z1); + if (ret != 6) return "Fail\n"; + + // Get the blocks + return getBlocks(commandserver, Vec3{(float) x0, (float) y0, (float) z0}, Vec3{(float) x1, (float) y1, (float) z1}); + } else if (cmd == "world.getPlayerId") { + for (Player *player : commandserver->minecraft->level->players) { + if (get_player_username(player) == args) + return std::to_string(player->id) + "\n"; + } + INFO("Player [%s] not found.", args.c_str()); + return "Fail\n"; + } else if (cmd == "entity.getName") { + int id; + int ret = sscanf(args.c_str(), "%d", &id); + if (ret != 1) return "\n"; + + Entity *entity = commandserver->minecraft->level->getEntity(id); + if (entity == NULL) { + INFO("Player (or Entity) [%i] not found in entity.getName.", id); + } + return getEntityName(entity) + "\n"; + } else if (cmd == "world.getEntities") { + int type; + int ret = sscanf(args.c_str(), "%d", &type); + if (ret != 1) return "\n"; + + std::string result = ""; + for (Entity *entity : commandserver->minecraft->level->entities) { + if (type == -1 || entity->getEntityTypeId() == type) { + // Get pos + // Put it together + result += getEntityData(commandserver, entity) + "|"; + } + } + if (result.size() > 1) result.pop_back(); + return result + "\n"; + } else if (cmd == "world.removeEntity") { + int id; + int ret = sscanf(args.c_str(), "%d", &id); + if (ret != 1) return "0\n"; + + Entity *entity = commandserver->minecraft->level->getEntity(id); + if (entity != NULL) { + // It may be a good idea to check if the entity is a player before removing + // but it doesn't crash, just lock the player in place until you rejoin the world + entity->remove(); + return "1\n"; + } + return "0\n"; + } else if (cmd == "world.removeEntities") { + int type; + int ret = sscanf(args.c_str(), "%d", &type); + if (ret != 1) return "0\n"; + + int removed = 0; + for (Entity *entity : commandserver->minecraft->level->entities) { + if ((type == -1 || entity->getEntityTypeId() == type) && !entity->isPlayer()) { + entity->remove(); + removed++; + } + } + return std::to_string(removed) + "\n"; + } else if (cmd == "events.chat.posts") { + std::string ret = ""; + for (GuiMessage gm : commandserver->minecraft->gui.messages) { + std::string message = gm.message; + std::replace(message.begin(), message.end(), '|', '\\'); + INFO("%s %i", message.c_str(), gm.time); + ret += "0," + message + "|"; + } + if (ret.size() > 1) ret.pop_back(); + return ret + "\n"; + } else if (cmd == "events.projectile.hits") { + std::string result = ""; + while (event_at != event_start) { + result += popProjectile().toString(commandserver) + "|"; + } + if (result.size() > 1) result.pop_back(); + return result + "\n"; + } else if (cmd == "player.setDirection") { + float x, y, z; + int ret = sscanf(args.c_str(), "%f,%f,%f", &x, &y, &z); + if (ret != 3) return ""; + setDir((Entity *) commandserver->minecraft->player, x, y, z); + } else if (cmd == "entity.setDirection") { + int id; + float x, y, z; + int ret = sscanf(args.c_str(), "%d,%f,%f,%f", &id, &x, &y, &z); + if (ret != 4) return ""; + Entity *entity = commandserver->minecraft->level->getEntity(id); + if (entity == NULL) { + INFO("Entity [%i] not found.", id); + } else { + setDir(entity, x, y, z); + } + } else if (cmd == "player.getDirection") { + Vec3 vec = getDir((Entity *) commandserver->minecraft->player); + return std::to_string(vec.x) + "," + std::to_string(vec.y) + "," + std::to_string(vec.z) + "\n"; + } else if (cmd == "entity.getDirection") { + int id; + int ret = sscanf(args.c_str(), "%d", &id); + if (ret != 1) return ""; + Entity *entity = commandserver->minecraft->level->getEntity(id); + if (entity == NULL) { + INFO("Entity [%i] not found.", id); + return "Fail\n"; + } else { + Vec3 vec = getDir(entity); + return std::to_string(vec.x) + "," + std::to_string(vec.y) + "," + std::to_string(vec.z) + "\n"; + } + } else if (cmd == "player.setRotation") { + float yaw; + int ret = sscanf(args.c_str(), "%f", &yaw); + if (ret != 1) return ""; + commandserver->minecraft->player->yaw = yaw; + } else if (cmd == "entity.setRotation") { + int id; + float yaw; + int ret = sscanf(args.c_str(), "%d,%f", &id, &yaw); + if (ret != 2) return ""; + Entity *entity = commandserver->minecraft->level->getEntity(id); + if (entity == NULL) { + INFO("Entity [%i] not found.", id); + } else { + entity->yaw = yaw; + } + } else if (cmd == "player.setPitch") { + float pitch; + int ret = sscanf(args.c_str(), "%f", &pitch); + if (ret != 1) return ""; + commandserver->minecraft->player->pitch = pitch; + } else if (cmd == "entity.setPitch") { + int id; + float pitch; + int ret = sscanf(args.c_str(), "%d,%f", &id, &pitch); + if (ret != 2) return ""; + Entity *entity = commandserver->minecraft->level->getEntity(id); + if (entity == NULL) { + INFO("Entity [%i] not found.", id); + } else { + entity->pitch = pitch; + } + } else if (cmd == "player.getRotation") { + return std::to_string(commandserver->minecraft->player->yaw) + "\n"; + } else if (cmd == "entity.getRotation") { + int id; + int ret = sscanf(args.c_str(), "%d", &id); + if (ret != 1) return ""; + Entity *entity = commandserver->minecraft->level->getEntity(id); + if (entity == NULL) { + INFO("Entity [%i] not found.", id); + return "Fail\n"; + } else { + return std::to_string(entity->yaw) + "\n"; + } + } else if (cmd == "player.getPitch") { + return std::to_string(commandserver->minecraft->player->pitch) + "\n"; + } else if (cmd == "entity.getPitch") { + int id; + int ret = sscanf(args.c_str(), "%d", &id); + if (ret != 1) return ""; + Entity *entity = commandserver->minecraft->level->getEntity(id); + if (entity == NULL) { + INFO("Entity [%i] not found.", id); + return "Fail\n"; + } else { + return std::to_string(entity->pitch) + "\n"; + } + } else if (cmd == "player.getEntities" || cmd == "entity.getEntities") { + // Parse + int dist, type; + Entity *src = NULL; + if (cmd == "player.getEntities") { + int ret = sscanf(args.c_str(), "%d,%d", &dist, &type); + if (ret != 2) return ""; + src = (Entity *) commandserver->minecraft->player; + } else { + int id = 0; + int ret = sscanf(args.c_str(), "%d,%d,%d", &id, &dist, &type); + if (ret != 3) return ""; + src = commandserver->minecraft->level->getEntity(id); + if (src == NULL) { + INFO("Entity [%i] not found.", id); + return "Fail\n"; + } + } + // Run + std::string result = ""; + for (Entity *entity : commandserver->minecraft->level->entities) { + if ((type == -1 || entity->getEntityTypeId() == type) && edist(src, entity) < dist) { + result += getEntityData(commandserver, entity) + "|"; + } + } + if (result.size() > 1) result.pop_back(); + return result + "\n"; + } else if (cmd == "player.removeEntities" || cmd == "entity.removeEntities") { + // Parse + int dist, type; + Entity *src = NULL; + if (cmd == "player.removeEntities") { + int ret = sscanf(args.c_str(), "%d,%d", &dist, &type); + if (ret != 2) return ""; + src = (Entity *) commandserver->minecraft->player; + } else { + int id = 0; + int ret = sscanf(args.c_str(), "%d,%d,%d", &id, &dist, &type); + if (ret != 3) return ""; + src = commandserver->minecraft->level->getEntity(id); + if (src == NULL) { + INFO("Entity [%i] not found.", id); + return "Fail\n"; + } + } + // Run + int removed = 0; + for (Entity *entity : commandserver->minecraft->level->entities) { + if ((type == -1 || entity->getEntityTypeId() == type) && edist(src, entity) < dist && !entity->isPlayer()) { + entity->remove(); + removed++; + } + } + return std::to_string(removed) + "\n"; + } else if (cmd == "world.setSign") { + // Parse + int x, y, z, id, data; + char l1[100], l2[100], l3[100], l4[100]; + int ret = sscanf(args.c_str(), "%d,%d,%d,%d,%d,%99[^,],%99[^,],%99[^,],%99s", &x, &y, &z, &id, &data, l1, l2, l3, l4); + if (ret < 5) return "Fail\n"; + // Translate + commandserver->pos_translator.from(x, y, z); + // Set block + commandserver->minecraft->level->setTileAndData(x, y, z, id, data); + // Set sign data + if (ret == 3) return ""; + SignTileEntity *sign = (SignTileEntity *) commandserver->minecraft->level->getTileEntity(x, y, z); + if (sign == NULL || sign->type != 4) return ""; + if (ret > 5) sign->lines[0] = l1; + if (ret > 6) sign->lines[1] = l2; + if (ret > 7) sign->lines[2] = l3; + if (ret > 8) sign->lines[3] = l4; + } else if (cmd == "world.spawnEntity") { + // Parse + float x, y, z; + int id; + int ret = sscanf(args.c_str(), "%f,%f,%f,%d", &x, &y, &z, &id); + if (ret != 4) return "Fail\n"; + // Translate + int ix = x, iy = y, iz = z; + int ix1 = x, iy1 = y, iz1 = z; + commandserver->pos_translator.from(ix, iy, iz); + x += ix - ix1; + y += iy - iy1; + z += iz - iz1; + // Spawn + Entity *entity = NULL; + if (id < 0x40) { + entity = (Entity *) MobFactory::CreateMob(id, commandserver->minecraft->level); + } else { + entity = EntityFactory::CreateEntity(id, commandserver->minecraft->level); + } + if (entity == NULL) return "Fail\n"; + entity->moveTo(x, y, z, 0, 0); + commandserver->minecraft->level->addEntity(entity); + return std::to_string(entity->id) + "\n"; + } else if (cmd == "world.getEntityTypes") { + std::string result = ""; + for (auto &i : names) { + result += std::to_string(i.first) + "," + i.second + "|"; + } + if (result.size() > 1) result.pop_back(); + return result + "\n"; + } else { + // Either invalid or a vanilla command, either way hand it off to the orignal handler + return old(commandserver, client, command); + } + // No return, it's different from empty! Empty is "\n". "" will result in nothing being sent back while "\n" will result in an empty string being sent back + return ""; +} + +// Arrow entity hit +static Entity *shooter = NULL; +HitResult *Arrow_tick_HitResult_constructor_injection(HitResult *self, Entity *target) { + // Orignal + self->type = 1; + self->entity = target; + self->unknown = false; + self->x = target->x; + self->y = target->y; + self->z = target->z; + // Add event + if (shooter && shooter->isPlayer()) { + addProjectile(ProjectileHitEvent{ + .x = (int) self->x, + .y = (int) self->y, + .z = (int) self->z, + .owner = get_player_username((Player *) shooter), + .targetId = target->id + }); + } + return self; +} + +// Arrow block hit +void Arrow_tick_injection(Arrow_tick_t old, Arrow *self) { + int oldFlightTime = self->flight_time; + shooter = self->level->getEntity(self->getAuxData()); + old(self); + if (self && !self->pending_removal && self->grounded && oldFlightTime != self->flight_time) { + if (shooter && shooter->isPlayer()) { + // Hit! Get the data + INFO("B_ADDED"); + addProjectile(ProjectileHitEvent{ + .x = self->hit_x, + .y = self->hit_y, + .z = self->hit_z, + .owner = get_player_username((Player *) shooter), + .targetId = 0 + }); + } + } +} + +void init_api() { + overwrite_calls(CommandServer_parse, CommandServer_parse_injection); + overwrite_calls(Arrow_tick, Arrow_tick_injection); + overwrite_call((void *) 0x8b1e8, (void *) Arrow_tick_HitResult_constructor_injection); +} diff --git a/mods/src/init/init.cpp b/mods/src/init/init.cpp index 8675e099..bb78817c 100644 --- a/mods/src/init/init.cpp +++ b/mods/src/init/init.cpp @@ -38,6 +38,7 @@ __attribute__((constructor)) static void init() { init_cake(); init_home(); init_override(); + init_api(); if (!reborn_is_server()) { init_benchmark(); } diff --git a/mods/src/server/server.cpp b/mods/src/server/server.cpp index 155bf50f..e7b498cf 100644 --- a/mods/src/server/server.cpp +++ b/mods/src/server/server.cpp @@ -112,7 +112,7 @@ static std::vector get_players_in_level(Level *level) { return level->players; } // Get Player's Username -static std::string get_player_username(Player *player) { +std::string get_player_username(Player *player) { const std::string *username = &player->username; char *safe_username_c = from_cp437(username->c_str()); std::string safe_username = safe_username_c; diff --git a/symbols/CMakeLists.txt b/symbols/CMakeLists.txt index 1a82f073..46d6ab69 100644 --- a/symbols/CMakeLists.txt +++ b/symbols/CMakeLists.txt @@ -131,6 +131,7 @@ set(SRC src/gui/components/GuiComponent.def src/gui/components/Button.def src/gui/Gui.def + src/gui/GuiMessage.def src/gui/components/IntRectangle.def src/gui/components/RectangleArea.def src/gui/components/ScrollingPane.def diff --git a/symbols/src/entity/Arrow.def b/symbols/src/entity/Arrow.def index c5d4949f..e0517744 100644 --- a/symbols/src/entity/Arrow.def +++ b/symbols/src/entity/Arrow.def @@ -1,3 +1,10 @@ extends Entity; +vtable 0x10db20; + property bool is_critical = 0xd8; +property int hit_x = 0xdc; +property int hit_y = 0xe0; +property int hit_z = 0xe4; +property bool grounded = 0xf0; +property int flight_time = 0xf8; diff --git a/symbols/src/entity/Entity.def b/symbols/src/entity/Entity.def index 679a65a5..d7f67025 100644 --- a/symbols/src/entity/Entity.def +++ b/symbols/src/entity/Entity.def @@ -48,4 +48,4 @@ property bool on_ground = 0xb2; property bool pending_removal = 0xb8; property bool freeze_physics = 0xb9; property float fall_distance = 0xac; -property bool fire_immune = 0xc6; \ No newline at end of file +property bool fire_immune = 0xc6; diff --git a/symbols/src/gui/Gui.def b/symbols/src/gui/Gui.def index e931fc46..b90a72a4 100644 --- a/symbols/src/gui/Gui.def +++ b/symbols/src/gui/Gui.def @@ -16,6 +16,7 @@ method void handleKeyPressed(int key) = 0x25a08; method void renderHearts() = 0x2641c; method void renderDebugInfo() = 0x26958; +property std::vector messages = 0x18; property Minecraft *minecraft = 0x9f4; property float selected_item_text_timer = 0x9fc; property int flashing_slot = 0xa2c; diff --git a/symbols/src/gui/GuiMessage.def b/symbols/src/gui/GuiMessage.def new file mode 100644 index 00000000..9e129fa3 --- /dev/null +++ b/symbols/src/gui/GuiMessage.def @@ -0,0 +1,6 @@ +size 0x8; + +property std::string message = 0x0; +property int time = 0x4; + +mark-as-simple; diff --git a/symbols/src/tile/entity/SignTileEntity.def b/symbols/src/tile/entity/SignTileEntity.def index f25f52d7..48b342d3 100644 --- a/symbols/src/tile/entity/SignTileEntity.def +++ b/symbols/src/tile/entity/SignTileEntity.def @@ -1 +1,3 @@ extends TileEntity; + +property std::string lines[4] = 0x30; \ No newline at end of file