1
0
mirror of https://github.com/p2r3/bareiron.git synced 2025-10-02 07:35:08 +02:00

implement basic support for mobs

This commit is contained in:
p2r3
2025-08-22 02:29:59 +03:00
parent 5a0f8fd376
commit ca9edce43a
9 changed files with 205 additions and 41 deletions

View File

@@ -308,9 +308,13 @@ async function convert () {
}
if (registry.endsWith("variant")) {
// The mob "variants" only require one valid variant to be accepted
// Send the shortest one to save memory
const shortest = registries[registry].sort((a, b) => a.length - b.length)[0];
registryBuffers.push(serializeRegistry(registry, [shortest]));
// Send "temperate" if available, otherwise shortest string to save memory
if (registries[registry].includes("temperate")) {
registryBuffers.push(serializeRegistry(registry, ["temperate"]));
} else {
const shortest = registries[registry].sort((a, b) => a.length - b.length)[0];
registryBuffers.push(serializeRegistry(registry, [shortest]));
}
} else {
registryBuffers.push(serializeRegistry(registry, registries[registry]));
}

View File

@@ -15,10 +15,19 @@
#define true 1
#define false 0
// TCP port, Minecraft's default is 25565
#define PORT 25565
// How many players to keep in memory, NOT the amount of concurrent players
// Even when offline, players who have logged on before take up a slot
#define MAX_PLAYERS 16
// How many mobs to allocate memory for
#define MAX_MOBS (MAX_PLAYERS)
// Server game mode: 0 - survival; 1 - creative; 2 - adventure; 3 - spectator
#define GAMEMODE 0
// Max render distance, determines how many chunks to send
#define VIEW_DISTANCE 2
// Time between server ticks in microseconds (default = 2s)
#define TIME_BETWEEN_TICKS 2000000
// How many visited chunks to "remember"
// The server will not re-send chunks that the player has recently been in
#define VISITED_HISTORY 4
@@ -77,6 +86,13 @@ typedef struct {
uint8_t craft_count[9];
} PlayerData;
typedef struct {
uint8_t type;
short x;
uint8_t y;
short z;
} MobData;
#pragma pack(pop)
extern BlockChange block_changes[20000];
@@ -84,4 +100,6 @@ extern int block_changes_count;
extern PlayerData player_data[MAX_PLAYERS];
extern MobData mob_data[MAX_MOBS];
#endif

View File

