mirror of
https://github.com/p2r3/bareiron.git
synced 2025-10-01 23:25:09 +02:00
implement basic support for mobs
This commit is contained in:
@@ -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
|
||||
// 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]));
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -37,3 +37,5 @@ BlockChange block_changes[20000];
|
||||
int block_changes_count = 0;
|
||||
|
||||
PlayerData player_data[MAX_PLAYERS];
|
||||
|
||||
MobData mob_data[MAX_MOBS];
|
||||
|
55
src/main.c
55
src/main.c
@@ -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
|
||||
|
126
src/tools.c
126
src/tools.c
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user