diff --git a/include/globals.h b/include/globals.h index 1974473e..599402c1 100644 --- a/include/globals.h +++ b/include/globals.h @@ -162,6 +162,9 @@ // Doesn't implement authentication, hence disabled by default. // #define DEV_ENABLE_BEEF_DUMPS +// If defined, mobs are allowed to affect terrain +#define MOB_GRIEFING + #define STATE_NONE 0 #define STATE_STATUS 1 #define STATE_LOGIN 2 @@ -253,12 +256,13 @@ typedef struct { union EntityDataValue { uint8_t byte; - int pose; + int varint; // Also used for poses }; typedef struct { uint8_t index; // 0 - Byte + // 1 - VarInt // 21 - Pose int type; union EntityDataValue value; diff --git a/src/main.c b/src/main.c index 1d8bddbc..0243e101 100644 --- a/src/main.c +++ b/src/main.c @@ -377,14 +377,33 @@ void handlePacket (int client_fd, int length, int packet_id, int state) { if (mob_y != 255) { // Spawn passive mobs above ground during the day, // or hostiles underground and during the night + uint32_t mob_choice = (r >> 12) & 3; if ((world_time < 13000 || world_time > 23460) && mob_y > 48) { - uint32_t mob_choice = (r >> 12) & 3; - if (mob_choice == 0) spawnMob(25, mob_x, mob_y, mob_z, 4); // Chicken - else if (mob_choice == 1) spawnMob(28, mob_x, mob_y, mob_z, 10); // Cow - else if (mob_choice == 2) spawnMob(95, mob_x, mob_y, mob_z, 10); // Pig - else if (mob_choice == 3) spawnMob(106, mob_x, mob_y, mob_z, 8); // Sheep + switch (mob_choice) { + case 0: + spawnMob(25, mob_x, mob_y, mob_z, 4); // Chicken + case 1: + spawnMob(28, mob_x, mob_y, mob_z, 10); // Cow + case 2: + spawnMob(95, mob_x, mob_y, mob_z, 10); // Pig + case 3: + spawnMob(106, mob_x, mob_y, mob_z, 8); // Sheep + default: + break; + } } else { - spawnMob(145, mob_x, mob_y, mob_z, 20); // Zombie + switch (mob_choice) { + case 0: + case 1: + case 2: + spawnMob(/*(biome == W_desert) ? 65 :*/ 145, mob_x, mob_y, mob_z, 20); // Zombie + break; + case 3: + spawnMob(30, mob_x, mob_y, mob_z, 8); // Creeper, aww man + break; + default: + break; + } } } } @@ -443,7 +462,7 @@ void handlePacket (int client_fd, int length, int packet_id, int state) { case 0x34: if (state == STATE_PLAY) cs_setHeldItem(client_fd); break; - + case 0x3C: if (state == STATE_PLAY) cs_swingArm(client_fd); break; diff --git a/src/procedures.c b/src/procedures.c index 59330fc4..cbc64f4a 100644 --- a/src/procedures.c +++ b/src/procedures.c @@ -398,14 +398,14 @@ void broadcastPlayerMetadata (PlayerData *player) { EntityData metadata[] = { { - 0, // Index (Entity Bit Mask) - 0, // Type (Byte) - { entity_bit_mask }, // Value + 0, // Index (Entity Bit Mask) + 0, // Type (Byte) + entity_bit_mask, // Value }, { - 6, // Index (Pose), - 21, // Type (Pose), - { pose }, // Value (Standing) + 6, // Index (Pose), + 21, // Type (Pose), + pose, // Value (Standing) } }; @@ -431,15 +431,28 @@ void broadcastMobMetadata (int client_fd, int entity_id) { size_t length; switch (mob->type) { + case 30: { // Creeper + int fuse; + if ((mob->data >> 6) & 3) fuse = 1; + else fuse = -1; + metadata = malloc(sizeof *metadata); + metadata[0] = (EntityData){ + 16, // Index (Fuse) + 1, // Type (VarInt) + { .varint = fuse }, // Value + }; + length = 1; + break; + } case 106: // Sheep if (!((mob->data >> 5) & 1)) // Don't send metadata if sheep isn't sheared return; metadata = malloc(sizeof *metadata); metadata[0] = (EntityData){ - 17, // Index (Sheep Bit Mask), - 0, // Type (Byte), - { (uint8_t)0x10 }, // Value + 17, // Index (Sheep Bit Mask), + 0, // Type (Byte), + (uint8_t)0x10, // Value }; length = 1; @@ -1374,6 +1387,39 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t } +void createExplosion (short x, uint8_t y, short z, short radius, int8_t damage, int attacker) { + #ifdef MOB_GRIEFING + if (true) { + #else + if (attacker == 0) { + #endif + for (short xI = x - radius; xI <= x + radius; xI++) { + for (uint8_t yI = y - radius; yI <= y + radius; yI++) { + for (short zI = z - radius; zI <= z + radius; zI++) { + short randRad = (radius * (fast_rand() / 0x1FFFFFF + 192)) / 255; + + if (((xI- x) * (xI - x) + (yI - y) * (yI - y) + (zI - z) * (zI - z)) <= randRad*randRad) { + makeBlockChange(xI, yI, zI, B_air); + } + } + } + } + } + + if (damage != 0) { + for (int i = 0; i < MAX_PLAYERS; i ++) { + if (player_data[i].client_fd == -1) continue; + short x2 = x - player_data[i].x, y2 = z - player_data[i].z; + if (x2 * x2 + y2 * y2 < radius * radius) hurtEntity(player_data[i].client_fd, attacker, D_explosion, 10); + } + for (int i = 0; i < MAX_MOBS; i ++) { + if (mob_data[i].type == 0) continue; + short x2 = x - mob_data[i].x, y2 = z - mob_data[i].z; + if (x2 * x2 + y2 * y2 < radius * radius) hurtEntity(-2 - i, attacker, D_explosion, 10); + } + } +} + void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health) { for (int i = 0; i < MAX_MOBS; i ++) { @@ -1523,6 +1569,15 @@ void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t da // Killed by being in lava strcpy((char *)recv_buffer + player_name_len, " tried to swim in lava"); recv_buffer[player_name_len + 22] = '\0'; + } else if (damage_type == D_explosion) { + // Killed by an explosion + if (attacker_id < -1) { + strcpy((char *)recv_buffer + player_name_len, " blown up by a mob"); + recv_buffer[player_name_len + 18] = '\0'; + } else { + strcpy((char *)recv_buffer + player_name_len, " blew up"); + recv_buffer[player_name_len + 8] = '\0'; + } } else if (attacker_id < -1) { // Killed by a mob strcpy((char *)recv_buffer + player_name_len, " was slain by a mob"); @@ -1574,6 +1629,7 @@ void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t da switch (mob->type) { case 25: givePlayerItem(player, I_chicken, 1); break; case 28: givePlayerItem(player, I_beef, 1 + (fast_rand() % 3)); break; + case 30: givePlayerItem(player, I_gunpowder, (fast_rand() % 3)); break; case 95: givePlayerItem(player, I_porkchop, 1 + (fast_rand() % 3)); break; case 106: givePlayerItem(player, I_mutton, 1 + (fast_rand() & 1)); break; case 145: givePlayerItem(player, I_rotten_flesh, (fast_rand() % 3)); break; @@ -1717,8 +1773,8 @@ void handleServerTick (int64_t time_since_last_tick) { // Currently has no effect on hostile mobs uint8_t panic = (mob_data[i].data >> 6) & 3; - // Burn hostile mobs if above ground during sunlight - if (!passive && (world_time < 13000 || world_time > 23460) && mob_data[i].y > 48) { + // Burn undead mobs if above ground during sunlight + if (mob_data[i].type == 145 && (world_time < 13000 || world_time > 23460) && mob_data[i].y > 48) { hurtEntity(entity_id, -1, D_on_fire, 2); } @@ -1788,8 +1844,20 @@ void handleServerTick (int64_t time_since_last_tick) { // If we're already next to the player, hurt them and skip movement if (closest_dist < 3 && abs(old_y - closest_player->y) < 2) { - hurtEntity(closest_player->client_fd, entity_id, D_generic, 6); + if (mob_data[i].type == 30) { // If mob is a creeper explode instead of deal meelee damage + if (panic >= 2) { + createExplosion(mob_data[i].x, mob_data[i].y, mob_data[i].z, 4, 10, entity_id); + } else if (server_ticks % (uint32_t)TICKS_PER_SECOND == 0) { + mob_data[i].data += (1 << 6); + broadcastMobMetadata(-1, entity_id); + } + } else { + hurtEntity(closest_player->client_fd, entity_id, D_generic, 6); + } continue; + } else if (mob_data[i].type == 30 && panic != 0) { // Defuse creeper + mob_data[i].data &= 0x3F; + broadcastMobMetadata(-1, entity_id); } // Move towards the closest player on 8 axis @@ -1936,8 +2004,9 @@ ssize_t writeEntityData (int client_fd, EntityData *data) { switch (data->type) { case 0: // Byte return writeByte(client_fd, data->value.byte); + case 1: // VarInt case 21: // Pose - writeVarInt(client_fd, data->value.pose); + writeVarInt(client_fd, data->value.varint); return 0; default: return -1; @@ -1952,8 +2021,9 @@ int sizeEntityData (EntityData *data) { case 0: // Byte value_size = 1; break; + case 1: // VarInt case 21: // Pose - value_size = sizeVarInt(data->value.pose); + value_size = sizeVarInt(data->value.varint); break; default: return -1;