Files
CubeGame/src/rcraft.cpp
T
2026-06-13 22:02:49 +03:00

1604 lines
57 KiB
C++

#include "raylib.h"
#include "math.h"
#include "raymath.h"
#include <stdint.h>
#include <stdbool.h>
#include "common.hpp"
#include <stdio.h>
#include "rlgl.h"
#include <unordered_set>
#include <iostream>
#include <stack>
#include <cstring>
#include <algorithm>
#include <vector>
#include <list>
#include <climits>
#include <queue>
#include <thread>
#define MAX_RAY_DISTANCE 8.0f
struct MessageData {
std::string message;
float lifetime;
};
static inline int imax(int a, int b) {
if(a > b) { return a; }
return b;
}
extern std::vector<Item> registry;
void Debug_Write(const std::string &stuff);
Vector2 cameraMovementSmooth;
static float camYaw = 0.0f;
static float camPitch = -45.0f;
static const float acceleration = 48.0f;
static const float drag = 6.0f; // tuned for feel
Camera camera;
bool online = false;
bool flying = false;
unsigned long long worldTime;
static float entUpdateCounter;
int selectedBlockType = 1;
bool hide_ui = false;
bool taking_panorama = false;
int panorama_step;
Music currentMusic;
static float tilNextMusic;
Font fnt;
Texture2D terrain;
Texture2D water;
Texture2D white_pixel;
Entity* ent;
const bool ROTATE_PLAYER_WITH_CAMERA = true;
static Sound wood_place;
static Sound stone_place;
static Sound dirt_place;
static Sound grass_place;
bool tick_entities;
const std::string LocaleGet(std::string loc);
bool InputGetKeyDown(const std::string name);
bool InputGetKeyPressed(const std::string name);
extern std::list<Entity> entities;
std::bitset<WORLD_SIZE_BLOCKS*WORLD_SIZE_BLOCKS/8/8> clouds;
std::queue<Vector3I> tickedBlocks;
struct MeshUnit {
Model model;
int vertices;
};
struct ProfilerPart {
std::string id;
double time;
Color color;
};
std::vector<ProfilerPart> profParts;
static void AddProfilerPart(std::string id, double time, Color color) {
ProfilerPart part = {};
part.id = id;
part.time = time;
part.color = color;
profParts.push_back(part);
}
static Vector3 CameraForwardFromYawPitch(float yaw, float pitch)
{
Vector3 f;
f.x = cosf(pitch) * cosf(yaw);
f.y = sinf(pitch);
f.z = cosf(pitch) * sinf(yaw);
return Vector3Normalize(f);
}
Chunk world[MAX_WORLD_AREA];
static MeshUnit rendering[MAX_WORLD_AREA];
static MeshUnit renderingT[MAX_WORLD_AREA];
GameState state = GAME_STATE_MAINMENU;
bool loaderParam_remeshOnly = false;
int worldCreationProgress = 0;
int worldCreationStep = 0;
static bool IsBlockSolid(int bx, int by, int bz)
{
return GetBlock(world, bx, by, bz) != 0;
}
static bool IsBlockWater(int bx, int by, int bz)
{
return GetBlock(world, bx, by, bz) == BLOCK_WATERS;
}
typedef struct Plane {
float a, b, c, d;
} Plane;
typedef struct Frustum {
Plane planes[6];
} Frustum;
static Frustum ExtractFrustumPlanes(Camera camera, float aspect)
{
Frustum frustum;
// Get current Projection and View matrices from raylib
Matrix proj = MatrixPerspective(camera.fovy * DEG2RAD, aspect, 0.01f, 1000.0f);
Matrix view = MatrixLookAt(camera.position, camera.target, camera.up);
Matrix mat = MatrixMultiply(view, proj); // View-Projection Matrix
// Left Plane
frustum.planes[0] = (Plane){ mat.m3 + mat.m0, mat.m7 + mat.m4, mat.m11 + mat.m8, mat.m15 + mat.m12 };
// Right Plane
frustum.planes[1] = (Plane){ mat.m3 - mat.m0, mat.m7 - mat.m4, mat.m11 - mat.m8, mat.m15 - mat.m12 };
// Bottom Plane
frustum.planes[2] = (Plane){ mat.m3 + mat.m1, mat.m7 + mat.m5, mat.m11 + mat.m9, mat.m15 + mat.m13 };
// Top Plane
frustum.planes[3] = (Plane){ mat.m3 - mat.m1, mat.m7 - mat.m5, mat.m11 - mat.m9, mat.m15 - mat.m13 };
// Near Plane
frustum.planes[4] = (Plane){ mat.m3 + mat.m2, mat.m7 + mat.m6, mat.m11 + mat.m10, mat.m15 + mat.m14 };
// Far Plane
frustum.planes[5] = (Plane){ mat.m3 - mat.m2, mat.m7 - mat.m6, mat.m11 - mat.m10, mat.m15 - mat.m14 };
// Normalize planes
for (int i = 0; i < 6; i++) {
float length = sqrtf(frustum.planes[i].a * frustum.planes[i].a +
frustum.planes[i].b * frustum.planes[i].b +
frustum.planes[i].c * frustum.planes[i].c);
frustum.planes[i].a /= length;
frustum.planes[i].b /= length;
frustum.planes[i].c /= length;
frustum.planes[i].d /= length;
}
return frustum;
}
static bool IsBoxInFrustum(Frustum frustum, BoundingBox box)
{
for (int i = 0; i < 6; i++) {
// Find the positive vertex (farthest along the plane normal)
Vector3 p = box.min;
if (frustum.planes[i].a >= 0) p.x = box.max.x;
if (frustum.planes[i].b >= 0) p.y = box.max.y;
if (frustum.planes[i].c >= 0) p.z = box.max.z;
// Calculate dot product of plane equation
float dot = (frustum.planes[i].a * p.x) +
(frustum.planes[i].b * p.y) +
(frustum.planes[i].c * p.z) +
frustum.planes[i].d;
// If the positive vertex is behind the plane, the box is outside
if (dot < 0) return false;
}
return true;
}
static bool IsChunkInFrustum(Frustum frustum, Chunk chunk, int x, int y) {
float highestBlock = 0;
for (int i = 0; i < CHUNK_SIZE*CHUNK_SIZE; i++)
{
if(highestBlock < chunk.highestBlock[i]) highestBlock = chunk.highestBlock[i];
}
BoundingBox bb = {};
bb.min = (Vector3){x, 0, y};
bb.max = (Vector3){x+CHUNK_SIZE, highestBlock, y+CHUNK_SIZE};
return IsBoxInFrustum(frustum, bb);
}
static void ResolveCollisions(float dt)
{
float minY = player_position.y;
float maxY = player_position.y + PLAYER_HEIGHT;
int minX = floorf(player_position.x - PLAYER_RADIUS);
int maxX = floorf(player_position.x + PLAYER_RADIUS);
int minZ = floorf(player_position.z - PLAYER_RADIUS);
int maxZ = floorf(player_position.z + PLAYER_RADIUS);
int minB = floorf(minY);
int maxB = floorf(maxY);
for (int bx = minX; bx <= maxX; bx++)
{
for (int bz = minZ; bz <= maxZ; bz++)
{
for (int by = minB; by <= maxB; by++)
{
if (!IsBlockSolid(bx, by, bz)) continue;
if(IsBlockWater(bx, by, bz)) {
if(player_velocity.y < -5) {
player_velocity.y = -5;
}
if(player_velocity.y > 5) {
player_velocity.y = 5;
}
continue;
}
player_velocity.x *= fmaxf(0.0f, 1.0f - drag * dt);
player_velocity.z *= fmaxf(0.0f, 1.0f - drag * dt);
float bx0 = (float)bx;
float by0 = (float)by;
float bz0 = (float)bz;
float bx1 = bx0 + 1.0f;
float by1 = by0 + 1.0f;
float bz1 = bz0 + 1.0f;
float px0 = player_position.x - PLAYER_RADIUS;
float py0 = player_position.y;
float pz0 = player_position.z - PLAYER_RADIUS;
float px1 = player_position.x + PLAYER_RADIUS;
float py1 = player_position.y + PLAYER_HEIGHT;
float pz1 = player_position.z + PLAYER_RADIUS;
float ix = fminf(px1, bx1) - fmaxf(px0, bx0);
float iy = fminf(py1, by1) - fmaxf(py0, by0);
float iz = fminf(pz1, bz1) - fmaxf(pz0, bz0);
if (ix > 0 && iy > 0 && iz > 0)
{
// find smallest penetration axis and push out along it
if (ix < iy && ix < iz)
{
// push on X
if (player_position.x < bx0) player_position.x -= ix;
else player_position.x += ix;
player_velocity.x = 0;
}
else if (iy < ix && iy < iz)
{
// push on Y
if (player_position.y < by0) player_position.y -= iy;
else player_position.y += iy;
if (player_velocity.y > 0) player_velocity.y = 0;
else player_velocity.y = 0;
}
else
{
// push on Z
if (player_position.z < bz0) player_position.z -= iz;
else player_position.z += iz;
player_velocity.z = 0;
}
}
}
}
}
}
bool loadedChunks[MAX_WORLD_AREA] = { false };
bool dirtyChunks[MAX_WORLD_AREA] = { false };
void Debug_EndMeasure(const std::string &stuff);
void Debug_EndMeasureLoops(const std::string &stuff, int loops);
static void RemeshChunk(int x, int y) {
int idx = x + y * MAX_WORLD_SIZE;
if(loadedChunks[idx]) {
UnloadModel(renderingT[idx].model);
UnloadModel(rendering[idx].model);
}
Chunk chunk = world[idx];
RebuildOpaqueMask(&chunk);
MeshData renderDataOpq = GenerateChunkMesh(GenerateCRDOpaque(chunk, world), chunk, x, y, 0xFF);
MeshData renderDataTsl = GenerateChunkMesh(GenerateCRDTranslucent(chunk, world), chunk, x, y, 0xFF);
Model model = LoadModelFromMesh(GenChunkMesh(renderDataOpq));
model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = terrain;
MeshUnit munit0;
munit0.model = model;
munit0.vertices += renderDataOpq.vertexCount;
rendering[idx] = munit0;
model = LoadModelFromMesh(GenChunkMesh(renderDataTsl));
model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = terrain;
MeshUnit munit1;
munit1.model = model;
munit1.vertices += renderDataTsl.vertexCount;
renderingT[idx] = munit1;
loadedChunks[idx] = true;
}
static bool RaycastBlock(Vector3 origin, Vector3 dir, float maxDist, Vector3 *outBlock, Vector3 *outPrev)
{
float t = 0.0f;
Vector3 pos = origin;
Vector3 step = Vector3Scale(dir, 0.1f); // step resolution (smaller => more accurate)
Vector3 last = origin;
while (t < maxDist)
{
pos = Vector3Add(origin, Vector3Scale(dir, t));
int bx = floorf(pos.x);
int by = floorf(pos.y);
int bz = floorf(pos.z);
if (bx < 0 || by < 0 || bz < 0 || bx >= WORLD_SIZE_BLOCKS || by >= WORLD_HEIGHT || bz >= WORLD_SIZE_BLOCKS) { t += 0.1f; continue; }
if (IsBlockSolid(bx, by, bz) && !IsBlockWater(bx, by, bz))
{
// hit block at bx,by,bz; last is previous pos (empty)
if (outBlock) *outBlock = (Vector3){ (float)bx, (float)by, (float)bz };
if (outPrev) *outPrev = (Vector3){ (float)floorf(last.x), (float)floorf(last.y), (float)floorf(last.z) };
return true;
}
last = pos;
t += 0.1f;
}
return false;
}
static Entity* RaycastEntity(Vector3 origin, Vector3 dir, float maxDist)
{
float t = 0.0f;
Vector3 pos = origin;
Vector3 step = Vector3Scale(dir, 0.1f);
Vector3 last = origin;
while (t < maxDist)
{
pos = Vector3Add(origin, Vector3Scale(dir, t));
int bx = floorf(pos.x);
int by = floorf(pos.y);
int bz = floorf(pos.z);
if (bx < 0 || by < 0 || bz < 0 || bx >= WORLD_SIZE_BLOCKS || by >= WORLD_HEIGHT || bz >= WORLD_SIZE_BLOCKS) { t += 0.1f; continue; }
for(Entity &ent : entities) {
if(Vector3Distance(pos, ent.position) < 0.5f) {
return &ent;
}
}
last = pos;
t += 0.1f;
}
return nullptr;
}
static inline Vector3 BlockToWorldCenter(int bx, int by, int bz)
{
return (Vector3){ bx , by , bz };
}
static inline void RemeshChunkForBlock(int bx, int by, int bz)
{
int cx = bx / CHUNK_SIZE;
int cz = bz / CHUNK_SIZE;
if (cx >= 0 && cx < MAX_WORLD_SIZE && cz >= 0 && cz < MAX_WORLD_SIZE) RemeshChunk(cx, cz);
}
static void AddNekit() {
Entity temp = NewEntity();
temp.type = 2;
temp.health = 50;
temp.velocity = {0, 0, 0};
temp.position = player_position + (Vector3){0, 10, 0};
AddEntity(temp);
}
ENetPeer *peer;
ENetHost* client;
static std::stack<std::vector<uint8_t>> worldPacketStack;
static int fetchedChunks;
int plrId = -1;
static bool pauseMenuActive;
void _LoadWorld(const char* path) {
Debug_Write("Loading world");
LoadWorld(world, path);
Debug_Write("World loaded");
state = GAME_STATE_LOADING;
worldCreationProgress = 0;
worldCreationStep = 2;
}
void PlayRandomMusic() {
currentMusic = LoadMusicStream(Pathify((std::string("music/calm0")+std::to_string(GetRandomValue(1,3))+std::string(".ogg")).c_str()));
PlayMusicStream(currentMusic);
}
static int _blockPlacedC = 0;
void SetBlockNetwork(Chunk world[], int bx, int by, int bz, int t) {
float pitch = GetRandomValue(-100, 100) / 100.0f * 0.25f + 1.0f;
int sound = B_Stone;
tickedBlocks.push({bx, by, bz});
tickedBlocks.push({bx-1, by, bz});
tickedBlocks.push({bx+1, by, bz});
tickedBlocks.push({bx, by-1, bz});
tickedBlocks.push({bx, by+1, bz});
tickedBlocks.push({bx, by, bz-1});
tickedBlocks.push({bx, by, bz+1});
if(t != 0) {
sound = BLOCKS[t].type;
}
else {
int id = GetBlock(world, bx, by, bz);
BlockDef block = BLOCKS[id];
if(id != 0) {
sound = block.type;
for (int x = 0; x < 4; x++)
{
for (int z = 0; z < 4; z++)
{
for (int y = 0; y < 4; y++)
{
if(GetRandomValue(0, 100) < 25) {
Particle part = NewParticle({bx + x / 4.0f - 0.375f, by + y / 4.0f - 0.375f, bz + z / 4.0f - 0.375f});
part.data[0] = block.textureSide;
if(y == 3) {
part.data[0] = block.textureTop;
}
part.velocity = Vector3({(x <= 1) ? -1.0f : 1.0f, (y <= 1) ? -1.0f : 1.0f, (z <= 1) ? -1.0f : 1.0f}) * 1.5f;
AddParticle(part);
part.lifetime = GetRandomValue(0, 100);
}
}
}
}
}
else {
return;
}
}
switch (sound)
{
case B_Dirt:
MS_PlaySound(dirt_place, pitch, 1, (Vector3){bx, by, bz});
break;
case B_Grass:
MS_PlaySound(grass_place, pitch, 1, (Vector3){bx, by, bz});
break;
case B_Wood:
MS_PlaySound(wood_place, pitch, 1, (Vector3){bx, by, bz});
break;
default:
MS_PlaySound(stone_place, pitch, 1, (Vector3){bx, by, bz});
break;
}
if(online) {
char data[5];
data[0] = NET_ARG_BLOCKDELTA;
data[1] = (uint8_t)bx;
data[2] = (uint8_t)by;
data[3] = (uint8_t)bz;
data[4] = (uint8_t)t;
SendPacketR(peer, data, 5);
}
else {
SetBlock(world, bx, by, bz, t);
int cx = bx / CHUNK_SIZE;
int cz = bz / CHUNK_SIZE;
int lx = bx - cx * CHUNK_SIZE;
int lz = bz - cz * CHUNK_SIZE;
dirtyChunks[GetIndexWorld(cx, cz)] = true;
if (lx == 0) dirtyChunks[GetIndexWorld(cx - 1, cz)] = true;
if (lx == CHUNK_SIZE - 1) dirtyChunks[GetIndexWorld(cx + 1, cz)] = true;
if (lz == 0) dirtyChunks[GetIndexWorld(cx, cz - 1)] = true;
if (lz == CHUNK_SIZE - 1) dirtyChunks[GetIndexWorld(cx, cz + 1)] = true;
}
_blockPlacedC++;
}
static void TickAvailableBlocks() {
if(tickedBlocks.size() > 0) {
Vector3I pos = tickedBlocks.front();
if(CanTick(GetBlock(world, pos.x, pos.y, pos.z))) {
TickBlock(world, pos.x, pos.y, pos.z);
tickedBlocks.pop();
}
else {
tickedBlocks.pop();
TickAvailableBlocks();
}
}
}
static bool serverStarted;
int TryConnect(const char* hostname) {
client = StartClient();
if(client == NULL)
{
fprintf(stderr, "An error occurred while trying to create an ENet client host!\n");
return NET_ERROR_INTERNAL;
}
ENetAddress address;
ENetEvent event;
int s = enet_address_set_host (& address, hostname);
address.port = PORT;
peer = enet_host_connect(client, &address, 1, 0);
if (peer == NULL)
{
fprintf (stderr,
"No available peers for initiating an ENet connection.\n");
return NET_ERROR_NOTFOUND;
}
printf("Found a host\n");
while (!serverStarted)
{
if(!online) serverStarted = true;
}
if (enet_host_service (client, & event, 10000) && event.type == ENET_EVENT_TYPE_CONNECT){
puts ("Connection succeeded.");
char data[1];
data[0] = NET_ARG_REQWORLD;
SendPacketR(peer, data, 1); // Tell the server to send all chunks
online = true;
state = GAME_STATE_LOADING;
return 0;
}
else
{
enet_peer_reset (peer);
return NET_ERROR_FAILED;
}
return -1;
}
std::thread serverThread;
void ServerThread(bool& started) {
StartInternalServer(started);
while(true) {
ServerNetworkUpdate();
}
}
void InitSingleplayer() {
online = true;
serverThread = std::thread(ServerThread, std::ref(serverStarted));
int result = TryConnect("localhost");
if(result == NET_ERROR_FAILED) {
state = GAME_STATE_MAINMENU;
}
}
std::vector<MessageData> messages;
bool survival = false;
bool chatOpen = false;
float oxygen = 100;
float blockBreakProgress;
void TakeDamage(float amount) {
player_health -= amount;
}
int currentlyTickedChunk = 0;
Vector3 highlightBlock = {-1, -1, -1};
static void GameUpdate(Camera camera, float &time) {
if (player_position.y < 0 ||
player_position.x < 0 ||
player_position.z < 0 ||
player_position.z > WORLD_SIZE_BLOCKS ||
player_position.x > WORLD_SIZE_BLOCKS) {
player_position = { MAX_WORLD_SIZE / 2 * CHUNK_SIZE, 128.0f, MAX_WORLD_SIZE / 2 * CHUNK_SIZE };
player_velocity = { 0 };
}
bool highlightValid = false;
float dt = GetFrameTime();
if (dt <= 0) dt = DELTA_SMOOTH;
if (dt > 0.1f) dt = 0.1f;
time += dt;
Vector3 forward = Vector3Normalize({ cosf(camYaw), 0.0f, sinf(camYaw) });
Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, camera.up));
camera.position = (Vector3){ player_position.x - 0.5f, player_position.y + 1.25f, player_position.z - 0.5f};
camera.target = Vector3Add(camera.position, CameraForwardFromYawPitch(camYaw, camPitch));
camera.up = (Vector3){0,1,0};
float dt_faux = dt / 4.0f;
if(IsKeyPressed(KEY_ESCAPE)) {
if(chatOpen) chatOpen = false;
else if (inventoryOpen) CloseInventory();
else {
pauseMenuActive = !pauseMenuActive;
if(!pauseMenuActive) {
DisableCursor();
}
else {
EnableCursor();
}
}
}
/*if(IsKeyPressed(KEY_F2)) {
taking_panorama = true;
panorama_step = -1;
}
if(taking_panorama) {
if(panorama_step == 6) {
taking_panorama = false;
}
else {
camYaw = (90 * panorama_step) * DEG2RAD;
if(panorama_step < 3) {
camPitch = 0;
}
else {
camPitch = 89.99f * DEG2RAD;
if(panorama_step == 4) {
camPitch = -89.99f * DEG2RAD;
}
camYaw = 0;
}
}
}*/
if(InputGetKeyPressed("chat") && !pauseMenuActive) {
chatOpen = !chatOpen;
if(!chatOpen) {
DisableCursor();
}
else {
EnableCursor();
}
}
for (int _ = 0; _ < 4; _++)
{
player_position.x += player_velocity.x * dt_faux;
player_position.y += player_velocity.y * dt_faux;
player_position.z += player_velocity.z * dt_faux;
if(!flying) {
player_velocity.y -= GRAVITY * dt_faux;
}
ResolveCollisions(dt_faux);
}
if(!pauseMenuActive && !chatOpen && !inventoryOpen) {
float speedMod = 1;
if(InputGetKeyPressed("fly")) {
flying = !flying;
}
Vector3 wish = {0,0,0};
Vector2 walkAxis = InputGetWalkAxis();
wish = Vector3Add(Vector3Scale(forward, walkAxis.y),
Vector3Scale(right, walkAxis.x));
float inputMag = Vector3Length((Vector3){wish.x, 0, wish.z});
Vector3 wishDir = (inputMag > 0.0001f) ? Vector3Scale(wish, 1.0f / inputMag)
: (Vector3){0,0,0};
if (flying) {
speedMod = InputGetKeyDown("speed_up") ? 6.0f : 3.0f;
float w_y = 0;
if (InputGetKeyDown("jump")) w_y += 1.0f;
if (InputGetKeyDown("shift")) w_y -= 1.0f;
player_velocity.y = w_y * MOVE_SPEED * speedMod;
}
if (inputMag > 0.0001f) {
player_velocity.x += wishDir.x * acceleration * inputMag * speedMod * dt;
player_velocity.z += wishDir.z * acceleration * inputMag * speedMod * dt;
}
float hSpeed = sqrtf(player_velocity.x*player_velocity.x + player_velocity.z*player_velocity.z);
float maxH = MOVE_SPEED * speedMod;
if (hSpeed > maxH) {
float s = maxH / hSpeed;
player_velocity.x *= s;
player_velocity.z *= s;
}
if(!flying) {
bool onGround = false;
int belowY = floorf(player_position.y - 0.05f);
for (int bx = floorf(player_position.x - PLAYER_RADIUS); bx <= floorf(player_position.x + PLAYER_RADIUS); bx++)
for (int bz = floorf(player_position.z - PLAYER_RADIUS); bz <= floorf(player_position.z + PLAYER_RADIUS); bz++)
if (IsBlockSolid(bx, belowY, bz) && !IsBlockWater(bx, belowY, bz)) onGround = true;
if (InputGetKeyPressed("jump") && onGround) player_velocity.y = JUMP_SPEED;
if (InputGetKeyDown("jump") && IsBlockWater(player_position.x, player_position.y, player_position.z)) player_velocity.y += dt * 30;
}
Vector2 md = InputGetLookAxis();
camYaw += md.x * MOUSE_SENS;
camPitch -= md.y * MOUSE_SENS;
cameraMovementSmooth += md * dt / 8.0f;
cameraMovementSmooth -= cameraMovementSmooth * dt * 16.0f;
if (camPitch < PITCH_MIN && !taking_panorama) camPitch = PITCH_MIN;
if (camPitch > PITCH_MAX && !taking_panorama) camPitch = PITCH_MAX;
Vector3 camForward = CameraForwardFromYawPitch(camYaw, camPitch);
Vector3 rayOrigin = (Vector3){ player_position.x, player_position.y + 1.7f, player_position.z};;
Vector3 hitBlock, prevBlock;
if(IsKeyDown(KEY_N) && !online) {
AddNekit();
}
if(IsKeyPressed(KEY_F10)) {
tick_entities = !tick_entities;
}
if(IsKeyPressed(KEY_B) && !online) {
Entity temp = NewEntity();
temp.type = 3;
temp.position = camera.position;
temp.velocity = (camera.target - camera.position) * 4.0f;
AddEntity(temp);
}
if (RaycastBlock(rayOrigin, Vector3Normalize(camForward), 5, &hitBlock, &prevBlock))
{
if((int)hitBlock.x != (int)highlightBlock.x || (int)hitBlock.y != (int)highlightBlock.y || (int)hitBlock.z != (int)highlightBlock.z)
blockBreakProgress = 0.0f;
highlightBlock = hitBlock;
highlightValid = true;
}
else
{
blockBreakProgress = 0.0f;
highlightValid = false;
}
if (InputGetLMBDown())
{
player_attack_time += dt * 2;
if(player_attack_time > 1) {
player_attack_time = 0;
}
if(highlightValid) {
int bx = (int)highlightBlock.x;
int by = (int)highlightBlock.y;
int bz = (int)highlightBlock.z;
float speed = 1.0f;
int tier = 0;
Item* selected_item = HotbarGetSelected().item;
if(selected_item != nullptr) {
if(selected_item->type == IType_Mining) {
speed += selected_item->tier;
tier = selected_item->tier;
}
}
BlockDef block = BLOCKS[GetBlock(world, bx, by, bz)];
float hardnessMultiplier = block.hardness / 4.0f;
if(player_break_parts_time >= 0.05f) {
Vector3 v = {0, 1, 0};
int side = GetRandomValue(0, 3);
switch (side)
{
case 1:
v = {1, GetRandomValue(0, 15) / 16.0f, GetRandomValue(0, 15) / 16.0f};
break;
case 2:
v = {GetRandomValue(0, 15) / 16.0f, GetRandomValue(0, 15) / 16.0f, 1};
break;
default:
v = {GetRandomValue(0, 15) / 16.0f, 1, GetRandomValue(0, 15) / 16.0f};
break;
}
Particle part = NewParticle({bx + v.x - 0.375f, by + v.y - 0.375f, bz + v.z - 0.375f});
part.data[0] = block.textureSide;
part.velocity = {0};
AddParticle(part);
part.lifetime = GetRandomValue(0, 100);
player_break_parts_time = 0;
}
player_break_parts_time += dt;
if(block.min_tier <= tier)
blockBreakProgress+=dt*speed/hardnessMultiplier;
if(blockBreakProgress > 1.0f) {
HotbarAddItem(&registry[0], GetBlock(world, bx, by, bz));
SetBlockNetwork(world, bx, by, bz, 0);
blockBreakProgress = 0.0f;
}
}
}
else {
player_attack_time += (0.5f - player_attack_time) * dt * 16.0f;
blockBreakProgress = 0.0f;
}
if (InputGetLMB())
{
Entity* ent = RaycastEntity(rayOrigin, Vector3Normalize(camForward), 5);
int tier = 0;
Item* selected_item = HotbarGetSelected().item;
if(selected_item != nullptr) {
if(selected_item->type == IType_Weapon) {
tier = selected_item->tier;
}
}
if(ent != nullptr) {
ent->health -= 5.0f + tier * 5;
ent->damage_flash = 50;
ent->velocity += (camera.target - camera.position) * 50;
}
}
if (InputGetRMB())
{
InventoryItem selected = HotbarGetSelected();
if(selected.item != nullptr) {
std::cout << selected.item->type << std::endl;
switch (selected.item->type)
{
case IType_Edible:
HotbarRemoveSelected();
player_health += 10.0f;
break;
case IType_Block:
if(highlightValid) {
int px, py, pz;
px = (int)prevBlock.x;
py = (int)prevBlock.y;
pz = (int)prevBlock.z;
if (px >= 0 && py >= 0 && pz >= 0 && px < WORLD_SIZE_BLOCKS && py < WORLD_HEIGHT && pz < WORLD_SIZE_BLOCKS)
{
if (!(abs(px - player_position.x + 0.5f) < PLAYER_RADIUS * 2 && abs(pz - player_position.z + 0.5f) < PLAYER_RADIUS * 2 && abs(py - player_position.y) < PLAYER_HEIGHT - 0.2f))
{
SetBlockNetwork(world, px, py, pz, selected.damage);
HotbarRemoveSelected();
player_place_time = 1.0f;
}
}
}
break;
default:
break;
}
}
}
if(player_place_time > 0) {
player_place_time -= dt * 3.0f;
}
if(player_place_time < 0) {
player_place_time = 0;
}
float y = GetMouseWheelMove();
bool changed = abs(y) > 0.5f;
if(y > 0.5f) {
selectedBlockType++;
}
if(y < -0.5f) {
selectedBlockType--;
}
if(selectedBlockType > MAX_BLOCK_ID) {
selectedBlockType = 1;
}
if(selectedBlockType < 1) {
selectedBlockType = MAX_BLOCK_ID;
}
}
int bestIdx = -1;
int bestDist2 = INT_MAX;
int c = 0;
for (bool l : loadedChunks) {
int x, y;
GetXZFromIndex(c, &x, &y);
int dx = x - ((int)(player_position.x) >> 4);
int dy = y - ((int)(player_position.z) >> 4);
int dist2 = dx*dx + dy*dy;
if (dist2 > RENDER_DISTANCE_SQUARED) {
loadedChunks[c] = false;
}
else {
loadedChunks[c] = true;
}
c++;
}
c = 0;
for (bool b : dirtyChunks) {
if(b) {
int x, y;
GetXZFromIndex(c, &x, &y);
int dx = x - ((int)(player_position.x) >> 4);
int dy = y - ((int)(player_position.z) >> 4);
int dist2 = dx*dx + dy*dy;
if (dist2 <= RENDER_DISTANCE_SQUARED && dist2 < bestDist2) {
bestDist2 = dist2;
bestIdx = c;
}
}
c++;
}
double PROFCOUNT = GetTime();
if (bestIdx != -1) {
RebuildOpaqueMask(&world[bestIdx]);
int bx, by;
GetXZFromIndex(bestIdx, &bx, &by);
RemeshChunk(bx, by);
dirtyChunks[bestIdx] = false;
}
AddProfilerPart("rebuild", GetTime()-PROFCOUNT, BLUE);
PROFCOUNT = GetTime();
if(tick_entities) {
entUpdateCounter += dt;
if(entUpdateCounter > TPS_DIV) {
while (entUpdateCounter > 0)
{
UpdateEntities(world, TPS_DIV / 2.0f);
entUpdateCounter-= TPS_DIV;
}
TickAvailableBlocks();
entUpdateCounter = 0.0f;
}
}
AddProfilerPart("entities", GetTime()-PROFCOUNT, MAGENTA);
PROFCOUNT = GetTime();
ParticlesUpdate(dt, world);
AddProfilerPart("particles", GetTime()-PROFCOUNT, ORANGE);
MS_Update(dt, camera);
UpdateMusicStream(currentMusic);
std::stack<std::string> messages_local;
for(MessageData &msg : messages) {
if(msg.lifetime < 10.0f) {
messages_local.push(msg.message);
}
msg.lifetime += dt;
}
UpdateCamera(&camera, CAMERA_CUSTOM);
unsigned int debug_verts_opaque = 0;
unsigned int debug_verts_transparent = 0;
UpdateHotbar();
Frustum frustum = ExtractFrustumPlanes(camera, (float)GetScreenWidth() / (float)GetScreenHeight());
BeginDrawing();
ClearBackground(RAYWHITE);
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), BLUE);
BeginMode3D(camera);
PROFCOUNT = GetTime();
double chunkRenderTime = 0;
for (int x = 0; x < MAX_WORLD_SIZE * MAX_WORLD_SIZE; x++)
{
int _x, _z;
GetXZFromIndex(x, &_x, &_z);
_x *= CHUNK_SIZE;
_z *= CHUNK_SIZE;
if(!loadedChunks[x]) continue;
if(!IsChunkInFrustum(frustum, world[x], _x, _z)) continue;
DrawModel(rendering[x].model, {0, 0, 0}, 1, WHITE);
debug_verts_opaque += rendering[x].vertices;
}
AddProfilerPart("chunk render opq", GetTime()-PROFCOUNT, MAROON);
DrawEntities(dt);
ParticlesDraw();
//Transparent shit
rlDisableDepthMask();
rlBegin(RL_QUADS);
rlSetTexture(white_pixel.id);
const int cloudMapLength = WORLD_SIZE_BLOCKS/8;
PROFCOUNT = GetTime();
for (int x = 0; x < cloudMapLength; x++)
{
for (int z = 0; z < cloudMapLength; z++)
{
rlColor4f(255, 255, 255, 255);
if(clouds.test(x * cloudMapLength + z)) { //TODO: Normal frustum culling
Vector3 A = Vector3Normalize((Vector3){x*8, 70, z*8}-(Vector3){camera.position.x, camera.position.y, camera.position.z});
float dot = Vector3DotProduct(A, camera.target - camera.position);
if(dot > cosf(FOV*DEG2RAD)) {
int ex_x = (imax(-x*8, 0) + imax(x*8-WORLD_SIZE_BLOCKS, 0)) >> 5;
int ex_z = (imax(-z*8, 0) + imax(z*8-WORLD_SIZE_BLOCKS, 0)) >> 5;
rlVertex3f(x*8, 70, z*8);
rlVertex3f(x*8+8, 70, z*8);
rlVertex3f(x*8+8, 70, z*8+8);
rlVertex3f(x*8, 70, z*8+8);
}
}
}
}
rlSetTexture(water.id);
for (int x = -8; x < MAX_WORLD_SIZE+8; x++)
{
for (int z = -8; z < MAX_WORLD_SIZE+8; z++)
{
if((x < 0 || x >= MAX_WORLD_SIZE) || (z < 0 || z >= MAX_WORLD_SIZE)) {
Vector3 A = Vector3Normalize((Vector3){x * CHUNK_SIZE, 32, z * CHUNK_SIZE}-(Vector3){camera.position.x, camera.position.y, camera.position.z});
float dot = Vector3DotProduct(A, camera.target - camera.position);
if(dot > cosf(FOV*DEG2RAD)) {
Vector3 A = Vector3Normalize((Vector3){x, 70, z}-(Vector3){camera.position.x, camera.position.y, camera.position.z});
float dot = Vector3DotProduct(A, camera.target - camera.position);
int ex_x = (imax(-x, 0) + imax(x-MAX_WORLD_SIZE, 0));
int ex_z = (imax(-z, 0) + imax(z-MAX_WORLD_SIZE, 0));
rlColor4ub(255, 255, 255, 255); //(255 >> ex_x) >> ex_z
rlTexCoord2f(0, CHUNK_SIZE);rlVertex3f(x*CHUNK_SIZE - 0.5f, 32 + 0.5f, (z+1)*CHUNK_SIZE - 0.5f);
rlTexCoord2f(CHUNK_SIZE, CHUNK_SIZE);rlVertex3f((x+1)*CHUNK_SIZE - 0.5f, 32 + 0.5f, (z+1)*CHUNK_SIZE - 0.5f);
rlTexCoord2f(CHUNK_SIZE, 0);rlVertex3f((x+1)*CHUNK_SIZE - 0.5f, 32 + 0.5f, z*CHUNK_SIZE - 0.5f);
rlTexCoord2f(0, 0);rlVertex3f(x*CHUNK_SIZE - 0.5f, 32 + 0.5f, z*CHUNK_SIZE - 0.5f);
}
}
}
}
rlEnd();
rlEnableDepthMask();
AddProfilerPart("fancy", GetTime()-PROFCOUNT, BLACK);
PROFCOUNT = GetTime();
std::vector<std::pair<float,int>> distances;
distances.reserve(MAX_WORLD_SIZE * MAX_WORLD_SIZE);
for (int i = 0; i < MAX_WORLD_SIZE * MAX_WORLD_SIZE; ++i) {
if (!loadedChunks[i]) continue;
int cx, cz;
GetXZFromIndex(i, &cx, &cz);
if (!IsChunkInFrustum(frustum, world[i], cx*CHUNK_SIZE, cz*CHUNK_SIZE)) continue;
float wx = cx * CHUNK_SIZE + CHUNK_SIZE * 0.5f;
float wz = cz * CHUNK_SIZE + CHUNK_SIZE * 0.5f;
float dx = wx - camera.position.x;
float dz = wz - camera.position.y;
float dist2 = dx*dx + dz*dz;
distances.emplace_back(dist2, i);
}
std::sort(distances.begin(), distances.end(),
[](auto &a, auto &b){ return a.first > b.first; });
for (auto &p : distances) {
int idx = p.second;
DrawModel(renderingT[idx].model, {0, 0, 0}, 1.0f, WHITE);
debug_verts_transparent += renderingT[idx].vertices;
}
AddProfilerPart("chunk render tsl", GetTime()-PROFCOUNT, RED);
if (highlightValid)
{
Vector3 center = BlockToWorldCenter((int)highlightBlock.x, (int)highlightBlock.y, (int)highlightBlock.z);
DrawCubeWires(center, 1.01f, 1.01f, 1.01f, BLACK);
DrawCube(center, 1.01f, 1.01f, 1.01f, Fade(WHITE, blockBreakProgress * 0.6f));
}
EndMode3D();
Vector3 camPos = camera.position;
Vector3 camTarget = camera.target;
camera.position = {0, 0, 0};
camera.target = {-cameraMovementSmooth.x, -cameraMovementSmooth.y, 1};
BeginMode3D(camera);
rlDisableDepthMask();
rlDisableDepthTest();
DrawSelectedItem();
rlEnableDepthTest();
rlEnableDepthMask();
EndMode3D();
camera.position = camPos;
camera.target = camTarget;
if(IsBlockWater(camera.position.x, camera.position.y+0.5f, camera.position.z)) {
oxygen -= dt * 10.0f;
DrawTexturePro(water, {0, 0, 16, 16}, {0, 0, (float)GetScreenWidth(), (float)GetScreenHeight()}, {0, 0}, 0, WHITE);
}
else {
oxygen += dt * 20.0f;
oxygen = Clamp(oxygen, 0, MAX_HP);
}
if(!hide_ui && !taking_panorama) {
char text[128];
sprintf(text, "%s %s", GAME_NAME, GAME_VERSION);
DrawTextB(text, 20, 20, DEFAULT_FONT_SIZE, WHITE);
sprintf(text, "%.0f FPS", 1 / dt);
DrawTextB(text, 20, 40, DEFAULT_FONT_SIZE, WHITE);
sprintf(text, "Player %d %.1f %.1f %.1f", plrId, player_position.x, player_position.y, player_position.z);
DrawTextB(text, 20, 60, DEFAULT_FONT_SIZE, WHITE);
MemInfo m;
Debug_GetProcessMemory(m);
float residentmem = m.resident / 1024.0f / 1024.0f;
float virtualmem = m.virtualSize / 1024.0f / 1024.0f;
sprintf(text, "Mem %.2fMiB/%.2fMiB", residentmem, virtualmem);
DrawTextB(text, 20, 80, DEFAULT_FONT_SIZE, WHITE);
sprintf(text, "Drawing %d verts", debug_verts_opaque+debug_verts_transparent);
DrawTextB(text, 120, 40, DEFAULT_FONT_SIZE, WHITE);
DrawRectangle(GetScreenWidth()/2-3, GetScreenHeight()/2-3, 6, 6, BLACK);
DrawRectangle(GetScreenWidth()/2-2, GetScreenHeight()/2-2, 4, 4, WHITE);
int idx = 0;
while (messages_local.size() > 0)
{
std::string msg = messages_local.top();
DrawTextB(msg.c_str(), 24, 96 + idx * 24, DEFAULT_FONT_SIZE, WHITE);
idx++;
messages_local.pop();
}
DrawRectangle(4-1, GetScreenHeight()-13, 2*(MAX_HP+2), 12, BLACK);
DrawRectangle(4+1, GetScreenHeight()-11, 2*player_health, 8, RED);
if(oxygen < MAX_HP) {
DrawRectangle(4-1, GetScreenHeight()-13-14, 2*(MAX_HP+2), 12, BLACK);
DrawRectangle(4+1, GetScreenHeight()-11-14, 2*oxygen, 8, BLUE);
}
DrawHotbar();
double totalTime = 0.0;
for (auto &p : profParts) totalTime += p.time;
if (totalTime <= 0.0) return;
float startAngle = -PI/2.0f;
const int segmentsPerSlice = 24;
const float cx = GetScreenWidth()-64;
const float cy = 64;
const float radius = 32;
for (auto &p : profParts) {
float frac = float(p.time / totalTime);
float sweep = frac * 2.0f * PI;
float endAngle = startAngle + sweep;
float angleA = startAngle;
for (int s = 1; s <= segmentsPerSlice; ++s) {
float angleB = startAngle + (s / (float)segmentsPerSlice) * sweep;
float x1 = cx;
float y1 = cy;
float x2 = cx + cosf(angleA) * radius;
float y2 = cy + sinf(angleA) * radius;
float x3 = cx + cosf(angleB) * radius;
float y3 = cy + sinf(angleB) * radius;
DrawTriangle({x3, y3}, {x2, y2}, {x1, y1}, p.color);
angleA = angleB;
}
/*float mid = (startAngle + endAngle) * 0.5f;
float lx = cx + cosf(mid) * (radius * 0.6f);
float ly = cy + sinf(mid) * (radius * 0.6f);
char label[128];
sprintf(label, "%s (%.1f ms)", p.id.c_str(), p.time * 1000.0f);
DrawText(label, lx - 20, ly - 6, 10, WHITE);*/
startAngle = endAngle;
}
int T = 0;
std::sort(profParts.begin(), profParts.end(),
[](auto &a, auto &b){ return a.time > b.time; });
for (auto &p : profParts) {
char label[128];
sprintf(label, "%s (%.1f ms) (%.1f%%)", p.id.c_str(), p.time * 1000.0f, p.time / totalTime*100);
DrawText(label, cx-radius-180+1, cy+T*8-radius+1, 8, BLACK);
DrawText(label, cx-radius-180, cy+T*8-radius, 8, p.color);
T++;
};
profParts.clear();
}
if(pauseMenuActive) {
DrawPauseMenu();
}
if(chatOpen) {
char* msg = DrawChat();
if(msg != nullptr) {
chatOpen = false;
DisableCursor();
char data[1+MAX_MESSAGE_LENGTH];
data[0] = NET_ARG_MESSAGEC;
memcpy(data+1, msg, MAX_MESSAGE_LENGTH);
SendPacketR(peer, data, 1+MAX_MESSAGE_LENGTH);
memset(msg, 0, MAX_MESSAGE_LENGTH);
}
}
EndDrawing();
if(oxygen < 0.1f) {
player_health -= dt * 20.0f;
}
if(taking_panorama) {
if(panorama_step >= 0) {
char name[16];
sprintf(name, "%d.png", panorama_step);
TakeScreenshot(name);
}
panorama_step++;
}
}
static void GameStart() {
const int cloudMapSize = MAX_WORLD_SIZE*CHUNK_SIZE/8;
for (int x = 0; x < cloudMapSize; x++)
{
for (int y = 0; y < cloudMapSize; y++) {
clouds.set(y * cloudMapSize + x, GetCloud(x*8, y*8));
}
}
LoadPlayerData();
//PlayRandomMusic();
char data[1];
data[0] = NET_ARG_REQPLAYERS;
SendPacketR(peer, data, 1);
camera = { 0 };
camera.position = (Vector3){ MAX_WORLD_SIZE / 2 * CHUNK_SIZE, 12.0f, MAX_WORLD_SIZE / 2 * CHUNK_SIZE};
camera.target = (Vector3){camera.position.x, camera.position.y, camera.position.z + 1};
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
camera.fovy = FOV;
camera.projection = CAMERA_PERSPECTIVE;
int center = MAX_WORLD_SIZE / 2 * CHUNK_SIZE;
player_position = (Vector3){ MAX_WORLD_SIZE / 2 * CHUNK_SIZE, 128.0f, MAX_WORLD_SIZE / 2 * CHUNK_SIZE};
player_position.y = world[GetIndexWorld(center / 16, center / 16)].highestBlock[0] + 2;
player_velocity = (Vector3){ 0, 0, 0 };
DisableCursor();
InitHotbar();
SetTargetFPS(-1);
wood_place = LoadSound(Pathify("sound/block_wood.wav"));
stone_place = LoadSound(Pathify("sound/block_stone.wav"));
dirt_place = LoadSound(Pathify("sound/block_dirt.wav"));
grass_place = LoadSound(Pathify("sound/block_grass.wav"));
}
static void CreateNetPlayer(int id) {
Entity temp = NewEntity();
temp.type = 1;
temp.position = player_position;
temp.data[0] = id;
temp.data[1] = 0;
AddEntity(temp);
}
static void RemovePlayerEntity(int id) {
auto match = [&](const Entity ent){
return ent.data[0] == id && ent.type == 1;
};
auto it = std::find_if(entities.begin(), entities.end(), match);
if (it != entities.end()) {
entities.erase(it);
}
}
static void ParseMessagePacket(std::vector<unsigned char> &a_data) {
std::string output;
if(a_data[1] == 255) {
output += std::string("[Server] ");
}
for (int i = 0; i < a_data[2]; ++i) {
output.push_back(static_cast<char>(a_data[3 + i]));
}
MessageData msg;
msg.lifetime = 0;
msg.message = output;
messages.push_back(msg);
}
static void ParseData(ENetPacket* packet) {
int len = packet->dataLength;
std::vector<unsigned char> a_data;
std::cout << "Processing packet: " << len << "b ;";
for (int i = 0; i < len; i++)
{
a_data.push_back(packet->data[i]);
}
switch (a_data[0])
{
case NET_ARG_CHUNKF:
puts("Server sent a chunk, caching for later use");
worldPacketStack.push(a_data);
break;
case NET_ARG_PLRID:
plrId = a_data[1];
std::cout << "Server assigned ID:" << plrId << std::endl;
break;
case NET_ARG_PLRCON:
puts("Player connected");
if(a_data[1] != plrId) {
CreateNetPlayer(a_data[1]);
}
break;
case NET_ARG_PLRDCN:
puts("Player disconnected");
RemovePlayerEntity(a_data[1]);
break;
case NET_ARG_ECHOMOVE:
std::cout << "Server requested to sync pos " << (int)(a_data[1]) << std::endl;
if(a_data[1] == plrId) {
float x = DeserializeFloat(a_data[2],a_data[3]);
float y = DeserializeFloat(a_data[4],a_data[5]);
float z = DeserializeFloat(a_data[6],a_data[7]);
player_position.x = x;
player_position.y = y;
player_position.z = z;
}
for (Entity &plr : entities)
{
std::cout << (int)(plr.data[0]) << " " << (int)(plr.type) << std::endl;
if(plr.data[0] == a_data[1] && plr.type == 1) {
float x = DeserializeFloat(a_data[2],a_data[3]);
float y = DeserializeFloat(a_data[4],a_data[5]);
float z = DeserializeFloat(a_data[6],a_data[7]);
plr.position.x = x;
plr.position.y = y;
plr.position.z = z;
//plr.yaw = (short)floorf(a_data[8] / 255.0f* 65536);
plr.data[1] = 255;
std::cout << x << " " << y << " " << z << " " << (float)(plr.yaw) << std::endl;
}
}
break;
case NET_ARG_ECHOYAW:
std::cout << "Server requested to sync yaw " << (int)(a_data[1]) << std::endl;
for (Entity &plr : entities)
{
if(plr.data[0] == a_data[1] && plr.type == 1) {
plr.yaw = (short)floorf(a_data[2] / 255.0f * 65536);
}
}
break;
case NET_ARG_PLRREG:
puts("Got a connected player");
if(a_data[1] != plrId) {
CreateNetPlayer(a_data[1]);
}
for (Entity &plr : entities)
{
if(plr.data[0] == a_data[1] && plr.type == 1) {
float x = DeserializeFloat(a_data[2],a_data[3]);
float y = DeserializeFloat(a_data[4],a_data[5]);
float z = DeserializeFloat(a_data[6],a_data[7]);
plr.position.x = x;
plr.position.y = y;
plr.position.z = z;
plr.yaw = (short)floorf(a_data[8] / 255.0f * 65536);
}
}
break;
case NET_ARG_BLOCK:
SetBlock(world, a_data[2], a_data[3], a_data[4], a_data[5]);
dirtyChunks[GetIndexWorld(a_data[2] / 16, a_data[4] / 16)] = true;
if(a_data[2] == 0) {
dirtyChunks[GetIndexWorld(a_data[2] / 16-1, a_data[4] / 16)] = true;
}
if(a_data[2] == CHUNK_SIZE-1) {
dirtyChunks[GetIndexWorld(a_data[2] / 16+1, a_data[4] / 16)] = true;
}
if(a_data[4] == 0) {
dirtyChunks[GetIndexWorld(a_data[2] / 16, a_data[4] / 16-1)] = true;
}
if(a_data[4] == CHUNK_SIZE-1) {
dirtyChunks[GetIndexWorld(a_data[2] / 16, a_data[4] / 16+1)] = true;
}
break;
case NET_ARG_MESSAGEE:
ParseMessagePacket(a_data);
break;
default:
puts("Malformed data?");
break;
}
}
Vector3 player_positionPrev;
float yawPrev;
void NetworkUpdate() {
if(!online) return;
if(state == GAME_STATE_GAME) {
float dist = Vector3Distance(player_positionPrev, player_position);
if(dist > 0.05f) {
char data[8];
data[0] = NET_ARG_PLAYERMOVE;
SerializeFloat2Data(player_position.x, &data, 1);
SerializeFloat2Data(player_position.y, &data, 3);
SerializeFloat2Data(player_position.z, &data, 5);
float deg = camYaw * RAD2DEG;
int wrapped = (int)(deg + 180) % 360;
float scaled = wrapped / 360.0f * 255.0f;
int iv = (int)floorf(scaled + 0.5f);
if (iv < 0) iv = 0;
if (iv > 255) iv = 255;
data[7] = (uint8_t)iv;
SendPacket(peer, &data, sizeof(data));
player_positionPrev = player_position;
}
float yawDist = camYaw - yawPrev;
if(abs(yawDist) > 0.25f) {
char data[2];
data[0] = NET_ARG_PLAYERYAW;
float deg = camYaw * RAD2DEG;
int wrapped = (int)(deg + 180) % 360;
float scaled = wrapped / 360.0f * 255.0f;
int iv = (int)floorf(scaled + 0.5f);
iv &= 0xFF;
data[1] = (uint8_t)iv;
SendPacket(peer, &data, sizeof(data));
yawPrev = camYaw;
}
}
ENetEvent event;
while(enet_host_service(client, &event, 2) > 0)
{
switch(event.type)
{
case ENET_EVENT_TYPE_RECEIVE:
ParseData(event.packet);
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
puts("Disconnection succeeded.");
break;
}
}
}
void GameModeCleanupFull() {
if(online) {
enet_host_destroy(client);
online = false;
}
for (int idx = 0; idx < MAX_WORLD_AREA; idx++)
{
if(loadedChunks[idx]) {
//UnloadModel(renderingT[idx]);
//UnloadModel(rendering[idx]);
}
}
memset(loadedChunks, 0, sizeof(loadedChunks));
memset(dirtyChunks, 0, sizeof(dirtyChunks));
entities.clear();
}
void GameModeCleanup() {
state = GAME_STATE_TRANSITION;
SaveWorld(world, "tempworld.data");
SavePlayerData();
pauseMenuActive = false;
EnableCursor();
GameModeCleanupFull();
state = GAME_STATE_MAINMENU;
worldCreationProgress = 0;
worldCreationStep = 0;
}
extern void InitMenu();
int main(void)
{
SetTraceLogLevel(LOG_WARNING);
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
InitWindow(WIDTH, HEIGHT, GAME_NAME);
InitWorldgen(0);
InitAudioDevice();
InitMenu();
InputInit();
EntitiesInit();
LocaleUpdate();
LoadRecipes();
camera = { 0 };
camera.position = (Vector3){0, 0, 0};
camera.target = (Vector3){camera.position.x, camera.position.y, camera.position.z + 1};
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
camera.fovy = 90;
camera.projection = CAMERA_PERSPECTIVE;
fnt = LoadFontEx(Pathify("font.ttf"), 32, NULL, 1024*8);
terrain = LoadTexture(Pathify("terrain.png"));
GenTextureMipmaps(&terrain);
Image temp = GenImageColor(1, 1, WHITE);
white_pixel = LoadTextureFromImage(temp);
Image src = LoadImageFromTexture(terrain);
UVCorners uvs = GetUVTex(14);
Rectangle src0;
src0.x = uvs.corner2.x * terrain.width;
src0.y = uvs.corner2.y * terrain.height;
src0.width = (uvs.corner3.x - uvs.corner2.x) * terrain.width;
src0.height = (uvs.corner1.y - uvs.corner2.y) * terrain.height;
Image copied = ImageCopy(src);
ImageCrop(&copied, src0);
water = LoadTextureFromImage(copied);
SetTextureWrap(water, TEXTURE_WRAP_REPEAT);
SetTargetFPS(-1);
float time;
SetExitKey(KEY_NULL);
while (!WindowShouldClose())
{
NetworkUpdate();
InputUpdate();
while (worldPacketStack.size() > 0)
{
std::vector<unsigned char> data = worldPacketStack.top();
int idx = (int)(data[1]);
std::cout << "Processing chunk " << (int)idx << std::endl;
Chunk c;
int x, z;
GetXZFromIndex(idx, &x, &z);
c.x = (uint8_t)x;
c.z = (uint8_t)z;
std::cout << data.size() << std::endl;
for (int i = 2; i < CHUNK_DATA_SIZE+2; i++)
{
c.blocks[i-2] = data[i];
}
for (int i = CHUNK_DATA_SIZE+2; i < CHUNK_DATA_SIZE+2+CHUNK_SIZE*CHUNK_SIZE; i++)
{
c.highestBlock[i-CHUNK_DATA_SIZE-2] = data[i];
}
world[idx] = c;
dirtyChunks[idx] = true;
worldPacketStack.pop();
fetchedChunks++;
}
std::string text0 = LocaleGet("step_0");
char text[64];
switch (state)
{
case GAME_STATE_GAME:
GameUpdate(camera, time);
break;
case GAME_STATE_LOADING:
// World creation crap
BeginDrawing();
DrawBackground();
if(worldCreationStep == 1) {
text0 = LocaleGet("step_1");
}
if(worldCreationStep == 2) {
text0 = LocaleGet("step_2");
}
if(worldCreationStep == 3) {
text0 = LocaleGet("step_3");
}
DrawTextB(text0.c_str(), GetScreenWidth()/2 - 64, GetScreenHeight()/2 - 32, DEFAULT_FONT_SIZE, WHITE);
if(worldCreationStep != 1) {
DrawRectangle( GetScreenWidth()/2-64, GetScreenHeight()/2, 128, 16, BLACK);
DrawRectangle( GetScreenWidth()/2-62, GetScreenHeight()/2 + 2, 124 * ((float)worldCreationProgress / static_cast<float>(MAX_WORLD_AREA)), 12, GREEN);
sprintf(text, LocaleGet("chunk_status").c_str(), worldCreationProgress, MAX_WORLD_AREA);
DrawTextB(text, GetScreenWidth()/2 - 32, GetScreenHeight()/2 + 32, DEFAULT_FONT_SIZE, WHITE);
}
EndDrawing();
// * Well, there was a loop here.
if(worldCreationProgress < MAX_WORLD_AREA) {
for (int _ = 0; _ < 16; _++)
{
int x = worldCreationProgress % MAX_WORLD_SIZE;
int y = worldCreationProgress / MAX_WORLD_SIZE;
int idx = x + y * MAX_WORLD_SIZE;
if(worldCreationStep == 0 && !online) {
Chunk chunk = GenerateChunk(x, y);
world[idx] = chunk;
}
if(worldCreationStep == 1 && !online) {
if(worldCreationProgress > 0) {
worldCreationStep++;
}
else {
GenerateWorldAdditional(world);
}
}
if (worldCreationStep == 2)
{
//RebuildOpaqueMask(&world[idx]);
}
if (worldCreationStep == 3) {
dirtyChunks[idx] = true;
}
if(online)
worldCreationProgress = fetchedChunks+1;
}
}
else {
if(worldCreationStep <= 2) {
worldCreationProgress = 0;
worldCreationStep++;
}
else {
state = GAME_STATE_GAME;
GameStart();
break;
}
}
break;
case GAME_STATE_MAINMENU:
DrawMainMenu();
break;
case GAME_STATE_WORLDSELECT:
DrawWorldSelect();
break;
case GAME_STATE_MULTIPLAYERSELECT:
DrawMultiplayer();
break;
case GAME_STATE_TRANSITION:
DrawLogoCenter();
break;
case GAME_STATE_OPTIONS:
DrawControls();
break;
}
}
GameModeCleanupFull();
UnloadTexture(terrain);
CloseWindow();
return 0;
}