1604 lines
57 KiB
C++
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(®istry[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;
|
|
} |