#include "raylib.h" #include "math.h" #include "raymath.h" #include #include #include "common.hpp" #include #include "rlgl.h" #include #include #include #include #include #include #include #include #include #include #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 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 entities; std::bitset clouds; std::queue tickedBlocks; struct MeshUnit { Model model; int vertices; }; struct ProfilerPart { std::string id; double time; Color color; }; std::vector 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> 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 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 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> 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 &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(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 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 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(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; }