diff --git a/include/globals.h b/include/globals.h index 7aa3f90..0caa465 100644 --- a/include/globals.h +++ b/include/globals.h @@ -93,6 +93,9 @@ typedef struct { short x; uint8_t y; short z; + // Lower 5 bits: health + // Upper 3 bits: reserved (?) + uint8_t data; } MobData; #pragma pack(pop) diff --git a/include/packets.h b/include/packets.h index 9fa5632..85177af 100644 --- a/include/packets.h +++ b/include/packets.h @@ -1,6 +1,7 @@ #ifndef H_PACKETS #define H_PACKETS +// Serverbound packets int cs_handshake (int client_fd); int cs_loginStart (int client_fd, uint8_t *uuid, char *name); int cs_clientInformation (int client_fd); @@ -17,6 +18,7 @@ int cs_closeContainer (int client_fd); int cs_clientStatus (int client_fd); int cs_chat(int client_fd); +// Clientbound packets int sc_loginSuccess (int client_fd, uint8_t *uuid, char *name); int sc_knownPacks (int client_fd); int sc_finishConfiguration (int client_fd); @@ -44,6 +46,9 @@ int sc_damageEvent (int client_fd, int id, int type); int sc_setHealth (int client_fd, uint8_t health, uint8_t food); int sc_respawn (int client_fd); int sc_systemChat (int client_fd, char* message, uint16_t len); +int cs_interact (int client_fd); +int sc_entityEvent (int client_fd, int entity_id, uint8_t status); +int sc_removeEntity (int client_fd, int entity_id); int sc_registries (int client_fd); #endif diff --git a/include/procedures.h b/include/procedures.h index 0dc640a..33dcaee 100644 --- a/include/procedures.h +++ b/include/procedures.h @@ -31,7 +31,8 @@ uint16_t getMiningResult (uint16_t held_item, uint8_t block); void bumpToolDurability (PlayerData *player); void handlePlayerAction (PlayerData *player, int action, short x, short y, short z); -void spawnMob (uint8_t type, short x, uint8_t y, short z); +void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health); +void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t damage); void handleServerTick (int64_t time_since_last_tick); #endif diff --git a/src/main.c b/src/main.c index b9dcea8..4b9367f 100644 --- a/src/main.c +++ b/src/main.c @@ -158,14 +158,20 @@ void handlePacket (int client_fd, int length, int packet_id) { } break; - case 0x1B: { + case 0x1B: if (state == STATE_PLAY) { // Serverbound keep-alive (ignored) recv_all(client_fd, recv_buffer, 8, false); return; } break; - } + + case 0x19: + if (state == STATE_PLAY) { + if (cs_interact(client_fd)) break; + return; + } + break; case 0x1D: case 0x1E: @@ -190,10 +196,7 @@ void handlePacket (int client_fd, int length, int packet_id) { if (on_ground) { int8_t damage = player->grounded_y - player->y - 3; if (damage > 0 && getBlockAt(player->x, player->y, player->z) != B_water) { - if (damage >= player->health) player->health = 0; - else player->health -= damage; - sc_damageEvent(client_fd, client_fd, D_fall); - sc_setHealth(client_fd, player->health, player->hunger); + hurtEntity(client_fd, -1, D_fall, damage); } player->grounded_y = player->y; } @@ -295,14 +298,14 @@ void handlePacket (int client_fd, int length, int packet_id) { getBlockAt(mob_x, mob_y + 1, mob_z) == B_air ) { // Spawn passive mobs during the day, hostiles during the night - if (world_time < 12000) { + if (world_time < 13000) { uint32_t mob_choice = (r >> 12) & 3; - if (mob_choice == 0) spawnMob(25, mob_x, mob_y, mob_z); // Chicken - else if (mob_choice == 1) spawnMob(28, mob_x, mob_y, mob_z); // Cow - else if (mob_choice == 2) spawnMob(95, mob_x, mob_y, mob_z); // Pig - else if (mob_choice == 3) spawnMob(106, mob_x, mob_y, mob_z); // Sheep + if (mob_choice == 0) spawnMob(25, mob_x, mob_y, mob_z, 20); // Chicken + else if (mob_choice == 1) spawnMob(28, mob_x, mob_y, mob_z, 20); // Cow + else if (mob_choice == 2) spawnMob(95, mob_x, mob_y, mob_z, 20); // Pig + else if (mob_choice == 3) spawnMob(106, mob_x, mob_y, mob_z, 20); // Sheep } else { - spawnMob(145, mob_x, mob_y, mob_z); // Zombie + spawnMob(145, mob_x, mob_y, mob_z, 20); // Zombie } } } diff --git a/src/packets.c b/src/packets.c index 0d9b9cd..159569f 100644 --- a/src/packets.c +++ b/src/packets.c @@ -973,6 +973,52 @@ int cs_chat (int client_fd) { return 0; } +// C->S Interact +int cs_interact (int client_fd) { + + int entity_id = readVarInt(client_fd); + uint8_t type = readByte(client_fd); + + if (type == 2) { + // Ignore target coordinates + recv_all(client_fd, recv_buffer, 12, false); + } + if (type != 1) { + // Ignore hand + recv_all(client_fd, recv_buffer, 1, false); + } + + // Ignore sneaking flag + recv_all(client_fd, recv_buffer, 1, false); + + hurtEntity(entity_id, client_fd, D_generic, 1); + + return 0; +} + +// S->C Entity Event +int sc_entityEvent (int client_fd, int entity_id, uint8_t status) { + + writeVarInt(client_fd, 6); + writeByte(client_fd, 0x1E); + + writeUint32(client_fd, entity_id); + writeByte(client_fd, status); + + return 0; +} + +// S->C Remove Entities, but for only one entity per packet +int sc_removeEntity (int client_fd, int entity_id) { + + writeVarInt(client_fd, 2 + sizeVarInt(entity_id)); + writeByte(client_fd, 0x46); + + writeByte(client_fd, 1); + writeVarInt(client_fd, entity_id); + +} + // S->C Registry Data (multiple packets) and Update Tags (configuration, multiple packets) int sc_registries (int client_fd) { diff --git a/src/procedures.c b/src/procedures.c index cf98b08..c9b1e8c 100644 --- a/src/procedures.c +++ b/src/procedures.c @@ -485,7 +485,7 @@ void handlePlayerAction (PlayerData *player, int action, short x, short y, short } -void spawnMob (uint8_t type, short x, uint8_t y, short z) { +void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health) { for (int i = 0; i < MAX_MOBS; i ++) { // Look for type 0 (unallocated) @@ -496,6 +496,7 @@ void spawnMob (uint8_t type, short x, uint8_t y, short z) { mob_data[i].x = x; mob_data[i].y = y; mob_data[i].z = z; + mob_data[i].data = health & 31; // Broadcast entity creation to all players for (int j = 0; j < MAX_PLAYERS; j ++) { @@ -515,6 +516,56 @@ void spawnMob (uint8_t type, short x, uint8_t y, short z) { } +void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t damage) { + + // Retrieve player data if applicable + PlayerData *player; + if (attacker_id < 65536 && attacker_id != -1) { + if (getPlayerData(attacker_id, &player)) return; + } else if (entity_id < 65536) { + if (getPlayerData(entity_id, &player)) return; + } + + if (attacker_id < 65536 && attacker_id != -1) { // Attacker is a player + // Scale damage based on held item + uint16_t held_item = player->inventory_items[player->hotbar]; + if (held_item == I_wooden_sword) damage *= 4; + else if (held_item == I_golden_sword) damage *= 4; + else if (held_item == I_stone_sword) damage *= 5; + else if (held_item == I_iron_sword) damage *= 6; + else if (held_item == I_diamond_sword) damage *= 7; + else if (held_item == I_netherite_sword) damage *= 8; + } + + // Whether this attack caused the target entity to die + uint8_t entity_died = false; + + if (entity_id < 65536) { // The attacked entity is a player + if (player->health <= damage) { + player->health = 0; + entity_died = true; + } else player->health -= damage; + // Update health on the client + sc_setHealth(player->client_fd, player->health, player->hunger); + } else { // The attacked entity is a mob + MobData *mob = &mob_data[entity_id - 65536]; + uint8_t mob_health = mob->data & 31; + if (mob_health <= damage) { + mob->data -= mob_health; + entity_died = true; + } else mob->data -= damage; + } + + // Broadcast damage event to all players + for (int i = 0; i < MAX_PLAYERS; i ++) { + int client_fd = player_data[i].client_fd; + if (client_fd == -1) continue; + sc_damageEvent(client_fd, entity_id, damage_type); + if (entity_died) sc_entityEvent(client_fd, entity_id, 3); + } + +} + // Simulates events scheduled for regular intervals // Takes the time since the last tick in microseconds as the only arguemnt void handleServerTick (int64_t time_since_last_tick) { @@ -540,6 +591,19 @@ void handleServerTick (int64_t time_since_last_tick) { for (int i = 0; i < MAX_MOBS; i ++) { if (mob_data[i].type == 0) continue; + // Mob has died, deallocate it + if ((mob_data[i].data & 31) == 0) { + mob_data[i].type = 0; + for (int j = 0; j < MAX_PLAYERS; j ++) { + if (player_data[j].client_fd == -1) continue; + // Spawn death smoke particles + sc_entityEvent(player_data[j].client_fd, 65536 + i, 60); + // Remove the entity from the client + sc_removeEntity(player_data[j].client_fd, 65536 + i); + } + continue; + } + uint8_t passive = ( mob_data[i].type == 25 || // Chicken mob_data[i].type == 28 || // Cow @@ -554,7 +618,7 @@ void handleServerTick (int64_t time_since_last_tick) { // Find the player closest to this mob PlayerData* closest_player; - uint32_t closest_dist = 65535; + uint32_t closest_dist = 65536; for (int j = 0; j < MAX_PLAYERS; j ++) { if (player_data[j].client_fd == -1) continue; uint16_t curr_dist = ( @@ -594,10 +658,7 @@ 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) { - if (closest_player->health < 6) closest_player->health = 0; - else closest_player->health -= 6; - sc_damageEvent(closest_player->client_fd, closest_player->client_fd, D_generic); - sc_setHealth(closest_player->client_fd, closest_player->health, closest_player->hunger); + hurtEntity(closest_player->client_fd, 65536 + i, D_generic, 6); continue; }