Files
barecopper/src/main.c

570 lines
17 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#ifndef CLOCK_REALTIME
#define CLOCK_REALTIME 0
#endif
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_task_wdt.h"
#include "esp_timer.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#else
#include <arpa/inet.h>
#include <unistd.h>
#endif
#include "globals.h"
#include "tools.h"
#include "varnum.h"
#include "packets.h"
#include "worldgen.h"
#include "registries.h"
#include "procedures.h"
void handlePacket (int client_fd, int length, int packet_id) {
int state = getClientState(client_fd);
switch (packet_id) {
case 0x00:
if (state == STATE_NONE) {
if (cs_handshake(client_fd)) break;
return;
} else if (state == STATE_LOGIN) {
uint8_t uuid[16];
char name[16];
if (cs_loginStart(client_fd, uuid, name)) break;
if (reservePlayerData(client_fd, uuid, name)) break;
if (sc_loginSuccess(client_fd, uuid, name)) break;
return;
} else if (state == STATE_CONFIGURATION) {
if (cs_clientInformation(client_fd)) break;
if (sc_knownPacks(client_fd)) break;
if (sc_registries(client_fd)) break;
return;
}
break;
case 0x02:
if (state == STATE_CONFIGURATION) {
if (cs_pluginMessage(client_fd)) break;
return;
}
break;
case 0x03:
if (state == STATE_LOGIN) {
printf("Client Acknowledged Login\n\n");
setClientState(client_fd, STATE_CONFIGURATION);
return;
} else if (state == STATE_CONFIGURATION) {
printf("Client Acknowledged Configuration\n\n");
// Enter client into "play" state
setClientState(client_fd, STATE_PLAY);
sc_loginPlay(client_fd);
PlayerData *player;
if (getPlayerData(client_fd, &player)) break;
// Send full client spawn sequence
spawnPlayer(player);
// Prepare join message for broadcast
uint8_t player_name_len = strlen(player->name);
char join_message[16 + player_name_len];
strcpy(join_message, player->name);
strcpy(join_message + player_name_len, " joined the game");
// Register all existing players and spawn their entities, and broadcast
// information about the joining player to all existing players.
for (int i = 0; i < MAX_PLAYERS; i ++) {
if (player_data[i].client_fd == -1) continue;
sc_playerInfoUpdateAddPlayer(client_fd, player_data[i]);
sc_systemChat(player_data[i].client_fd, join_message, 16 + player_name_len);
if (player_data[i].client_fd == client_fd) continue;
sc_playerInfoUpdateAddPlayer(player_data[i].client_fd, *player);
sc_spawnEntityPlayer(client_fd, player_data[i]);
sc_spawnEntityPlayer(player_data[i].client_fd, *player);
}
// Send information about all other entities (mobs)
// For more info on the arguments, see the spawnMob function
for (int i = 0; i < MAX_MOBS; i ++) {
if (mob_data[i].type == 0) continue;
sc_spawnEntity(
client_fd, 65536 + i, recv_buffer,
mob_data[i].type, mob_data[i].x, mob_data[i].y, mob_data[i].z,
0, 0
);
}
return;
}
break;
case 0x07:
if (state == STATE_CONFIGURATION) {
printf("Received Client's Known Packs\n");
printf(" Finishing configuration\n\n");
sc_finishConfiguration(client_fd);
}
break;
case 0x08:
if (state == STATE_PLAY) {
cs_chat(client_fd);
return;
}
break;
case 0x0B:
if (state == STATE_PLAY) {
if (cs_clientStatus(client_fd)) break;
return;
}
break;
case 0x0C:
if (state == STATE_PLAY) {
// client tick
return;
}
break;
case 0x11:
if (state == STATE_PLAY) {
cs_clickContainer(client_fd);
return;
}
break;
case 0x12:
if (state == STATE_PLAY) {
cs_closeContainer(client_fd);
return;
}
break;
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:
case 0x1F:
case 0x20:
if (state == STATE_PLAY) {
double x, y, z;
float yaw, pitch;
uint8_t on_ground;
// Read player position (and rotation)
if (packet_id == 0x1D) cs_setPlayerPosition(client_fd, &x, &y, &z, &on_ground);
else if (packet_id == 0x1F) cs_setPlayerRotation (client_fd, &yaw, &pitch, &on_ground);
else if (packet_id == 0x20) cs_setPlayerMovementFlags (client_fd, &on_ground);
else cs_setPlayerPositionAndRotation(client_fd, &x, &y, &z, &yaw, &pitch, &on_ground);
PlayerData *player;
if (getPlayerData(client_fd, &player)) break;
// Handle fall damage
if (on_ground) {
int8_t damage = player->grounded_y - player->y - 3;
if (damage > 0 && getBlockAt(player->x, player->y, player->z) != B_water) {
hurtEntity(client_fd, -1, D_fall, damage);
}
player->grounded_y = player->y;
}
// Don't continue if all we got were flags
if (packet_id == 0x20) return;
// Update rotation in player data (if applicable)
if (packet_id != 0x1D) {
player->yaw = ((short)(yaw + 540) % 360 - 180) * 127 / 180;
player->pitch = pitch / 90.0f * 127.0f;
}
// Broadcast player position to other players
#ifdef SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT
// If applicable, broadcast only every client_count-th movement update
uint8_t should_broadcast = false;
if (++player->packets_since_update >= client_count) {
should_broadcast = true;
player->packets_since_update = 0;
}
#else
#define should_broadcast (client_count > 0)
#endif
if (should_broadcast) {
// If the packet had no rotation data, calculate it from player data
if (packet_id == 0x1D) {
yaw = player->yaw * 180 / 127;
pitch = player->pitch * 90 / 127;
}
// Send current position data to all connected players
for (int i = 0; i < MAX_PLAYERS; i ++) {
if (player_data[i].client_fd == -1) continue;
if (player_data[i].client_fd == client_fd) continue;
if (packet_id == 0x1F) {
sc_updateEntityRotation(player_data[i].client_fd, client_fd, player->yaw, player->pitch);
} else {
sc_teleportEntity(player_data[i].client_fd, client_fd, x, y, z, yaw, pitch);
}
sc_setHeadRotation(player_data[i].client_fd, client_fd, player->yaw);
}
}
// Don't continue if all we got was rotation data
if (packet_id == 0x1F) return;
// Cast the values to short to get integer position
short cx = x, cy = y, cz = z;
// Determine the player's chunk coordinates
short _x = (cx < 0 ? cx - 16 : cx) / 16, _z = (cz < 0 ? cz - 16 : cz) / 16;
// Calculate distance between previous and current chunk coordinates
short dx = _x - (player->x < 0 ? player->x - 16 : player->x) / 16;
short dz = _z - (player->z < 0 ? player->z - 16 : player->z) / 16;
// Update position in player data
player->x = cx;
player->y = cy;
player->z = cz;
// Exit early if no chunk borders were crossed
if (dx == 0 && dz == 0) return;
// Check if the player has recently been in this chunk
int found = false;
for (int i = 0; i < VISITED_HISTORY; i ++) {
if (player->visited_x[i] == _x && player->visited_z[i] == _z) {
found = true;
break;
}
}
if (found) return;
// Update player's recently visited chunks
for (int i = 0; i < VISITED_HISTORY - 1; i ++) {
player->visited_x[i] = player->visited_x[i + 1];
player->visited_z[i] = player->visited_z[i + 1];
}
player->visited_x[VISITED_HISTORY - 1] = _x;
player->visited_z[VISITED_HISTORY - 1] = _z;
printf("sending new chunks (%d, %d)\n", _x, _z);
sc_setCenterChunk(client_fd, _x, _z);
int count = 0;
clock_t start, end;
start = clock();
uint32_t r = fast_rand();
// One in every 4 new chunks spawns a mob
if ((r & 3) == 0) {
// The mob is placed in the middle of the new chunk row,
// at a random position within the chunk
short mob_x = (_x + dx * VIEW_DISTANCE) * 16 + ((r >> 4) & 15);
short mob_z = (_z + dz * VIEW_DISTANCE) * 16 + ((r >> 8) & 15);
uint8_t mob_y = getHeightAt(mob_x, mob_z) + 1;
// Ensure that there's space to spawn the mob
if (
getBlockAt(mob_x, mob_y, mob_z) == B_air &&
getBlockAt(mob_x, mob_y + 1, mob_z) == B_air
) {
// Spawn passive mobs during the day, hostiles during the night
if (world_time < 13000) {
uint32_t mob_choice = (r >> 12) & 3;
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, 20); // Zombie
}
}
}
while (dx != 0) {
sc_chunkDataAndUpdateLight(client_fd, _x + dx * VIEW_DISTANCE, _z);
count ++;
for (int i = 1; i <= VIEW_DISTANCE; i ++) {
sc_chunkDataAndUpdateLight(client_fd, _x + dx * VIEW_DISTANCE, _z - i);
sc_chunkDataAndUpdateLight(client_fd, _x + dx * VIEW_DISTANCE, _z + i);
count += 2;
}
dx += dx > 0 ? -1 : 1;
}
while (dz != 0) {
sc_chunkDataAndUpdateLight(client_fd, _x, _z + dz * VIEW_DISTANCE);
count ++;
for (int i = 1; i <= VIEW_DISTANCE; i ++) {
sc_chunkDataAndUpdateLight(client_fd, _x - i, _z + dz * VIEW_DISTANCE);
sc_chunkDataAndUpdateLight(client_fd, _x + i, _z + dz * VIEW_DISTANCE);
count += 2;
}
dz += dz > 0 ? -1 : 1;
}
end = clock();
double total_ms = (double)(end - start) / CLOCKS_PER_SEC * 1000;
printf("generated %d chunks in %.0f ms (%.2f ms per chunk)\n", count, total_ms, total_ms / (double)count);
return;
}
break;
case 0x34:
if (state == STATE_PLAY) {
cs_setHeldItem(client_fd);
return;
}
break;
case 0x28:
if (state == STATE_PLAY) {
cs_playerAction(client_fd);
return;
}
break;
case 0x3F:
if (state == STATE_PLAY) {
cs_useItemOn(client_fd);
return;
}
break;
default: break;
}
// if (packet_id < 16) printf("Unknown/bad packet: 0x0%X, length: %d, state: %d\n\n", packet_id, length, state);
// else printf("Unknown/bad packet: 0x%X, length: %d, state: %d\n\n", packet_id, length, state);
recv_count = recv(client_fd, recv_buffer, length, 0);
}
void disconnectClient (int *client_fd, int cause) {
if (*client_fd == -1) return;
client_count --;
setClientState(*client_fd, STATE_NONE);
clearPlayerFD(*client_fd);
close(*client_fd);
*client_fd = -1;
printf("Disconnected client %d, cause: %d, errno: %d\n\n", *client_fd, cause, errno);
}
int main () {
// Hash the seeds to ensure they're random enough
world_seed = splitmix64(world_seed);
printf("World seed: ");
for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((world_seed >> (8 * i)) & 255));
rng_seed = splitmix64(rng_seed);
printf("\nRNG seed: ");
for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((rng_seed >> (8 * i)) & 255));
printf("\n\n");
for (int i = 0; i < sizeof(block_changes) / sizeof(BlockChange); i ++) {
block_changes[i].block = 0xFF;
}
int server_fd, opt = 1;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
int clients[MAX_PLAYERS], client_index = 0;
for (int i = 0; i < MAX_PLAYERS; i ++) {
clients[i] = -1;
client_states[i * 2] = -1;
player_data[i].client_fd = -1;
}
// Create socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket failed");
exit(EXIT_FAILURE);
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("socket options failed");
exit(EXIT_FAILURE);
}
// Bind socket to IP/port
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server listening on port %d...\n", PORT);
// Set non-blocking socket flag
int flags = fcntl(server_fd, F_GETFL, 0);
fcntl(server_fd, F_SETFL, flags | O_NONBLOCK);
// Track time of last server tick
int64_t last_tick_time = get_program_time();
/**
* Cycles through all connected clients, handling one packet at a time
* from each player. With every iteration, attempts to accept a new
* client connection.
*/
while (true) {
// Check if it's time to yield to the idle task
task_yield();
// Attempt to accept a new connection
for (int i = 0; i < MAX_PLAYERS; i ++) {
if (clients[i] != -1) continue;
clients[i] = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
// If the accept was successful, make the client non-blocking too
if (clients[i] != -1) {
printf("New client, fd: %d\n", clients[i]);
int flags = fcntl(clients[i], F_GETFL, 0);
fcntl(clients[i], F_SETFL, flags | O_NONBLOCK);
client_count ++;
}
break;
}
// Look for valid connected clients
client_index ++;
if (client_index == MAX_PLAYERS) client_index = 0;
if (clients[client_index] == -1) continue;
// Handle periodic events (server ticks)
int64_t time_since_last_tick = get_program_time() - last_tick_time;
if (time_since_last_tick > TIME_BETWEEN_TICKS) {
handleServerTick(time_since_last_tick);
last_tick_time = get_program_time();
}
// Handle this individual client
int client_fd = clients[client_index];
// Check if at least 2 bytes are available for reading
ssize_t recv_count = recv(client_fd, &recv_buffer, 2, MSG_PEEK);
if (recv_count < 2) {
if (recv_count == 0 || (recv_count < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
disconnectClient(&clients[client_index], 1);
}
continue;
}
// Read packet length
int length = readVarInt(client_fd);
if (length == VARNUM_ERROR) {
disconnectClient(&clients[client_index], 2);
continue;
}
// Read packet ID
int packet_id = readVarInt(client_fd);
if (packet_id == VARNUM_ERROR) {
disconnectClient(&clients[client_index], 3);
continue;
}
// Handle packet data
handlePacket(client_fd, length - sizeVarInt(packet_id), packet_id);
if (recv_count == 0 || (recv_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK)) {
disconnectClient(&clients[client_index], 4);
continue;
}
}
close(server_fd);
printf("Server closed.\n");
}
#ifdef ESP_PLATFORM
void bareiron_main (void *pvParameters) {
main();
vTaskDelete(NULL);
}
static void wifi_event_handler (void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
printf("Got IP, starting server...\n\n");
xTaskCreate(bareiron_main, "bareiron", 4096, NULL, 5, NULL);
}
}
void wifi_init () {
nvs_flash_init();
esp_netif_init();
esp_event_loop_create_default();
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL);
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL);
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
.threshold.authmode = WIFI_AUTH_WPA2_PSK
}
};
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
esp_wifi_start();
}
void app_main () {
esp_timer_early_init();
wifi_init();
}
#endif