@@ -34,7 +34,7 @@ int sc_blockUpdate (int client_fd, int64_t x, int64_t y, int64_t z, uint8_t bloc
int sc_openScreen (int client_fd, uint8_t window, const char *title, uint16_t length);
int sc_acknowledgeBlockChange (int client_fd, int sequence);
int sc_playerInfoUpdateAddPlayer (int client_fd, PlayerData player);
int sc_spawnEntity (int client_fd, int id, uint8_t *uuid, int type, double x, double y, double z, double yaw, double pitch);
int sc_spawnEntity (int client_fd, int id, uint8_t *uuid, int type, double x, double y, double z, uint8_t yaw, uint8_t pitch);
int sc_spawnEntityPlayer (int client_fd, PlayerData player);
int sc_teleportEntity (int client_fd, int id, double x, double y, double z, float yaw, float pitch);
int sc_setHeadRotation (int client_fd, int id, uint8_t yaw);

View File

@@ -10,7 +10,7 @@ inline int mod_abs (int a, int b) {
return ((a % b) + b) % b;
}
inline int div_floor (int a, int b) {
return a < 0 ? (a - b) / b : a / b;
return a % b < 0 ? (a - b) / b : a / b;
}
ssize_t recv_all (int client_fd, void *buf, size_t n, uint8_t require_first);
@@ -36,6 +36,12 @@ void readString (int client_fd);
uint32_t fast_rand ();
uint64_t splitmix64 (uint64_t state);
#ifdef ESP_PLATFORM
#define get_program_time esp_timer_get_time
#else
int64_t get_program_time ();
#endif
extern int client_states[MAX_PLAYERS * 2];
void setClientState (int client_fd, int new_state);
@@ -61,4 +67,7 @@ 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 handleServerTick (int64_t time_since_last_tick);
#endif

View File

@@ -23,7 +23,8 @@ typedef struct {
uint32_t getChunkHash (short x, short z);
uint8_t getChunkBiome (short x, short z);
int getHeightAt (int rx, int rz, int _x, int _z, uint32_t chunk_hash, uint8_t biome);
int getHeightAtFromHash (int rx, int rz, int _x, int _z, uint32_t chunk_hash, uint8_t biome);
int getHeightAt (int x, int z);
uint8_t getTerrainAt (int x, int y, int z, ChunkAnchor anchor);
uint8_t getBlockAt (int x, int y, int z);

View File

@@ -37,3 +37,5 @@ BlockChange block_changes[20000];
int block_changes_count = 0;
PlayerData player_data[MAX_PLAYERS];
MobData mob_data[MAX_MOBS];

View File

@@ -99,6 +99,17 @@ void handlePacket (int client_fd, int length, int packet_id) {
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;
@@ -255,6 +266,16 @@ void handlePacket (int client_fd, int length, int packet_id) {
clock_t start, end;
start = clock();
uint32_t r = fast_rand();
if ((r & 3) == 0) {
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;
if (getBlockAt(mob_x, mob_y, mob_z) == B_air) {
spawnMob(95, mob_x, mob_y, mob_z);
}
}
while (dx != 0) {
sc_chunkDataAndUpdateLight(client_fd, _x + dx * VIEW_DISTANCE, _z);
count ++;
@@ -387,11 +408,8 @@ int main () {
int flags = fcntl(server_fd, F_GETFL, 0);
fcntl(server_fd, F_SETFL, flags | O_NONBLOCK);
// Track client keep-alives
struct timespec time_now;
struct timespec keepalive_last;
clock_gettime(CLOCK_REALTIME, &time_now);
clock_gettime(CLOCK_REALTIME, &keepalive_last);
// 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
@@ -421,28 +439,11 @@ int main () {
if (client_index == MAX_PLAYERS) client_index = 0;
if (clients[client_index] == -1) continue;
// Handle infrequent periodic events every few seconds
clock_gettime(CLOCK_REALTIME, &time_now);
time_t seconds_since_update = time_now.tv_sec - keepalive_last.tv_sec;
if (seconds_since_update > 10) {
// Send Keep Alive and Update Time packets to all in-game clients
world_time += 20 * seconds_since_update;
for (int i = 0; i < MAX_PLAYERS; i ++) {
if (clients[i] == -1) continue;
if (getClientState(clients[i]) != STATE_PLAY) continue;
sc_keepAlive(clients[i]);
sc_updateTime(clients[i], world_time);
}
// Reset keep-alive timer
clock_gettime(CLOCK_REALTIME, &keepalive_last);
/**
* If the RNG seed ever hits 0, it'll never generate anything
* else. This is because the fast_rand function uses a simple
* XORshift. This isn't a common concern, so we only check for
* this periodically. If it does become zero, we reset it to
* the world seed as a good-enough fallback.
*/
if (rng_seed == 0) rng_seed = world_seed;
// 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

View File

@@ -6,9 +6,14 @@
#ifdef ESP_PLATFORM
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "esp_timer.h"
#else
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#ifndef CLOCK_MONOTONIC
#define CLOCK_MONOTONIC 1
#endif
#endif
#include "globals.h"
@@ -184,6 +189,14 @@ uint64_t splitmix64 (uint64_t state) {
return z ^ (z >> 31);
}
#ifndef ESP_PLATFORM
int64_t get_program_time () {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL;
}
#endif
int client_states[MAX_PLAYERS * 2];
void setClientState (int client_fd, int new_state) {
@@ -379,11 +392,7 @@ void spawnPlayer (PlayerData *player) {
if (player->y == -32767) { // Is this a new player?
// Determine spawning Y coordinate based on terrain height
int _x = 8 / CHUNK_SIZE;
int _z = 8 / CHUNK_SIZE;
int rx = 8 % CHUNK_SIZE;
int rz = 8 % CHUNK_SIZE;
spawn_y = getHeightAt(rx, rz, _x, _z, getChunkHash(_x, _z), getChunkBiome(_x, _z)) + 1;
spawn_y = getHeightAt(8, 8) + 1;
} else { // Not a new player
// Calculate spawn position from player data
spawn_x = player->x > 0 ? (float)player->x + 0.5 : (float)player->x - 0.5;
@@ -663,3 +672,110 @@ void handlePlayerAction (PlayerData *player, int action, short x, short y, short
}
}
void spawnMob (uint8_t type, short x, uint8_t y, short z) {
for (int i = 0; i < MAX_MOBS; i ++) {
// Look for type 0 (unallocated)
if (mob_data[i].type != 0) continue;
// Assign it the input parameters
mob_data[i].type = type;
mob_data[i].x = x;
mob_data[i].y = y;
mob_data[i].z = z;
// Broadcast entity creation to all players
for (int j = 0; j < MAX_PLAYERS; j ++) {
if (player_data[j].client_fd == -1) continue;
sc_spawnEntity(
player_data[j].client_fd,
65536 + i, // Try to avoid conflict with client file descriptors
recv_buffer, // The UUID doesn't matter, feed it garbage
type, x, y, z,
// Face opposite of the player, as if looking at them when spawning
(player_data[j].yaw + 127) & 255, 0
);
}
break;
}
}
// 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) {
// Send Keep Alive and Update Time packets to all in-game clients
world_time += 20 * time_since_last_tick / 1000000;
for (int i = 0; i < MAX_PLAYERS; i ++) {
if (player_data[i].client_fd == -1) continue;
sc_keepAlive(player_data[i].client_fd);
sc_updateTime(player_data[i].client_fd, world_time);
}
/**
* If the RNG seed ever hits 0, it'll never generate anything
* else. This is because the fast_rand function uses a simple
* XORshift. This isn't a common concern, so we only check for
* this periodically. If it does become zero, we reset it to
* the world seed as a good-enough fallback.
*/
if (rng_seed == 0) rng_seed = world_seed;
// Tick mob behavior
for (int i = 0; i < MAX_MOBS; i ++) {
if (mob_data[i].type == 0) continue;
uint32_t r = fast_rand();
// Skip 50% of ticks randomly
if (r & 1) continue;
// Move by one block on the X or Z axis
// Yaw is set to face in the direction of motion
short new_x = mob_data[i].x, new_z = mob_data[i].z;
uint8_t yaw;
if ((r >> 2) & 1) {
if ((r >> 1) & 1) { new_x += 1; yaw = 192; }
else { new_x -= 1; yaw = 64; }
} else {
if ((r >> 1) & 1) { new_z += 1; yaw = 0; }
else { new_z -= 1; yaw = 128; }
}
// Vary the yaw angle to look just a little less robotic
yaw += ((r >> 6) & 15) - 8;
// Check if the block we're moving into is passable:
// if yes, and the block below is solid, keep the same Y level;
// if yes, but the block below isn't solid, drop down one block;
// if not, go up by up to one block;
// if going up isn't possible, skip this iteration.
uint8_t new_y = mob_data[i].y;
uint8_t block = getBlockAt(new_x, new_y, new_z);
if (block != B_air) {
if (getBlockAt(new_x, new_y + 1, new_z) == B_air) new_y += 1;
else continue;
} else if (getBlockAt(new_x, new_y - 1, new_z) == B_air) new_y -= 1;
// Store new mob position
mob_data[i].x = new_x;
mob_data[i].y = new_y;
mob_data[i].z = new_z;
// Broadcast relevant entity movement packets
for (int j = 0; j < MAX_PLAYERS; j ++) {
if (player_data[j].client_fd == -1) continue;
int entity_id = 65536 + i;
sc_teleportEntity (
player_data[j].client_fd, entity_id,
(double)new_x + 0.5, new_y, (double)new_z + 0.5,
yaw * 360 / 256, 0
);
sc_setHeadRotation(player_data[j].client_fd, entity_id, yaw);
}
}
}

View File

@@ -119,7 +119,7 @@ int interpolate (int a, int b, int c, int d, int x, int z) {
return (top * (CHUNK_SIZE - z) + bottom * z) / (CHUNK_SIZE * CHUNK_SIZE);
}
int getHeightAt (int rx, int rz, int _x, int _z, uint32_t chunk_hash, uint8_t biome) {
int getHeightAtFromHash (int rx, int rz, int _x, int _z, uint32_t chunk_hash, uint8_t biome) {
if (rx == 0 && rz == 0) {
int height = getCornerHeight(chunk_hash, biome);
@@ -135,6 +135,19 @@ int getHeightAt (int rx, int rz, int _x, int _z, uint32_t chunk_hash, uint8_t bi
}
int getHeightAt (int x, int z) {
int _x = div_floor(x, CHUNK_SIZE);
int _z = div_floor(z, CHUNK_SIZE);
int rx = mod_abs(x, CHUNK_SIZE);
int rz = mod_abs(z, CHUNK_SIZE);
uint32_t chunk_hash = getChunkHash(_x, _z);
uint8_t biome = getChunkBiome(_x, _z);
return getHeightAtFromHash(rx, rz, _x, _z, chunk_hash, biome);
}
uint8_t getTerrainAt (int x, int y, int z, ChunkAnchor anchor) {
if (y > 80) return B_air;
@@ -144,7 +157,7 @@ uint8_t getTerrainAt (int x, int y, int z, ChunkAnchor anchor) {
if (rx < 0) rx += CHUNK_SIZE;
if (rz < 0) rz += CHUNK_SIZE;
int height = getHeightAt(rx, rz, anchor.x, anchor.z, anchor.hash, anchor.biome);
int height = getHeightAtFromHash(rx, rz, anchor.x, anchor.z, anchor.hash, anchor.biome);
if (y < 64 || y < height) goto skip_feature;
@@ -170,7 +183,7 @@ uint8_t getTerrainAt (int x, int y, int z, ChunkAnchor anchor) {
switch (anchor.biome) {
case W_plains: { // Generate trees in the plains biome
uint8_t feature_y = getHeightAt(
uint8_t feature_y = getHeightAtFromHash(
feature_x < 0 ? feature_x % CHUNK_SIZE + CHUNK_SIZE : feature_x % CHUNK_SIZE,
feature_z < 0 ? feature_z % CHUNK_SIZE + CHUNK_SIZE : feature_z % CHUNK_SIZE,
anchor.x, anchor.z, anchor.hash, anchor.biome